{"thread":{"id":"4787","subject":"[ANNOUNCE] ClearCase UCM -> git converter","startedAt":"2006-07-06T17:36:15Z","lastAt":"2006-07-06T17:36:15Z","messageCount":1,"participants":["Rutger Nijlunsing"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"23328","messageId":"20060706173615.GA7395@nospam.com","threadId":"4787","inReplyTo":null,"subject":"[ANNOUNCE] ClearCase UCM -> git converter","fromName":"Rutger Nijlunsing","fromEmail":"rutger@nospam.com","sentAt":"2006-07-06T17:36:15Z","receivedAt":"2006-07-06T17:36:15Z","isPatch":false,"sender":{"key":"rutger.nijlunsing@gmail.com","avatar":null},"body":"Hi,\n\nFind attached a ClearCase UCM -> git converter.\n\nWhile UCM does have branches and so on, this importer imports one\nstream. Since UCM is somewhat like an expensive CVS, it is quite\ndifficult to retrieve more information from UCM in a useful way: the\nconcept are just too poluted.\n\nFeatures:\n  - Author files support to map ClearCase usernames to git user names\n  - Incrementally import one ClearCase UCM stream into git.\n    Just rerun the script when new baselines were created.\n  - Handles composite baselines\n  - Auto-detection of VOB, stream, rules to load, .. as much\n    as possible.\n  - Auto repack after a lot of objects have been generated\n  - Should be able to pick up where it left after an interruption.\n\nHOWEVER, it is Windows only currently since I don't have a cleartool\nunix client. It should not be too hard to convert though, since it\nuses the normal 'cleartool' command line client to retrieve all\ninformation.\n\nUsage:\n\nJust have your stream-to-be-converted running at, say, m:\\mystream_int\n(most likely an integration stream), and say something like:\n\n\tgit-ucmimport m:\\mystream_int c:\\mystream_git\n\n...and hopefully it should detect all configuration items and start\nconverting.\n\nCan also be found at http://www.wingding.demon.nl .\n\nRutger.\n\n-- \nRutger Nijlunsing ---------------------------------- eludias ed dse.nl\nnever attribute to a conspiracy which can be explained by incompetence\n----------------------------------------------------------------------\n\n#!/usr/bin/env ruby\n\n# ClearCase UCM -> git importer.\n#\n# Imports the baselines of _one_ stream into a Git repository.\n#\n# Features:\n#   - Author files support to map ClearCase usernames to git user names\n#   - Incrementally import one ClearCase UCM stream into git.\n#     Just rerun the script when new baselines were created.\n#   - Handles composite baselines\n#   - Auto-detection of VOB, stream, rules to load, .. as much\n#     as possible.\n#   - Auto repack after a lot of objects have been generated\n#   - Should be able to pick up where it left after an interrruption.\n#\n# Assumptions:\n#   - Works on Windows for me(TM). Not tested on Unix.\n#\n# Discussion:\n# Importing the whole tree of streams (branches) of UCM into git is\n# next to impossible. UCM has the concepts of 'activities' which\n# contain versions of changed files, and a set of activities is\n# delivered as a changeset. So far so good. However, activities may be\n# reused (even over changeset boundary) and may be interleaved with\n# each other. This makes it infeasible to map activities or changesets\n# on git commits, which is a much nicer concept.\n#\n# 2006 Rutger Nijlunsing <git-ucmimport@tux.tmfweb.nl>\n\n############################## Config documentation.\n# Don't change those; just use the command line switches to set them.\n# Section below is mainly for documentation purposes. ;)\n\n# ClearCase: Directory with existing Dynamic view on integration\n# stream to import. Probably a subdirectory on M:\\, or under /view\n$int_view_dir = \"\"\n\n# Location of snapshot view on substream of integration stream. We are\n# going to rebase this stream to a newer version in int_view, and then\n# check in the result.\n$sub_view_dir = \"\"\n\n# Baseline in parent stream to start importing at if the destination\n# snapshot view does not exist.\n# If empty, takes first baseline.\n# Use 'cleartool lsbl' in stream directory to get a list.\nfirst_baseline = \"\"\n\n# Subdirectory of $sub_view_dir to generate git repository at.  Only\n# files in this directory or lower are added. This can be used when a\n# common subdirectory is always added to the paths (which is common in\n# ClearCase). If unsure, leave empty.\n$sub_root = \"\"\n\n# Filename with author mapping from ClearCase to Git. Leave empty (\"\")\n# to have no mapping and use the ClearCase names.\n# Format of file is lines containing:\n#   'cc_author_name=Real Name <email@domain.com>'\n$ucm_authors_file = \"\"\n\n# Server Storage Location for the view storage directory.\n# This is the same as can be found in the GUI:\n#   ClearCase Explorer -> Join Project -> Next -> Next -> Next ->\n#     Advanced Options -> [x] Use server storage location,\n# and then the 'name' column of that table.\n# Use empty string (\"\") for default.\nstgloc = \"ADVIEWS\"\n\n##############################\n\n# Include required standard Ruby libraries\nrequire 'find'\nrequire 'fileutils'\nrequire 'time'\nrequire 'optparse'\nrequire 'ostruct'\n\n$VERBOSE = true\n$verbose = true\n\ndef die(*txt); puts txt.flatten.join(\"\\n\"); exit 1; end\n\ndef read_ucm_authors(authors_filename)\n  users = {}\n  begin\n    IO.foreach(authors_filename) { |line|\n      if line =~ %r{^(\\S+?)\\s*=\\s*(.+?)\\s*<(.+)>\\s*$}\n        user, name, email = $1, $2, $3\n        users[user] = [name, email]\n      end\n    }\n  rescue Errno::ENOENT, Errno::EACCES\n    die(\"Could not read #{authors_filename}: #{$!}\")\n  end\n  if $verbose\n    puts \"Read #{users.size} authors from #{authors_filename}\"\n  end\n  return users\nend\n\ndef write_ucm_authors(users, authors_filename)\n  begin\n    File.open(authors_filename, \"wb\") { |io|\n      users.keys.sort.each { |user|\n        io.puts \"#{user} = #{users[user][0]} <#{users[user][1]}>\"\n      }\n    }\n    if $verbose\n      puts \"Wrote #{users.size} authors to #{authors_filename}\"\n    end\n  rescue Errno::EACCES\n    die(\"Could not write #{authors_filename}: #{$!}\")\n  end\nend\n\ndef read_write_ucm_authors\n  # Maping from ClearCase author name to full name and email\n  default_ucm_authors_file = \"#{$git_dir}/ucm-authors\"\n  $users = File.readable?(default_ucm_authors_file) ?\n  read_ucm_authors(default_ucm_authors_file) : {}\n  # If authors file explicitly given, add\n  if $ucm_authors_file && $ucm_authors_file != \"\"\n    users_to_add = read_ucm_authors($ucm_authors_file)\n    if users_to_add.size > 0\n      $users.merge!(users_to_add) \n      write_ucm_authors($users, default_ucm_authors_file)\n    end\n  end\nend\n\nmodule Shell\n  # Escape string string so that it is parsed to the string itself\n  # Compare to Regexp.escape .\n  def self.escape(string)\n    (string !~ %r{[ \"]}i && string != \"\") ? \n      string : '\"' + string.gsub(%r{([\"])}i, '\\\\\\\\\\1') + '\"'\n  end if not defined? self.escape\nend\n\n# Run 'cleartool' and filter the output.\ndef system_filter_output(cmd, may_fail)\n  no_merge_required = false\n  hijacked = []\n  p cmd\n  File.popen(\"#{cmd} 2>&1\", \"rb\") { |io|\n    while line = io.gets\n      line = line.rstrip\n      next if line =~ %r{^[.]*$}\n      next if line =~ %r{^End dir }\n      next if line =~ %r{^Processing dir }\n      next if line =~ %r{^Done loading }\n      next if line =~ %r{^Log has been written to }\n      next if line =~ %r{^Making dir } # Only interested in files...\n      line = line.\n\tsub(%r{^Loading }, \"+ \").\n\tsub(%r{^Unloaded }, \"- \")\n      no_merge_required = true if line =~ %r{^No versions require merging}\n      if line =~ %r{^Keeping hijacked object \"(.*)\"}\n\thijacked << $1\n      else\n\traise if line =~ %r{hijacked}\n      end\n      puts line\n    end\n  }\n  # Some hijacked files might be left. Update them manually\n  hijacked.each { |f|\n    puts \"Unhijacking #{f}\"\n    safe_system(\"cleartool update -overwrite #{Shell.escape(f)}\")\n  }\n  ok = no_merge_required\n  if !ok && !may_fail\n    die(\n\t\"!!! View at #{Dir.pwd} is not clean.\",\n\t\"!!! No checkout out files, hijacked files etc. may be present.\",\n\t\"!!! Restore the view (undo hijacking, undo checkouts etc.) and retry.\"\n    )\n  end\n  return ok\nend\n\ndef skip(*cmd); puts \"Skipping #{cmd.inspect}\"; end\n\n# Run a command with its arguments.\n# When cmd is an array, a boolean indicates whether the next argument should\n# be emitted or not.\ndef safe_system(*cmd)\n  emit = true\n  cmdline = [cmd].flatten.collect { |arg|\n    if (arg == true) || (arg == false)\n      emit = arg\n      arg = nil\n    else\n      arg = nil if !emit\n      emit = true\n    end\n    arg.to_s\n  }.join(\" \")\n  puts cmdline if $verbose\n  system(cmdline)\n  if $? != 0\n    puts cmdline if !$verbose\n    puts \"!!! Command returned non-zero exit code: #{$?}\"\n    puts \"!!! Working dir: #{Dir.pwd}\"\n    exit $?\n  end\nend\n\ndef safe_popen(cmd, mode = \"w+\", &callback)\n  puts \"|\" + cmd if $verbose\n  res = IO.popen(cmd, mode, &callback)\n  if $? != 0\n    puts cmd if !$verbose\n    puts \"!!! Command returned non-zero exit code: #{$?}\"\n    puts \"!!! Working dir: #{Dir.pwd}\"\n    exit $?\n  end\n  return res\nend\n\n# Returns all rules loaded in the current directory\ndef read_loaded_rules\n  rules = {}\n  File.popen(\"cleartool catcs\", \"rb\") { |io|\n    while line = io.gets\n      rules[$1] = true if line =~ %r{^element \"\\[[0-9a-f]{32}=(.*)\\].*\\\" }\n    end\n  }\n  rules = rules.keys\n  return rules\nend\n\nclass Activity\n  attr_accessor :id, :author, :comment\n\n  def is_activity; @id !~ %r{^(deliver|rebase)\\.}; end\n\n  def initialize(id, author, comment)\n    @id = id\n    @author = author\n    @comment = comment\n  end\n\n  def hash; @id.hash; end\n  def eql?(other); @id == other.id; end\nend\n\n# A baseline of a stream\nclass Baseline\n  attr_reader :name\t\t# Name of baseline\n  attr_reader :date\t\t# Date of creation of baseline\n  attr_reader :author\t\t# User who created the baseline\n  attr_reader :comment\n  attr_reader :children\t\t# For composite baselines\n  attr_reader :parent\t\t# For non-composite baselines\n  attr_accessor :component\t# String\n\n  def initialize(name, date, author, comment)\n    @name = name\n    @date = date\n    @author = author\n    @comment = comment\n    @children = []\n    @parent = nil\n  end\n  \n  def set_parent(b); @parent = b; b.add_child(self); end\n  def add_child(baseline); @children << baseline; end\n\n  # Returns self and all children (recursively)\n  def children_recursive\n    res = [self]\n    children.each { |c| res += c.children_recursive }\n    return res\n  end\nend\n\n# Make sure .git exists and save the state\ndef git_init_db\n  Dir.chdir($git_root)\n  if !File.directory?($git_dir)\n    puts \"\\n=== Creating initial git repository at #{$git_root}\"\n    safe_system(\"git init-db\")\n    if !File.directory?($git_dir)\n      die(\"!!! Could not create git repo at #{$git_root}\")\n    end\n  end\n  save_state\nend\n\ndef git_commit(baseline, msg)\n  Dir.chdir($sub_view_dir)\n  puts \"\\n=== Removing ClearCase left-over files...\"\n  Find.find(\".\") { |elem|\n    if elem =~ %r{\\.(contrib|keep|renamed|unloaded|loading|mkelem)(\\.\\d+)?$} ||\n\telem =~ %r{\\.(updt|stackdump)$}\n      puts \"Removing #{elem}\"\n      FileUtils.rm_rf(elem)\n    end\n  }\n\n  Dir.chdir($git_root)\n  puts \"\\n=== Adding files to git\"\n  safe_system(\"git add .\")\n\n  author = baseline.author\n  email = nil\n  author, email = $users[author] if $users[author]\n  author ||= \"unknown\"\n  email ||= \"unknown\"\n\n  ENV['GIT_AUTHOR_NAME'] = ENV['GIT_COMMITTER_NAME'] = author\n  ENV['GIT_AUTHOR_EMAIL'] = ENV['GIT_COMMITTER_EMAIL'] = email\n  date = baseline.date\n  if date\n    puts \"Date: #{date}\"\n    ENV['GIT_AUTHOR_DATE'] = ENV['GIT_COMMITTER_DATE'] =\n      date.strftime(\"+0000 %Y-%m-%d %H:%M:%S\")\n  else\n    ENV.delete('GIT_AUTHOR_DATE')\n    ENV.delete('GIT_COMMITTER_DATE')\n  end\n\n  puts \"\\n=== Committing new version to git\"\n  puts \"Commit message:\"\n  puts msg\n  # 'git commit' might fail if no data is added, so no safe_popen()\n  cmd = \"git commit --no-verify -F - --all\"\n  File.popen(cmd, \"wb\") { |io|\n    io.print msg\n  }\n\n  puts \"\\n=== Tagging baseline in git\"\n  system(\"git tag -f #{baseline.name}\")\nend\n\n$state_version = \"20060706\"\n\n# Load state. Returns true in case of succes.\ndef load_state\n  begin\n    state = File.open($state, \"rb\") { |io| Marshal.load(io) }\n    if state.version != $state_version\n      die(\n        \"!!! Previous state has different version (#{state.version}) \",\n        \"!!! than this git-ucmimport version (#{$state_version})\"\n      )\n    else\n      $int_view_dir = state.int_view_dir\n      $sub_view_dir = state.sub_view_dir\n      $sub_root = state.sub_root\n      return true\n    end\n  rescue Errno::ENOENT\n  end\n  return false\nend\n\ndef save_state\n  File.open($state + \".new\", \"wb\") { |io|\n    state = OpenStruct.new\n    state.version = $state_version\n    state.int_view_dir = $int_view_dir\n    state.sub_view_dir = $sub_view_dir\n    state.sub_root = $sub_root\n    io.write(Marshal.dump(state))\n  }\n  # Rename atomically new state into _the_ state\n  File.rename($state + \".new\", $state)\nend\n\n######################################## Start of main\n\n# Make sure Cygwin is in front of path. Otherwise we might fail\n# if other Unix toolkits (like NutCracker) are simultanously installed.\nENV['PATH'] = \"c:\\\\cygwin\\\\bin;\" + ENV['PATH']\n\n$state = \".git/git-ucmimport.state\"\nstate_loaded = false\nstate_loaded ||= load_state if File.exists?($state)\n\nputs \"ClearCase UCM -> git importer\"\nputs \"(c)2006 R. Nijlunsing <git-ucmimport@tux.tmfweb.nl>\"\nputs \"License: LGPL.\"\nputs\n$opts = OptionParser.new\n$opts.banner = %q{Usage:\n  git-ucmimport [dynamic view to import] [dir to export to]\n     For the initial conversion.\n\n  git-ucmimport\n     From the git repository to incrementally convert.\n\nExample:\n  git-ucmimport --authors-file c:\\\\temp\\\\authors m:\\\\jparser_int c:\\\\jparser_git\n  ...to import the stream of view 'jparser_int' which is started at\n  m:\\\\jparser_int into newly-generated stream, view and directory at\n  c:\\\\jparser_git .  c:\\\\temp\\\\authors contains the usernames mapping.\n  c:\\\\jparser_git will both be a UCM view as well as an git repo, but will\n  be read-only! So to develop against the converted archive, use 'git clone'.\n\n  To convert the view incrementally, go to the root of the git repository\n  (which contains the '.git' directory), and call 'git-ucmimport' without\n  any parameters. In this example case that would be from c:\\\\jparser_git .\n\n  For more information regarding the options, read the top of this script.\n\n}\n$opts.on(\"--help\", \"-h\", \"This usage\") { puts $opts; exit 1 }\n$opts.on(\n  \"--authors-file FILE\", \"-A\",\n  \"Add file with mapping from ClearCase user\", \"to git author\"\n) { |f| $ucm_authors_file = File.expand_path(f) }\n$opts.on(\"\\nOptions for first-time import:\")\n$opts.on(\n  \"--stgloc LOCATION\",\n  \"Server Storage Location for the view\",\n  \"storage directory [chosen by UCM]\"\n) { |s| stgloc = s }\n$opts.on(\n  \"--first-baseline BASELINE\",\n  \"Start importing at given baseline\", \"[oldest baseline]\"\n) { |bl| first_baseline = bl }\n$opts.on(\n  \"--root DIRINREPO\",\n  \"Make given directory the root of the git\", \"repository\"\n) { |sr| $sub_root = sr }\n$opts.on(\"\")\nbegin\n  $opts.parse!(ARGV)\nrescue OptionParser::InvalidOption\n  die(\"!!! Invalid option\", $opts)\nend\n\nif !state_loaded\n  if ARGV.size != 2\n    puts $opts\n    die(\"!!! Need at least the import and export directory\")\n  end\n  $int_view_dir, $sub_view_dir = ARGV\nend\n\n# Git: Location of git archive\n$git_root = $sub_view_dir\n$git_root += \"/\" + $sub_root if $sub_root && $sub_root != \"\"\n$git_dir = \"#{$git_root}/.git\"\n$state = \"#{$git_dir}/git-ucmimport.state\"\n\nputs \"UCM Source:      Dynamic view at \\\"#{$int_view_dir}\\\"\"\nputs \"UCM Mirror repo: Snapshot view at \\\"#{$sub_view_dir}\\\"\"\nputs \"git repo:        At \\\"#{$git_root}\\\"\"\nputs \"Start baseline:  At #{first_baseline}\" if first_baseline && first_baseline != \"\"\nputs\n\n# Check for existance of git in $PATH\nsafe_system(\"git version\")\n\nif !File.directory?($int_view_dir)\n  die(\n      \"!!! Cannot find \\\"#{$int_view_dir}\\\",\",\n      \"!!! which should contain integration view directory.\",\n      \"!!! Did you start your view?\"\n  )\nend\n\nputs \"\\n=== Retrieving all current baselines to import\"\nDir.chdir($int_view_dir)\nvob = nil\t\t\t# String: VOB\nint_stream = nil\t\t# String: name of stream\nbaselines = {}\t\t\t# From name to Baseline\nbaseline = nil\n#File.open(\"c:\\\\temp\\\\lsbl.memberof\", \"rb\") { |io|\nFile.popen(\"cleartool lsbl -member_of\", \"rb\") { |io|\n  while line = io.gets\n    line = line.rstrip\n    if line =~ %r{^([^\\s]+)\\s+([^\\s]+)\\s+([^\\s]+)\\s+\\\"(.*)\\\"$}\n      # New baseline found\n      date, name, author, comment = $1, $2, $3, $4\n      $VERBOSE = false\t\t# Time.parse warns about year...\n      baseline = Baseline.new(name, Time.parse(date), author, comment)\n      $VERBOSE = true\n      $stderr.print \".\" if baselines.size % 10 == 0\n      baselines[name] = baseline\n    elsif line =~ %r{^  component: ([^ @]+)@([^ ]+)$}\n      baseline.component = $1\n      vob = $2\n    elsif line =~ %r{^  stream: ([^ @]+)@([^ ]+)$}\n      int_stream = $1\n    elsif line =~ %r{^    ([^ @]+)@([^ ]+) \\(.*\\)}\n      # 'member of:' line    \n      baseline.set_parent(baselines[$1])\n    end\n  end\n}\n$stderr.puts\n# We only need to keep the root of all composite baselines sorted on time.\nbaselines =\n  baselines.keys.\n  find_all { |n| baselines[n].parent.nil? }. # Only keep root baselines\n  collect { |n| baselines[n] }.\n  sort_by { |b| b.date }\t# Sort them on date\nputs \"Found #{baselines.size} (composite) baselines.\"\n\ncomponents = baselines.collect { |b| b.component }.uniq\nif components.size != 1\n  die(\n      \"!!! Not all baselines belong to the same component!\",\n      \"!!! Too afraid to continue.\",\n      \"!!! Components found: #{components.inspect}\"\n  )\nend\n\nif !first_baseline || first_baseline == \"\"\n  first_baseline = baselines[0].name\nend\nfirst_baseline_idx = nil\nbaselines.each_with_index { |b, idx|\n  first_baseline_idx = idx if b.name == first_baseline\n}\nif !first_baseline_idx\n  die(\"!!! Could not find first_baseline with name \\\"#{first_baseline}\\\"\")\nend\n\n# Create destination stream if needed\nif !File.exist?($sub_view_dir)\n  puts \"\\n=== Reading all rules loaded in stream to import\"\n  Dir.chdir($int_view_dir)\n  loaded_rules = read_loaded_rules\n  puts \"Currently loaded rules: #{loaded_rules.inspect}\"\n\n  sub_stream = File.basename($sub_view_dir)\n  puts \"\\n=== Creating read-only stream '#{sub_stream}'\"\n  safe_system(\n    \"cleartool mkstream\",\n    \"-in stream:#{int_stream}@#{vob}\",\n    \"-readonly\",\n    \"-baseline baseline:#{first_baseline}\",\n    \"#{sub_stream}@#{vob}\"\n  )\n\n  Dir.chdir(File.dirname($sub_view_dir))\n  puts \"\\n=== Creating snapshot view at #{$sub_view_dir}\"\n  safe_system(\n    \"cleartool mkview\",\n    \"-snapshot -ptime\",\n    \"-tag #{sub_stream}\",\n    \"-stream #{sub_stream}@#{vob}\",\n    stgloc && stgloc != \"\", \"-stgloc #{stgloc}\",\n    sub_stream\n  )\n  $sub_view_dir = Dir.pwd + \"/\" + sub_stream\n\n  puts \"\\n=== Load rules and update view\"\n  Dir.chdir($sub_view_dir)\n  loaded_rules.each { |rule|\n    system_filter_output(\"cleartool update -overwrite -add_loadrules #{rule[1..-1]}\", true)\n  }\n\n  git_init_db\n  read_write_ucm_authors\n  git_commit(baselines[first_baseline_idx], \"Initial commit\")\nelse\n  read_write_ucm_authors\n  puts \"\\n=== Updating view to be sure snapshot view matches UCM contents\"\n  Dir.chdir($sub_view_dir)\n  system_filter_output(\"cleartool update -overwrite\", true)\nend\n\ngit_init_db\n\nputs \"\\n=== Finding current baseline\"\nDir.chdir($sub_view_dir)\ncmd = \"cleartool lsstream -long -obsolete\"\np cmd\nlsstream = `#{cmd}`\nif lsstream !~ %r{^\\s+foundation baselines:\\s+([^@]+)@}m\n  puts lsstream\n  die(\"!!! Could not retrieve current baseline\")\nend\ncurrent_baseline = $1\nputs \"Current baseline: #{current_baseline}\"\nif current_baseline =~ %r{INITIAL$}\n  die(\n      \"!!! Current baseline should be newer than INITIAL\",\n      \"!!! Please rebase #{$sub_view_dir} to a newer baseline.\"\n  )\nend\n\ncurrent_baseline_idx = nil\nbaselines.each_with_index { |b, idx|\n  current_baseline_idx = idx if b.name == current_baseline\n}\n\nif !current_baseline_idx\n  die(\n    \"!!! Did not find our current baseline in Integration Stream.\",\n    \"!!! Make sure that stream of view #{$sub_view_dir} is really\",\n    \"!!! a child of stream of view #{$int_view_dir}\"\n  )\nend\n\nbaselines_to_commit = baselines[current_baseline_idx + 1 .. -1]\n\nif baselines_to_commit.empty?\n  puts \"\\nNothing to do, already up-to-date.\"\n  exit 0\nend\n\nputs \"Got #{baselines_to_commit.size} baselines to convert to git commits\"\n\nputs \"\\n=== Abort previous rebase attempt (if any).\"\nDir.chdir($sub_view_dir)\nsystem_filter_output(\"cleartool rebase -cancel -force\", true)\n\nbaselines_to_commit.each_with_index { |baseline, idx|\n  # If a lot of loose objects in Git, repack.\n  # Counts numbers of 'objects' directories, 256 max.\n  Dir.chdir($git_root)\n  if Dir[\".git/objects/??\"].size >= 253\n    puts \"\\n=== Repacking.\"\n    # To err is non-fatal, so no safe_system is needed.\n    system(\"git repack -d\")\n  end\n\n  puts\n  puts \"\\n=== [#{idx + 1}/#{baselines_to_commit.size}]\"\n  puts \"=== Reading effect of rebasing of #{$sub_view_dir} to baseline #{baseline.name}\"\n  Dir.chdir($sub_view_dir)\n  activities = []\n\n  # In the composite baseline, we get little information about the\n  # activities we bring in. Therefore, ask all contained baselines\n  # about the activities it contains.\n  baseline_names = baseline.\n    children_recursive.collect { |cbl| cbl.name }.join(\",\")\n  cmd = \"cleartool rebase -preview -baseline #{baseline_names}\"\n  safe_popen(cmd, \"rb\") { |io|\n    while line = io.gets\n      line = line.rstrip\n      if line =~ %r{^\\tactivity:([^\\t]*)\\t([^\\t]*)\\t\\\"(.*)\\\"$}\n\tid, author, comment = $1, $2, $3\n\tactivities << Activity.new(id, author, comment)\n      end\n    end\n  }\n  activities = activities.uniq\n\n  if activities.empty?\n    puts \"? New baseline contains no new activities, skipping\"\n    next\n  end\n\n  puts \"Will retrieve activities:\"\n  activities.each { |a|\n    puts \"  #{a.author} #{a.id}\"\n  }\n\n  system_filter_output(\"cleartool rebase -qall -baseline #{baseline.name}\", false)\n\n  subject =\n    activities.find_all { |a| a.is_activity }.\n    collect { |a| a.comment }.join(\" / \") + \"\\n\\n\"\n  msg = subject +\n    activities.collect { |a| \"#{a.comment}\\nActivity: #{a.id}\\n\\n\" }.join(\"\")\n  git_commit(baseline, msg)\n\n  puts \"\\n=== Completing ClearCase UCM rebase.\"\n  system_filter_output(\"cleartool rebase -complete\", true)\n}\n"}]}