# Thorfile tasks/functions for git/clearcase integration
require 'ruby-debug'
require 'time'
class Cc < Thor
include Thor::Actions
@@git_root = "./" + `git rev-parse --show-cdup`.strip
@@cleartool =
(str = `git config clearcase.cleartool`.strip).empty? ? "cleartool" : str
@@ccbr =
(str = `git config clearcase.trackingbranch`.strip).empty? ? "master" : str
@@grace_sec = 60 * 1
desc 'test', 'test task'
method_options %w( logfile -l ) => :string
def test
if options[:logfile] && File.exist?(options[:logfile])
say options[:logfile], :green
else
say 'no logfile specified', :red
end
if run "ls tmp"
say "succ", :green
else
say "failed", :red
end
end
no_tasks do
def run_git(command, flag=nil)
config = ( :capture == flag ? {:capture => true} : {})
run("git #{command}", config)
end
def run_ct(command, flag=nil)
config = ( :capture == flag ? {:capture => true} : {})
run("#{@@cleartool} #{command}", config)
end
def isclean()
inside(@@git_root) do
status = run_git("status --porcelain", :capture)
return true if 0 == status.length
say status
say "-------- dirty workspace ------", :red
return false
end
end
def stage_ccobjects(objects)
objects.each do |o|
fn = o.sub(/@@.*$/,'')
run "rm -f #{fn}"
run_ct "get -to #{fn} '#{o}'"
run_git "add #{fn}"
end
end
def convert_changes(loghash, dry_run = false)
num_commits = 0
loghash.each do |k,v|
commit_time = v[0][:date]
say "=== commit: [#{num_commits+=1}/#{loghash.length}] ====", :green
say "user: #{k[:user]}"
say "action: #{k[:action]}"
say "log: #{k[:comment]}"
say "date: #{commit_time}"
say "objects:\n" + v.map{|i| " #{i[:object]}"}.join("\n")
open(".cc_commit_message", "w") do |f|
f.puts k[:comment] + "\n\n"
f.puts v.map{|i| " #{i[:object]}"}.join("\n")
end
objects = v.map{|i| i[:object]}.uniq
if dry_run
run_git "status"
say "======== dry_run: not commiting ==========", :red
next
end
stage_ccobjects(objects)
ENV["GIT_COMMITTER_NAME"] = k[:user]
ENV["GIT_AUTHOR_NAME"] = k[:user]
ENV["GIT_COMMITTER_EMAIL"] = " "
ENV["GIT_AUTHOR_EMAIL"] = " "
ENV["GIT_COMMITTER_DATE"] = v[0][:date]
ENV["GIT_AUTHOR_DATE"] = v[0][:date]
run_git "commit -F .cc_commit_message"
end
end
def analize_cchistory(cchistory, elems = {})
p cchistory
logs = cchistory.split(/--/)
return unless logs
loghash = Hash.new {|h,k| h[k] = []}
logs.each do |item|
h = item.split(/###\s+###/)
next unless h[3]
hh = h[3].split(/@@/)
key = {
:user => h[1],
:action => h[2],
:comment => (h[4] || "").strip
}
val = {
:object => h[3],
:file => hh[0],
:date => h[0]
}
if key[:comment].empty?
key[:comment] = "<<NO COMMENT @ #{val[:date].sub(/:\d\d-/,':00-')}>>"
end
if !elems[val[:file]] or elems[val[:file]].empty? or val[:object] =~ /#{elems[val[:file]]}\\\d+$/
loghash[key] << val
end
end
return Hash[ loghash.map {|k,v| [k, v.sort_by {|x| x[:date]}.reverse] } ]
end
def cc_update_view(time = nil)
cs = run_ct("catcs", :capture)
exit if cs.empty?
if time
if cs =~ /^time/
cs.sub!(/time .*(\s+)/) { "time " + time + $1 }
else
cs.sub!(/element \* CHECKEDOUT(\s+)/) {|x| x + "time " + time + $1}
end
else
cs.sub!(/^time .*\s*/, '')
end
open(".configspec", "w") {|f| f.print cs}
max_count = 0
say "=========== updating clearcase view... ==========="
if File.exists?(".cc_update_lines")
open(".cc_update_lines") {|f| max_count = f.gets.to_i}
end
open("| #{@@cleartool} setcs -force -overwrite .configspec", "r") do |io|
io.each do |line|
$stdout.print( max_count==0 ?
"\r#{io.lineno} lines" :
"\r#{100 * io.lineno / max_count} %" )
$stdout.flush
end
open(".cc_update_lines", "w") {|file| file.print io.lineno}
end
say ""
end
def git_current_branch()
org_branch = run_git("branch", :capture)[/\*.*/][2..-1]
if "(no branch)" == org_branch
say "=========== aborting: not on any branch. =========", :red
exit
end
return org_branch
end
def git_checkout_branch(org_branch)
unless @@ccbr == org_branch
say "----------- checking out original branch -----------"
run_git "checkout -f #{org_branch}"
end
end
def get_cc_history_bkup(files)
# Analyze git log for last pull time for changed files (pick the oldest commit)
time_format = '%d-%B-%Y.%H:%M:%S'
gitlog = run_git("log --pretty=format:'%ae####%cD'", :capture)
cclog = gitlog.grep(/^####/) # gitlog of the cc following branch
str_git_last_time =
if (cclog && cclog[0])
cclog[0][4..-1].strip
else
# there is no cclog, then time of the git repo initialization
gitlog.split(/####/)[-1].strip
end
last_time = (Time.rfc2822(str_git_last_time) - @@grace_sec).strftime(time_format)
# get clearcase history data for target time & files
logfmt = "'%d###\n###%u###\n###%e###\n###%n###\n###%c--'"
lshistory_params = "-fmt #{logfmt} -since #{last_time} #{files.join(' ')}"
cchistory = run_ct("lshistory #{lshistory_params}", :capture)
return analize_cchistory(cchistory)
end
def get_cc_history(files)
# Analyze git log for last pull time for changed files (pick the oldest commit)
time_format = '%d-%B-%Y.%H:%M:%S'
gitlog = run_git("log --pretty=format:'%ae####%cD'", :capture)
cclog = gitlog.grep(/^####/) # gitlog of the cc following branch
str_git_last_time =
if (cclog && cclog[0])
cclog[0][4..-1].strip
else
# there is no cclog, then time of the git repo initialization
gitlog.split(/####/)[-1].strip
end
pp last_time = (Time.rfc2822(str_git_last_time) - @@grace_sec).strftime(time_format)
# get clearcase visible branch for the target files.
branches = run_ct("describe -fmt '%n\n' #{files.join(' ')}", :capture).gsub(/.+\\(.+)\\(.+)/, '\1').split(/\s+/)
pp elems = Hash[files.zip(branches)]
# get clearcase history data for target time & files
logfmt = "'%d###\n###%u###\n###%e###\n###%n###\n###%c--'"
lshistory_params = "-fmt #{logfmt} -since #{last_time} #{files.join(' ')}"
cchistory = run_ct("lshistory #{lshistory_params}", :capture)
return analize_cchistory(cchistory, elems)
end
end
desc 'test', 'cmd test'
def test
files = ["Workstation/SynapseVer.h", "HTML/SynapseScripts/ExecuteNextStudy.js"]
get_cc_history(files)
end
desc "init [trackingbranch]", 'initialize cc tracking branch'
def init(trackingbranch = "master")
run_git "init"
run_git "config clearcase.trackingbranch #{trackingbranch}"
run "echo 'Thorfile' >| .git/info/exclude"
ENV["GIT_AUTHOR_EMAIL"] = " "
run_git "add ."
run_git "commit -m 'init'"
run_git "tag init"
end
desc "update [time]", 'force CC update into workspace without checkins'
def update(time = nil)
exit unless isclean()
cc_update_view(time)
inside(@@git_root) do
#run "chmod -R +w ../SourceCode"
run_git "status"
end
end
desc 'pull', 'pull changes from upstream CC into repo'
method_options %w( dry-run -n ) => :boolean
def pull
exit unless isclean()
org_branch = git_current_branch()
inside(@@git_root) do
begin
run_git "checkout #{@@ccbr}"
cc_update_view()
say "========= track changes ==========="
say status = run_git("status --porcelain", :capture)
0 == status.length and raise "==== No upstream change detected. ===="
loghash = get_cc_history(status.split(/\n/).map{|line| line[3..-1]})
open(".cclog", "w") {|fp| fp.print loghash.inspect}
if loghash
loghash = loghash.select{|k,v| k[:action]=~/create/}.sort_by{|i| i[1][0][:date]}
end
convert_changes(loghash, options["dry-run"]) unless loghash.empty?
# re-update and checkin the changes.
cc_update_view()
if !run_git("status --porcelain", :capture).empty?
say "==== comming the detected upstream changes ====", :green
if options["dry-run"]
say "===== dry run: not merging or commiting =====", :green
else
run_git "add --all"
run_git "commit -m '<UPSTREAM CHANGE COULD NOT BE TRACKED>'"
end
end
rescue => evar
say evar, :red
ensure
#run "chmod -R +w ../SourceCode"
git_checkout_branch(org_branch)
end
end
end
desc 'merge [target_branch]', "merge changesets from specified target (default current) branch to #{@@ccbr} and commit them to CC"
method_options %w( dry-run -n ) => :boolean
method_options %w( file -F ) => :string
def merge(tgt_branch = nil)
exit unless isclean()
org_branch = git_current_branch()
tgt_branch ||= org_branch
logfile = options[:file]
begin
say "----------- checking out #{@@ccbr} -----------"
run_git "checkout #{@@ccbr}"
stat = run_git("diff --name-status ...#{tgt_branch}", :capture)
stat.empty? and raise "---- no difference ---"
mod_files = stat.split(/\n/).map{|s| s.gsub!(/^[^A]\t/,'')}.compact
add_files = stat.split(/\n/).map{|s| s.gsub!(/^A\t/,'')}.compact
mod_filestr = (mod_files + add_files.map{|fn| File.dirname(fn)}).uniq.join(" ")
add_filestr = add_files.join(" ")
run_ct "update -overwrite #{mod_filestr}"
isclean() or raise "---- detected upstream change. aborting... ------"
unless logfile && File.exist?(logfile)
run_git "log --format=format:'- %s%n%b%n' #{@@ccbr}..#{tgt_branch} > .cc.log"
run "cp .cc.log .cc.log.bkup"
run "$EDITOR .cc.log"
run "diff .cc.log .cc.log.bkup" and raise "commit log has to be updated."
logfile = ".cc.log"
end
unless run_ct "checkout -nquery -reserved -cfile #{logfile} #{mod_filestr}"
run_ct "uncheckout -rm #{mod_filestr}"
raise "----------- clearcase checkout failed -------------"
end
if options["dry-run"]
say "===== dry run: not merging or commiting =====", :green
else
run_git "merge --no-ff --no-commit --stat #{tgt_branch}"
run_ct "mkelem -cfile #{logfile} -ci #{add_filestr}" if !add_files.empty?
say "===== do clearcase & git commiting =====", :green
run_ct "checkin -identical -nc #{mod_filestr}"
run_git "commit -F #{logfile}"
end
rescue => evar
say "aborting the merge: #{evar}", :red
ensure
#say "========= change permission ==========="
#run "chmod -R +w ../SourceCode"
run_git "reset --hard"
git_checkout_branch(org_branch)
end
end
end
# vim: ft=ruby