class MGit::Push

@!scope 类似 git push 可自动生成 git gerrit 评审分支 mgit push –gerrit

Constants

MGIT_PUSH_GERRIT_ENABLED

默认是否开启gerrit相关功能

MGIT_PUSH_TOPIC_ENABLED

默认是否开启topic相关功能,开启后强制开启gerrit

OPT_LIST

Attributes

gerrit_enabled[R]
topic_id[R]

Private Class Methods

description() click to toggle source
# File lib/m-git/command/push.rb, line 301
def self.description
  return "更新远程分支和对应的数据对象。"
end
usage() click to toggle source
# File lib/m-git/command/push.rb, line 305
def self.usage
  return "mgit push [<git-push-option>|--gerrit] [(--mrepo|--el-mrepo) <repo>...] [--topic] [--help]"
end

Public Instance Methods

__setup_option_value(argv) click to toggle source
# File lib/m-git/command/push.rb, line 36
def __setup_option_value(argv)
  group_id_opt = argv.opt(OPT_LIST[:topic_id])
  if group_id_opt
    @topic_id = group_id_opt.value
  elsif MGIT_PUSH_TOPIC_ENABLED
    @topic_id = SecureRandom.uuid
  end

  @gerrit_enabled = !@topic_id.nil? || argv.opt_list.did_set_opt?(OPT_LIST[:gerrit]) || MGIT_PUSH_GERRIT_ENABLED
end
execute(argv) click to toggle source
# File lib/m-git/command/push.rb, line 47
def execute(argv)
  __setup_option_value(argv)
  if argv.git_opts&.length > 0
    raws_string = ''
    argv.raw_opts.each do |raws|
      raws_string += ' '
      raws_string += raws.join(' ')
    end
    Foundation.help!("禁止使用参数 #{argv.git_opts}\n" + Output.remind_message("建议直接使用mgit #{argv.cmd}#{raws_string}"))
  end
  Workspace.check_branch_consistency

  Output.puts_start_cmd

  # 获取远程仓库当前分支信息
  Workspace.pre_fetch

  do_repos = []
  diverged_repos = []
  do_nothing_repos = []
  detached_repos = []
  no_remote_repos = []
  no_tracking_repos = []
  remote_inconsist_repos = []
  dirty_repos = []

  Output.puts_processing_message("检查各仓库状态...")
  Workspace.serial_enumerate_with_progress(all_repos) { |repo|
    Timer.start(repo.name)

    url_consist = repo.url_consist?
    branch_status = repo.status_checker.branch_status
    remote_inconsist_repos.push(repo) if !url_consist

    if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged]
      diverged_repos.push(repo)
    # 仅超前且url一致的仓库直接加入到操作集
    elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:ahead] && url_consist
      do_repos.push(repo)
    elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
      no_remote_repos.push(repo)
    elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
      no_tracking_repos.push(repo)
    elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
      detached_repos.push(repo)
    else
      do_nothing_repos.push(repo.name)
    end

    if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
      dirty_repos.push(repo)
    end

    Timer.stop(repo.name)
  }
  Output.puts_success_message("检查完成!\n")

  # 将没有远程分支的仓库纳入到本次操作的仓库中
  do_repos += no_remote_repos
  no_remote_repos = []

  if diverged_repos.length > 0 ||
    detached_repos.length > 0 ||
    no_remote_repos.length > 0 ||
    no_tracking_repos.length > 0 ||
    remote_inconsist_repos.length > 0 ||
    dirty_repos.length > 0
    remind_repos = []
    remind_repos.push(['远程分支不存在', no_remote_repos.map { |e| e.name }]) if no_remote_repos.length > 0
    remind_repos.push(['未追踪远程分支(建议:mgit branch -u origin/<branch>)', no_tracking_repos.map { |e| e.name }]) if no_tracking_repos.length > 0
    remind_repos.push(['HEAD游离,当前不在任何分支上', detached_repos.map { |e| e.name }]) if detached_repos.length > 0
    remind_repos.push(['当前分支与远程分支分叉,需先pull本地合并', diverged_repos.map { |e| e.name }]) if diverged_repos.length > 0
    remind_repos.push(['实际url与配置不一致', remote_inconsist_repos.map { |e| e.name }]) if remote_inconsist_repos.length > 0
    remind_repos.push(['有本地改动', dirty_repos.map { |e| e.name }]) if dirty_repos.length > 0
    Output.interact_with_multi_selection_combined_repos(remind_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 强制执行', 'c: 终止']) { |input|
      if input == 'b'
        do_repos += diverged_repos
        do_repos += detached_repos
        do_repos += no_remote_repos
        do_repos += no_tracking_repos
        do_repos += remote_inconsist_repos
        do_repos.uniq! { |repo| repo.name }
      elsif input == 'c' || input != 'a'
        Output.puts_cancel_message
        return
      end
    }
  end
  if do_repos.length == 0
    Output.puts_remind_message("仓库均无新提交,无须执行!")
    return
  end
  HooksManager.execute_mgit_pre_push_hook(argv.cmd, argv.pure_opts, do_repos.map { |e| e.config })

  # 跳过无法处理的异常状态仓库
  skip_repos = do_repos.select { |repo|
    branch_status = repo.status_checker.branch_status
    branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached] ||
        branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
  }
  Output.puts_remind_block(skip_repos.map { |e| e.name }, "以上仓库无法强制执行,已跳过。") if skip_repos.length > 0
  do_repos -= skip_repos
  if do_repos.length == 0
    Output.puts_success_message("仓库均无新提交,无须执行!")
    return
  end

  # 找到本次操作仓库中推新分支的仓库
  no_remote_repo_names = do_repos.select { |repo|
    branch_status = repo.status_checker.branch_status
    branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
  }.map { |repo| repo.name }

  count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
  Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始push以上仓库#{count_msg}...")

  # ------ 执行push ------
  total_task = do_repos.length
  Output.update_progress(total_task, 0)

  config_repo_arr = do_repos.select { |repo| repo.config.is_config_repo }
  do_repos_without_config_repo = do_repos - config_repo_arr

  sub_error_repos, sub_cr_repos = __execute_push(argv, do_repos_without_config_repo) { |progress|
    Output.update_progress(total_task, progress)
  }

  # 保证最后操作主仓库,便于流水线进行模块注册
  sub_task_count = do_repos_without_config_repo.length
  config_error_repos, config_cr_repos = __execute_push(argv, config_repo_arr) { |progress|
    Output.update_progress(total_task, progress + sub_task_count)
  }
  puts "\n"
  # ----------------------
  # 显示错误仓库信息
  error_repos = sub_error_repos.merge(config_error_repos)
  Workspace.show_error(error_repos) if error_repos.length > 0

  # 显示成功推了新分支的仓库
  success_push_branch_repo_names = no_remote_repo_names - error_repos.keys
  if success_push_branch_repo_names.length > 0
    Output.puts_remind_block(success_push_branch_repo_names, "为以上仓库推送了新分支。")
    puts "\n"
  end

  # 显示成功仓库的评审链接
  if gerrit_enabled
    success_output = ''
    all_cr_repos = sub_cr_repos.keys + config_cr_repos.keys
    all_cr_repos.uniq!
    all_cr_repos.each do |repo_name|
      cr_url = sub_cr_repos[repo_name] || config_cr_repos[repo_name]
      success_output += Output.generate_title_block(repo_name, has_separator: false) { cr_url } + "\n\n"
    end

    if success_output.length > 0
      Output.puts_remind_message("以下本地提交代码评审链接,请联系仓库负责人评审后合入:")
      puts success_output
    end

    # 显示topic id
    if topic_id
      success_push_code_repo_names = do_repos.map { |e| e.name } - error_repos.keys - success_push_branch_repo_names
      if success_push_code_repo_names.length > 0
        Output.puts_remind_message("本次push的topic id:#{topic_id}\n") if !topic_id.nil? && topic_id.length > 0
      end
    end
  end

  # 显示成功信息
  if error_repos.empty?
    Output.puts_succeed_cmd(argv.absolute_cmd)
    Timer.show_time_consuming_repos
  elsif topic_id
    # 显示失败后的操作提示,若全部成功则不显示
    group_repo_names = error_repos.keys
    group_repo_name_str = group_repo_names.join(' ')
    is_all = Workspace.is_all_exec_sub_repos_by_name?(group_repo_names)
    mrepo_str = is_all ? '' : " --mrepo #{group_repo_name_str}"
    Output.puts_processing_block(group_repo_names, "以上仓库组推送失败,请处理后用以下指令再次推送:\n\n    mgit push --topic #{topic_id}#{mrepo_str}\n")
  end

end
options() click to toggle source
Calls superclass method MGit::BaseCommand#options
# File lib/m-git/command/push.rb, line 23
def options
  [
      ARGV::Opt.new(OPT_LIST[:gerrit],
                    info:"开启gerrit功能,如果没有对应远程分支则推送新分支,否则推送到审查分支(refs/for/**),默认未开启",
                    type: :boolean),
      ARGV::Opt.new(OPT_LIST[:topic_id],
                    info:"指定一组变更的分类topic,若未指定则自动生成,默认未开启,开启后强制开启Gerrit功能。mgit push --topic 12345 = git push origin HEAD:refs/for/<branch>%topic=12345",
                    type: :string),
  ].concat(super)
end

Private Instance Methods

__execute_push(argv, do_repos) { |task_count| ... } click to toggle source
# File lib/m-git/command/push.rb, line 233
def __execute_push(argv, do_repos)
  mutex = Mutex.new
  error_repos = {}
  cr_repos = {}
  task_count = 0

  Workspace.concurrent_enumerate(do_repos) { |repo|
    cmd, opt = __parse_cmd_and_opt(repo)
    git_cmd = repo.git_cmd(cmd, opt)

    Utils.execute_shell_cmd(git_cmd) { |stdout, stderr, status|
      mutex.lock
      error_msg, cr_url = __process_push_result(repo, stdout, stderr, status)
      error_repos[repo.name] = error_msg if error_msg
      cr_repos[repo.name] = cr_url if cr_url

      task_count += 1
      yield(task_count) if block_given?
      mutex.unlock
    }
  }

  [error_repos, cr_repos]
end
__parse_cmd_and_opt(repo) click to toggle source
# File lib/m-git/command/push.rb, line 283
def __parse_cmd_and_opt(repo)
  cmd = 'push'
  if repo.status_checker.branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
    opt = "-u origin #{repo.status_checker.current_branch}"
  else
    opt = "origin HEAD:#{repo.status_checker.current_branch}"
    if gerrit_enabled
      opt = "origin HEAD:refs/for/#{repo.status_checker.current_branch}"
      opt += "%topic=" + topic_id if topic_id
    end
  end
  [cmd, opt]
end
__process_push_result(repo, stdout, stderr, status) click to toggle source

@return [String, String] error_message, code_review_url

# File lib/m-git/command/push.rb, line 262
def __process_push_result(repo, stdout, stderr, status)
  # 标记状态更新
  repo.status_checker.refresh

  # 本地成功但远程失败此时status.success? == true,解析以检测这个情况
  repo_msg_parser = GitMessageParser.new(repo.config.url)
  check_msg = repo_msg_parser.parse_push_msg(stderr)

  # 本地和远程同时成功
  if status.success? && check_msg.nil?
    cr_url = repo_msg_parser.parse_code_review_url(stdout) || repo_msg_parser.parse_code_review_url(stderr) if gerrit_enabled
  elsif status.success? && !check_msg.nil?
    # 本地失败
    # check_msg = error_msg
  else
    check_msg = stderr
    check_msg += stdout if stdout.length > 0
  end
  [check_msg, cr_url]
end
enable_repo_selection() click to toggle source
# File lib/m-git/command/push.rb, line 297
def enable_repo_selection
  return true
end