class MGit::Rebase

@!scope 类似 git rebase

Constants

OPT_LIST
PROGRESS_AUTO
PROGRESS_STAGE
PROGRESS_STAGE_KEY

Public Class Methods

description() click to toggle source
# File lib/m-git/command/rebase.rb, line 332
def self.description
  "重新将提交应用到其他基点,该命令不执行lock的仓库。"
end
usage() click to toggle source
# File lib/m-git/command/rebase.rb, line 336
def self.usage
  "mgit rebase [<git-rebase-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]\nmgit rebase --continue\nmgit rebase --abort"
end

Public Instance Methods

check_master_rebase(argv) click to toggle source
# File lib/m-git/command/rebase.rb, line 287
def check_master_rebase(argv)
  opt_arr = argv.git_opts(raw:false)
  opt_arr.each { |opts|
    Foundation.help!("当前版本不支持\"-i\"或\"--interactive\"参数,请重试。") if ['-i','--interactive'].include?(opts.first)
  }
end
continue_execute(cmd, opts, repo, check_point, auto_update) click to toggle source
# File lib/m-git/command/rebase.rb, line 166
def continue_execute(cmd, opts, repo, check_point, auto_update)

  # 现场信息
  exec_subrepos = all_repos(except_config:true)
  is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
  context = OperationProgressContext.new(__progress_type)
  context.cmd = cmd
  context.opts = opts
  context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
  context.branch = repo.status_checker.current_branch(use_cache:true)

  # 更新主仓库
  update_config_repo(repo, context, auto_update) if check_point < PROGRESS_STAGE[:did_pull_config]

  if check_point < PROGRESS_STAGE[:did_refresh_config]
    # 操作主仓库
    config_error = exec_config_repo(repo, cmd, opts)
    return config_error if config_error
    # 更新配置表
    refresh_config(repo, context, auto_update)
  end

  if check_point < PROGRESS_STAGE[:did_pull_sub]
    # 如果本次操作所有子仓库,则再次获取所有子仓库(因为配置表可能已经更新,子仓库列表也有更新,此处获取的仓库包含:已有的子仓库 + 合并后新下载仓库 + 从缓存弹出的仓库)
    exec_subrepos = all_repos(except_config:true) if context.repos.nil?
    # 更新子仓库
    update_subrepos(exec_subrepos, context, auto_update)
  end

  config_error
end
do_abort(argv) click to toggle source
# File lib/m-git/command/rebase.rb, line 294
def do_abort(argv)
  return false unless argv.git_opts.include?('--abort')
  Output.puts_start_cmd
  OperationProgressManager.remove_progress(Workspace.root, __progress_type)
  do_repos = all_repos.select { |repo| repo.status_checker.is_in_rebase_progress? }

  if do_repos.length > 0
    append_message = ",另有#{all_repos.length - do_repos.length}个仓库无须操作" if do_repos.length < all_repos.length
    Output.puts_processing_block(do_repos.map { |e| e.name }, "开始操作以上仓库#{append_message}...")
    _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, do_repos)
    Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
  else
    Output.puts_success_message("没有仓库需要操作!")
  end

  true
end
enable_continue_operation() click to toggle source
# File lib/m-git/command/rebase.rb, line 328
def enable_continue_operation
  true
end
enable_repo_selection() click to toggle source
# File lib/m-git/command/rebase.rb, line 324
def enable_repo_selection
  true
end
exec_config_repo(repo, cmd, opts) click to toggle source
# File lib/m-git/command/rebase.rb, line 215
def exec_config_repo(repo, cmd, opts)
  error = nil
  Output.puts_processing_message("开始操作主仓库...")
  success, output = repo.execute_git_cmd(cmd, opts)
  if success
    Output.puts_success_message("操作成功!\n")
  else
    Output.puts_fail_message("操作失败!\n")
    error = output
  end
  error
end
execute(argv) click to toggle source
# File lib/m-git/command/rebase.rb, line 29
def execute(argv)
  return if do_abort(argv)

  check_master_rebase(argv)
  Workspace.check_branch_consistency

  Output.puts_start_cmd

  config_repo = generate_config_repo

  if mgit_try_to_continue?
    # 不处于中间态禁止执行
    Foundation.help!("当前并不处于操作中间态,无法进行continue操作!") if !OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)

    # 读取指令缓存失败禁止执行
    context, _ = OperationProgressManager.load_context(Workspace.root, __progress_type)
    Foundation.help!("缓存指令读取失败,continue无法继续进行,请重新执行完整指令。") if context.nil? || !context.validate?

    # 分支不匹配禁止执行
    Foundation.help!("当前主仓库所在分支跟上次操作时所在分支(#{context.branch})不一致,请切换后重试。") if config_repo.status_checker.current_branch(use_cache:true) != context.branch

    if !context.repos.nil?
      Output.puts_processing_message("加载上次即将操作的子仓库...")
      Workspace.update_all_repos(context.repos)
    end

    cmd = context.cmd
    opts = context.opts
    config_error = continue_execute(cmd, opts, config_repo, context.other[PROGRESS_STAGE_KEY], context.other[PROGRESS_AUTO])
    if config_error
      Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
      return
    end
  else
    # 处于中间态则提示
    if OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)
      if Output.continue_with_user_remind?("当前处于操作中间态,建议取消操作并执行\"mgit merge --continue\"继续操作未完成仓库。\n    继续执行将清除中间态并重新操作所有仓库,是否取消?")
        Output.puts_cancel_message
        return
      end
    end

    cmd = argv.cmd
    opts = argv.git_opts

    # 优先操作配置仓库
    config_error = rebase_config_repo(cmd, opts, config_repo, argv.opt_list.did_set_opt?(OPT_LIST[:pull]))
    if config_error
      Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
      return
    end
  end

  do_repos = []
  dirty_repos = []
  detached_repos = []
  no_tracking_repos = []

  Output.puts_processing_message("检查各仓库状态...")
  Workspace.serial_enumerate_with_progress(all_repos) { |repo|
    next if !config_repo.nil? && repo.name == config_repo.name

    status = repo.status_checker.status
    branch_status = repo.status_checker.branch_status

    if status == Repo::Status::GIT_REPO_STATUS[:clean] &&
      branch_status != Repo::Status::GIT_BRANCH_STATUS[:detached] &&
      branch_status != Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
      do_repos.push(repo)
    else
      if status == Repo::Status::GIT_REPO_STATUS[:dirty]
        dirty_repos.push(repo)
      end
      if branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
        detached_repos.push(repo)
      elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
        no_tracking_repos.push(repo)
      end
    end
  }
  Output.puts_success_message("检查完成!\n")

  if dirty_repos.length > 0 || no_tracking_repos.length > 0 || detached_repos.length > 0
    remind_repos = []
    remind_repos.push(['有本地改动', dirty_repos.map { |e| e.name }])
    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
    Output.interact_with_multi_selection_combined_repos(remind_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 强制执行', 'c: 终止']) { |input|
      if input == 'b'
        do_repos += dirty_repos
        do_repos += detached_repos
        do_repos += no_tracking_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("没有仓库需要执行rebase指令!") if config_repo.nil?
  else
    Output.puts_processing_message("开始rebase子仓库...")
    _, error_repos = Workspace.execute_git_cmd_with_repos(cmd, opts, do_repos)
  end

  Output.puts_succeed_cmd("#{cmd} #{opts}") if config_error.nil? || error_repos.empty?

  # 清除中间态
  OperationProgressManager.remove_progress(Workspace.root, __progress_type)
end
options() click to toggle source
Calls superclass method MGit::BaseCommand#options
# File lib/m-git/command/rebase.rb, line 23
def options
  [
      ARGV::Opt.new(OPT_LIST[:pull], info:'可选参数,指定后在合并仓库前会拉取远程分支更新代码,如:"mgit rabase --pull"。', type: :boolean)
  ].concat(super)
end
rebase_config_repo(cmd, opts, repo, auto_update) click to toggle source

合并主仓库

@param cmd [String] 合并指令

@param opts [String] 合并参数

@param repo [Repo] 配置仓库对象

@param exec_repos [Array<Repo>] 本次操作的所有仓库(含配置仓库)

# File lib/m-git/command/rebase.rb, line 152
def rebase_config_repo(cmd, opts, repo, auto_update)
  return if repo.nil?
  branch_status = repo.status_checker.branch_status
  if branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
    remind_config_repo_fail("主仓库\"#{repo.name}\"HEAD游离,当前不在任何分支上,无法执行!")
  elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking]
    remind_config_repo_fail("主仓库\"#{repo.name}\"未跟踪对应远程分支,无法执行!(需要执行'mgit branch -u origin/<branch>')")
  elsif repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
    remind_config_repo_fail("主仓库\"#{repo.name}\"有改动,无法执行\"#{cmd}\"!")
  else
    return continue_execute(cmd, opts, repo, PROGRESS_STAGE[:new_start], auto_update)
  end
end
refresh_config(repo, context, auto) click to toggle source

刷新配置表

# File lib/m-git/command/rebase.rb, line 229
def refresh_config(repo, context, auto)
  begin
    Workspace.update_config(strict_mode:false) { |missing_repos|
      if missing_repos.length > 0
        # 这里分支引导仅根据主仓库来进行,如果使用all_repos来作为引导
        # 基准,可能不准确(因为all_repos可能包含merge分支已有的本地
        # 仓库,而这些仓库所在分支可能五花八门,数量也可能多于处于正确
        # 分支的仓库)。
        success_missing_repos = Workspace.guide_to_checkout_branch(missing_repos, [repo], append_message:"拒绝该操作本次执行将忽略以上仓库")
        all_repos.concat(success_missing_repos)
        # success_missing_repos包含新下载的和当前分支已有的新仓库,其中已有仓库包含在@all_repos内,需要去重
        all_repos.uniq! { |repo| repo.name }
      end
    }
    refresh_context(context)
  rescue Error => e
    if e.type == MGIT_ERROR_TYPE[:config_generate_error]
      context.other = {
        PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_refresh_config],
        PROGRESS_AUTO => auto
      }

      OperationProgressManager.trap_into_progress(Workspace.root, context)
      show_progress_error("配置表生成失败", "#{e.msg}")
    end
  end
end
refresh_context(context) click to toggle source
# File lib/m-git/command/rebase.rb, line 274
def refresh_context(context)
  exec_subrepos = all_repos(except_config:true)
  is_all = Workspace.is_all_exec_sub_repos?(exec_subrepos)
  context.repos = is_all ? nil : exec_subrepos.map { |e| e.name } # nil表示操作所有子仓库
end
remind_config_repo_fail(msg) click to toggle source
# File lib/m-git/command/rebase.rb, line 280
def remind_config_repo_fail(msg)
  Output.puts_fail_message(msg)
  return if Output.continue_with_user_remind?("是否继续操作其余仓库?")
  Output.puts_cancel_message
  exit
end
show_progress_error(summary, detail) click to toggle source
# File lib/m-git/command/rebase.rb, line 312
    def show_progress_error(summary, detail)
      error = "#{summary} 已进入操作中间态。

原因:
  #{detail}

可选:
  - 使用\"mgit rebase --continue\"继续变基。
  - 使用\"mgit rebase --abort\"取消变基。"
      Foundation.help!(error, title:'暂停')
    end
update_config_repo(repo, context, auto) click to toggle source
# File lib/m-git/command/rebase.rb, line 198
def update_config_repo(repo, context, auto)
  if auto || Output.continue_with_user_remind?("即将合并主仓库,是否先拉取远程代码更新?")
    Output.puts_processing_message("正在更新主仓库...")
    success, output = repo.execute_git_cmd('pull', '')
    if !success
      context.other = {
        PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_pull_config],
        PROGRESS_AUTO => auto
      }
      OperationProgressManager.trap_into_progress(Workspace.root, context)
      show_progress_error("主仓库更新失败", "#{output}")
    else
      Output.puts_success_message("更新成功!\n")
    end
  end
end
update_subrepos(subrepos, context, auto) click to toggle source
# File lib/m-git/command/rebase.rb, line 257
def update_subrepos(subrepos, context, auto)
  if auto || Output.continue_with_user_remind?("即将合并子仓库,是否先拉取远程代码更新?")
    Output.puts_processing_message("正在更新子仓库...")
    _, error_repos = Workspace.execute_git_cmd_with_repos('pull', '', subrepos)
    if error_repos.length > 0
      context.other = {
        PROGRESS_STAGE_KEY => PROGRESS_STAGE[:did_pull_sub],
        PROGRESS_AUTO => auto
      }
      OperationProgressManager.trap_into_progress(Workspace.root, context)
      show_progress_error("子仓库更新失败", "见上述输出")
    else
      Output.puts_success_message("更新成功!\n")
    end
  end
end

Private Instance Methods

__progress_type() click to toggle source
# File lib/m-git/command/rebase.rb, line 342
def __progress_type
  OperationProgressManager::PROGRESS_TYPE[:rebase]
end