Full Code of baidu/m-git for AI

master 582913c5566c cached
109 files
334.7 KB
103.2k tokens
674 symbols
1 requests
Download .txt
Showing preview only (419K chars total). Download the full file or copy to clipboard to get everything.
Repository: baidu/m-git
Branch: master
Commit: 582913c5566c
Files: 109
Total size: 334.7 KB

Directory structure:
gitextract_p54qz427/

├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── docs/
│   ├── commands/
│   │   └── commands.md
│   ├── config/
│   │   ├── config-env.md
│   │   └── manifest.md
│   ├── design/
│   │   ├── design.md
│   │   ├── principle.md
│   │   └── workspace.md
│   ├── extension/
│   │   ├── cmd_ext.md
│   │   ├── hooks.md
│   │   ├── plugins.md
│   │   └── submodule.md
│   ├── faq/
│   │   └── faq.md
│   ├── references.md
│   └── use/
│       ├── common-commands.md
│       └── how-to-start.md
├── lib/
│   ├── m-git/
│   │   ├── argv/
│   │   │   ├── opt.rb
│   │   │   ├── opt_list.rb
│   │   │   └── parser.rb
│   │   ├── argv.rb
│   │   ├── base_command.rb
│   │   ├── command/
│   │   │   ├── add.rb
│   │   │   ├── branch.rb
│   │   │   ├── checkout.rb
│   │   │   ├── clean.rb
│   │   │   ├── commit.rb
│   │   │   ├── config.rb
│   │   │   ├── delete.rb
│   │   │   ├── fetch.rb
│   │   │   ├── forall.rb
│   │   │   ├── info.rb
│   │   │   ├── init.rb
│   │   │   ├── log.rb
│   │   │   ├── merge.rb
│   │   │   ├── pull.rb
│   │   │   ├── push.rb
│   │   │   ├── rebase.rb
│   │   │   ├── reset.rb
│   │   │   ├── self.rb
│   │   │   ├── stash.rb
│   │   │   ├── status.rb
│   │   │   ├── sync.rb
│   │   │   └── tag.rb
│   │   ├── command_manager.rb
│   │   ├── error.rb
│   │   ├── foundation/
│   │   │   ├── constants.rb
│   │   │   ├── dir.rb
│   │   │   ├── duration_recorder.rb
│   │   │   ├── git_message_parser.rb
│   │   │   ├── lock.rb
│   │   │   ├── loger.rb
│   │   │   ├── mgit_config.rb
│   │   │   ├── operation_progress_manager.rb
│   │   │   ├── timer.rb
│   │   │   └── utils.rb
│   │   ├── foundation.rb
│   │   ├── hooks_manager.rb
│   │   ├── manifest/
│   │   │   ├── cache_manager.rb
│   │   │   ├── internal.rb
│   │   │   ├── light_repo.rb
│   │   │   ├── light_repo_generator.rb
│   │   │   ├── linter.rb
│   │   │   └── manifest.sample
│   │   ├── manifest.rb
│   │   ├── open_api/
│   │   │   └── script_download_info.rb
│   │   ├── open_api.rb
│   │   ├── output/
│   │   │   └── output.rb
│   │   ├── plugin/
│   │   │   ├── completion.bash
│   │   │   ├── completion.zsh
│   │   │   └── init.sh
│   │   ├── plugin_manager.rb
│   │   ├── repo/
│   │   │   ├── status.rb
│   │   │   └── sync_helper.rb
│   │   ├── repo.rb
│   │   ├── template/
│   │   │   ├── local_manifest.rb
│   │   │   ├── manifest_hook.rb
│   │   │   ├── post_download_hook.rb
│   │   │   ├── post_hook.rb
│   │   │   ├── pre_exec_hook.rb
│   │   │   ├── pre_hook.rb
│   │   │   └── pre_push_hook.rb
│   │   ├── template.rb
│   │   ├── version.rb
│   │   ├── workspace/
│   │   │   ├── path_helper.rb
│   │   │   └── workspace_helper.rb
│   │   └── workspace.rb
│   └── m-git.rb
├── m-git.gemspec
├── manifest.json
├── mgit
└── test/
    ├── argv/
    │   ├── test_opt.rb
    │   ├── test_opt_list.rb
    │   └── test_parser.rb
    ├── foundation/
    │   ├── test_dir.rb
    │   ├── test_mgit_config.rb
    │   └── test_utils.rb
    ├── manifest/
    │   └── test_light_repo.rb
    ├── test_argv.rb
    ├── test_foundation.rb
    ├── test_helper.rb
    ├── test_manifest.rb
    ├── test_open_api.rb
    ├── test_plugin_manager.rb
    └── test_workspace.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

.bundle
vendor/
*.gem

## IDE
*.xccheckout
.idea

## File
*.svn/
*.DS_Store
.DS_Store?


## other
*.pyc

# log指令缓存
log_cache.json

================================================
FILE: CHANGELOG.md
================================================
## Release Note:

### 2.5.5

-【bug修复】修复 box config -m 命令 不可用问题,修改参数类型为String

### 2.5.4

- mgit增加inject注入文件功能
- 增加manifest.json文件,当前仓库可用于mgit-demo




================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at baidu. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: Gemfile
================================================
source "https://gems.ruby-china.com"

# git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

# Specify your gem's dependencies in mgit.gemspec
gemspec


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2020 Baidu, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: README.md
================================================
# MGit

MGit 是一款 Ruby 封装的基于 Git 的多仓库管理工具,可以高效的、同时的对多个 Git 仓库执行 Git 命令。
适合于在多个仓库中进行关联开发的项目,提高 Git 操作的效率,避免逐个执行 Git 命令带来的误操作风险。

- **易用的命令**
封装 Git 命令,命令和参数均由 Git 衍生而来,会使用 Git 就可以成本低上手 MGit。

- **直观高效的执行命令**
提供图表化的结果展示,开发者可以快速查看命令在多个仓库的执行结果;
多线程并发执行多仓库命令,通过仓库缓存机制提高仓库的拉取效率;

- **安全的执行命令**
在执行命令前对多仓库状态进行安全检查:分支是否异常,工作区是否未提交代码等;
对 .git 进行托管与 Git 工作区分类,避免误删丢失改动或提交;
执行存在风险的操作时,会给与风险操作提示,避免误操作;

- **方便扩展**
支持加载 ruby-gem 包作为插件,gem 包名格式 `m-git-${suffix}`和`mgit-${suffix}`
快速的扩展 MGit 的命令,增加自定义命令,扩展已有命令的功能;
提供类似`git hook`的 hook 点,方便开发者实现自定义逻辑;

## 快速开始
  #### 1、安装 MGit 工具

环境要求:

- 系统:支持 macOS、Ubuntu,暂时不支持 Windows
- Ruby版本: >= 2.3.7

```ruby
$ gem install m-git
```

#### 2、初始化多仓库 

初始化多仓库使用 `mgit init` 命令;

类似于 Git 从远程 clone 新仓库, 会将多个仓库 clone 到本地;

下面通过一个 demo 体验一下 MGit 命令:

```ruby
# 2.1 建议在一个新文件夹中拉取demo
$ mgit init -g https://github.com/baidu/m-git.git

# 2.2 体验一下mgit命令
$ mgit -l                 显示所有mgit管理的仓库
$ mgit branch --compact   查看多仓库的分支
$ mgit status             产看仓库分支超前/落后情况
```


#### 3、已有多仓库如何迁移到 MGit 管理

- 根据文档[配置 manifest.json](docs/config/manifest.md)

  将要管理的仓库都配置到 manifest.json 中
  
- 将 manifest.json 放到一个仓库中管理

  这个仓库同样会在 manifest.json 中描述,并且需要配置 "config-repo": true 
  
  这个仓库称为配置仓库,也叫做**主仓库**,其他仓库叫做**子仓库**
     
- 使用 `mgit init -f manifest文件路径` 初始化多仓库,命令测试 manifest.json 配置是否正常

  注意这个命令不会重复拉取主仓库,只会拉取所有的子仓库到当前目录,并在当前目录创建一个.mgit
  
  你可以在当前目录中看到每个仓库的源码,他们的路径可以通过  manifest.json 的 dest字段配置
  
  你也可以在 .mgit/source-git/ 下看到所有仓库的.git, 这是 MGit 对所有仓库的托管
  
- 本地测试成功后,你可以提交主仓库中的 manifest.json,推送主仓库的变更到远端
  
- 通过 `mgit init -g 主仓库地址` 命令初始化多仓库
  

#### 4、进一步了解 MGit

[常用命令](docs/use/common-commands.md)

[manifest文件介绍](docs/config/manifest.md)

[配置多仓库](docs/config/config-env.md) 

[使用 MGit 管理多仓库的案例](docs/use/how-to-start.md#4-mgit-) 

[了解更多](docs/references.md)


## 测试

单测在MGit仓库内的test文件夹下
新建单测文件,必须以‘test_’开头
执行单测:rake (如果报错尝试执行 bundle install)


## 如何贡献

欢迎开发者向 MGit 贡献代码。如果您开发了新功能或发现了 bug,欢迎给我们提交PR。

代码贡献要求:
1. 功能和实现应该具有通用性, 不是为了解决某个具体业务而定制的代码逻辑
2. 代码质量高,符合 Ruby 编码规范
3. 需要补充对应的单测 case

issues贡献: 如在使用中遇到问题,请在 https://github.com/baidu/m-git/issues 新建 issues 反馈问题。





================================================
FILE: Rakefile
================================================

require "bundler/gem_tasks"
task :default => :test


require 'rake/testtask'

Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.pattern = 'test/**/test_*.rb'
  t.verbose = true
end

================================================
FILE: docs/commands/commands.md
================================================

## Command-line Reference
参考:https://guides.cocoapods.org/terminal/commands.html#pod_spec_lint


# Brief Description Of Commands

## Initalizing
+ mgit
+ config     用于更新多仓库配置信息。

## Creating && Workspace
+ init       初始化多仓库目录。
+ sync       根据配置表(从远端或本地)同步仓库到工作区,包括被锁定仓库,已经在工作的不作处理(默认不执行pull)。
+ info       输出指定仓库的信息。
+ clean      对指定或所有仓库执行"git add . && git reset --hard"操作,强制清空暂存区和工作区。
+ delete     删除指定单个或多个仓库(包含被管理的.git文件和工程文件以及跟该.git关联的所有缓存)。

## Snapshotting
+ add        将文件改动加入暂存区。
+ status     输出所有仓库的状态。包括:"分支","暂存区","工作区","特殊(未跟踪和被忽略)","冲突"。
+ commit     将修改记录到版本库。
+ reset      将当前HEAD指针还原到指定状态。

## Branching
+ branch     列出,创建和删除分支。
+ checkout   切换分支或恢复工作区改动。
+ merge      合并两个或多个开发历史。
+ stash      使用git stash将当前工作区改动暂时存放起来。
+ tag        增删查或验证标签。增加标签示例:mgit tag -a 'v0.0.1' -m 'Tag description message'
+ log        输出指定仓库的提交历史。

## Trunk
+ fetch      从远程仓库下载数据对象和引用。
+ pull       从仓库或本地分支获取数据并合并。
+ rebase     重新将提交应用到其他基点,该命令不执行lock的仓库。
+ push       更新远程分支和对应的数据对象。

## Addition
+ forall     对多仓库批量执行指令。


================================================
FILE: docs/config/config-env.md
================================================
### config.yml 介绍

 config.yml 是 MGit 的配置文件;

 MGit的配置内容保存在多仓库目录下的`.mgit/config.yml`文件中,配置仅针对当前的多仓库生效;



- 通过 `$ mgit config --list`  可以查看当前config.yml中的配置

  ```ruby
  [是否将.git实体托管给MGit,托管后仓库内的.git目录是软链 (true/false)]
  managegit: true
  
  [MGit操作的最大并发数? (Integer)]
  maxconcurrentcount: (默认当前机器的逻辑 CPU 数)
  
  [是否按照配置表同步工作区? (true/false)]
  syncworkspace: true
  
  [同步工作区时回收的仓库是否保留到缓存中? (true/false)]
  savecache: false
  
  [是否开启日志打印,操作日志会收集到.mgit/log目录下? (true/false)]
  logenable: true
  
  [日志的打印级别, DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, FATAL = 4]
  loglevel: 0
  ```

- 通过 `$ mgit config --set` 可以对config.yml进行配置,如:"mgit config -s maxconcurrentcount 20"

- 当前支持的几个配置项:

  | key                | 类型    | 描述                                                         | 默认值 |
  | ------------------ | :------ | ------------------------------------------------------------ | ------ |
  | managegit          | Boolean | 如果该配置为`true`, 那么在通过MGit下载新仓库时(`mgit sync -n`),会将工作区仓库内的`.git`托管给MGit(将`.git`移动到`.mgit/source-git/`下)。若配置为`false`,则任何情况下都不操作`.git`, 如有`.git`已经被托管,则弹出到工作区。 | true  |
  | maxconcurrentcount | Integer | MGit操作的最大并发数.                                        | 当前机器的逻辑 CPU 数      |
  | syncworkspace      | Boolean | 当配置表发生改变的时候,工作区内的仓库可能和配置表不匹配,若配置为`true`,会将上次操作和本地操作依据的配置表做对比,将多余的仓库(如有的话)缓存到`/.mgit/source-git/.../cache`目录下。若配置为`false`,则不操作。 | true   |
  | savecache          | Boolean | 同步工作区时回收的仓库被放到缓存中后,若配置为`true`,则一直保留该工作区目录,若配置为`false`,则直接删除(`.git`被托管的仓库可以删除工作区,需要时导出即可)。该配置在`syncworkspace`为`false`时不生效。 | false  |
  | logenable          | Boolean | 是否开启日志打印,操作日志会收集到.mgit/log目录下            | true   |
  | loglevel           | Integer | 日志的打印级别, DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, FATAL = 4 | 1      |

  
  
 - 关于 managegit 与 syncworkspace 配置的详细说明
 
   建议配置:
      - managegit :true
      - syncworkspace : true
     
   在使用MGit管理多仓库时,当前分支dev-1分支 有 a、b、c 三个仓库;
   
   1、现在需要checkout到一个旧分支feature-1,feature-1只配置了 a、b仓库,此时c仓库会被MGit回收,不会展示在当前工作区(syncworkspace : true 按照配置表同步工作区)
   
   2、但c仓库还有未推到远端的开发分支和stash的代码,MGit会托管暂时不用的c仓库,保证本地c仓库中的的代码不会丢失( managegit: true 托管工作区)
   
   3、当再checkout到dev-1分支时,会将被托管的c仓库同步回工作区(syncworkspace : true 按照配置表同步工作区)

================================================
FILE: docs/config/manifest.md
================================================
### 1. manifest 配置介绍

manifest.json 配置表定义了多仓库管理所需的仓库信息,采用json格式,示例如下:

```ruby
{
  "remote": "https://github.com/baidu",//远程仓库地址
  "version":1, //配置文件版本
  "mgit-excluded": false,
  "dest": "Sources", //本地仓库相对mgit根目录存放路径
  "repositories": { //源码列表,包含git仓库和非git仓库
	"MainApp": {
      "remote-path": "mainapp" //远程仓库名,对于需要mgit管理的仓库是必需字段。此时git地址为:https://github.com/baidu/mainapp
      "config-repo": true //指定该仓库为配置仓库,即包含本配置表的仓库,仅允许指定一个仓库
    },
    "Watch": {
      "remote-path": "watch"
      "dest":"temp/test" //可选,覆盖最外层定义,在mgit根目录下仓库的父目录。该仓库本地路径为"<mgit根目录>/temp/test/Watch"
    },
    "BBAAccount": {
      "remote-path": "bbaaccount",
      "abs-dest":"/Users/someone/baidu/temp/my_account" //仓库本地完整路径(指定后dest无效)
    },
    "Script": {
      "remote-path": "script",
      "remote": "https://github.com/some_script",//可选,覆盖最外层定义
      "lock": {
	    "branch":"my_branch"//当前分支
	    或"tag":"tag1"//tag
	    或"commit_id":"123456"//HEAD指向的commit id
	  }//锁定某仓库状态,每次执行指令时会保持该仓库为指定的状态
    },
    "Some_Repo": {
      "remote-path": "some_repo",
      "mgit-excluded": true//指定不进行多仓库管理,可选
    },
    //本地git仓库或非git仓库(mgit不操作)
    "New_Repo": {
      "dest": "Some/Dir",
      "mgit-excluded": true
    },
    "Test_Repo": {
      "dest": "Some/Dir2",
      "dummy": true //(2.3.0已废弃)指定该仓库为占位仓库(mgit不操作,EasyBox组装器也不使用)
    }
  }
}
```



### 2. manifest 配置中的具体字段介绍

#### 2.1 一级字段

| 字段名          | 说明                                                         | 必要性 | 值类型               |
| :-------------- | :----------------------------------------------------------- | :----- | :------------------- |
| `remote`        | 远程仓库git地址的根目录。注意,完整地址为`<remote>/<remote-path>`。 | 必需   | `String`             |
| `version`       | 配置文件版本。                                               | 必需   | `Number`             |
| `dest`          | 在mgit根目录下仓库的父目录,此时完整本地目录为:`<mgit根目录>/<dest>/<repo_name>`。 | 必需   | `String`             |
| `mgit-excluded` | 指定为`true`则不被mgit纳入多仓库管理,即mgit不会操作该仓库。 | 可选   | `Bool`               |
| `repositories`  | 源码列表。                                                   | 必需   | `JSON<String, JSON>` |



####  2.2 `repositories`内的Json数据

| 字段名        | 说明                                                         | 值类型 |
| :------------ | :----------------------------------------------------------- | :----- |
| `<repo_name>` | 仓库唯一标识,值为该仓库的配置。在`dest`生效时,为本地仓库目录名,此时完整本地目录为:`<mgit根目录>/<dest>/<repo_name>`。 | `JSON` |



#### 2.3. 仓库配置字段

| 字段名                | 说明                                                         | 必要性                                                       | 值类型   |
| :-------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- | :------- |
| `remote-path`         | 远程仓库git地址相对路径。注意,完整的远程地址为`<remote>/<remote-path>`。 | 有条件可选:在不显式指定`mgit-excluded`为`true`的情况下(即希望该仓库纳入mgit管理的话),是必须字段,否则可选。 | `String` |
| `remote`              | 远程仓库地址根目录,如果指定则覆盖一级字段的`remote`。注意,完整的远程地址为`<remote>/<remote-path>`。 | 可选                                                         | `String` |
| `lock`                | 指定后会锁定仓库状态,每次执行指令时会保持该仓库为锁定状态。状态可以指定`branch`, `tag`, `commit_id`中的一个,如`lock: { "branch": "master" }`。 | 可选                                                         | `Json`   |
| `dest`                | 本地仓库相对mgit根目录存放的父目录,如果指定则覆盖一级字段的`dest`。此时完整本地目录为:`<mgit根目录>/<dest>/<repo_name>`。 | 可选                                                         | `String` |
| `abs-dest`            | 本地仓库的完整存放路径,如果指定则`dest`失效,此时仓库完整的本地路径为`<abs-dest>`。 | 可选                                                         | `String` |
| `config-repo`         | 如果指定为`true`则表明该仓库为包含该配置文件的配置仓库,最多只能指定一个。注意,指定了该仓库后,某些mgit操作会优先处理该仓库,如`checkout`,`merge`,`pull`。 | 可选                                                         | `Bool`   |
| `mgit-excluded`       | 指定为`true`则mgit不操作,此处指定会覆盖一级字段的`mgit-excluded`。 | 可选                                                         | `Bool`   |
| `[2.3.0已废弃] dummy` | 指定为`true`则表明该仓库是占位仓库,不受mgit操作,同时EasyBox组装器也不使用它的**源码**,单纯用于记录仓库信息,如一些完全二进制的三方库。<br><br>**提示**:指定`dummy:true`后,则默认指定`mgit-excluded:true`(如果显示指定了`mgit-excluded:false`,依然将其置为`true`)。该字段用于配合EasyBox进行工程组装,日常使用无需添加。 | 可选                                                         | `Bool`   |



- **mgit根目录**是指mgit初始化的目录,在该目录下存在`.mgit`隐藏文件。
- 配置表文件名必须是`manifest.json`
- 配置表中同名字段覆盖优先级:仓库配置 > 全局配置。
- 对于值为`Bool`类型的字段,缺省意味着该字段值为`false`。
- 采用包含配置表的中央仓库的方式管理多仓库时,需要将配置仓库的配置也描述在配置表中。



### 3. 本地配置表 local_manifest

当你想在本地调试时,可以通过创建`local_manifest.json`临时修改多仓库配置;可以在不修改`manifest.json`的情况下对配置进行调整;

可以选择以下方式创建 local_manifest.json:


- 通过 `migt init` 命令自动创建 

    - 执行命令时不带参数`-l`参数
    
      将自动在配置仓库创建不生效的`local_manifest.json`,内容为:
    
      ```ruby
      {
        "repositories": {
         // 可自行根据需要添加/修改仓库配置
        }
      }
      ```

    - 执行命令时添加参数`-l`
    
      则在配置仓库创建`local_manifest.json`,此时该文件只包含配置仓库信息:
    
      ```ruby
      // 此配置的含义:让mgit只管理配置仓库,其余仓库均不管理,主要用于简化壳工程初始化。
      {
        "mgit-excluded": true,
        "repositories": {
          "config_repo_name": {
            "mgit-excluded": false
           }
        }
      }
      ```
  


-  通过 `mgit config` 命令创建

    指定一个目录,在该目录下创建`local_manifest.json`,若目录不存在则自动创建。如:

    ```ruby
    $ mgit config -c /a/b/c
    
    ```

    **注意**:

    * `/a/b/c`为包含配置文件的文件夹目录,此时生成配置文件`/a/b/c/local_manifest.json`
    * 如果未传入值,如:`mgit config -c`,那么若配置仓库存在的话,会在配置仓库中创建空的本地配置文件。


-  手动创建 local_manifest.json

    * 手动新建配置文件`local_manifest.json`,放到任意位置,执行以下指令将其托管给mgit:

      ```
      //注意,此后如果local_manifest.json位置发生了移动,需要重新执行该指令使其生效。
      $ mgit config -u <path_to>/local_manifest.json
      ```

    * 在不执行指令的情况下,可手动创建`local_manifest.json`并将其放置于下列目录即可自动生效:

      将创建的local_manifest.json 放到`manifest.json`所在目录 或 `.mgit/source-config`文件夹内 即可生效

  

### 4、 对local_manifest.json 校验与合并

- `local_manifest.json`文件的字段与`manifest.json`完全一致,唯一区别是不会对它做字段合法性校验,对于不需要覆盖的字段可以缺省。
- 执行mgit指令时,会将`manifest.json`和`local_manifest.json`合并,配置文件中的字段会被本地配置文件中定义的对应字段覆盖(`repositories`字段除外,而是仓库配置内的对应字段被覆盖),如:



```ruby
// manifest.json:
{
  "remote":"https://github.com/baidu",
  "version":1,
  "dest":"Sources",
  "repositories": {
    "TestRepo1": {
      "remote-path":"test1.git",
       "mgit-excluded": false
    }
  }
}


// local_manifest.json:
{
    "remote":"https://github.com/baidu", //覆盖原定义
   "repositories": {
      //将manifest.json内定义的TestRepo1排除管理
       "TestRepo1": {
           "mgit-excluded": true
       },
       //添加一个新仓库,配置完成后执行mgit sync -n可下载该仓库
       "TestRepo2": {
          "remote-path":"test.git"
       }
   }
}

// 将合并为:
{
  "remote":"https://github.com/baidu", //被覆盖
  "version":1,
  "dest":"Sources",
  "repositories": {
    "TestRepo1": {
      "remote-path":"test1.git",
       "mgit-excluded": true //被覆盖
    },
    //被添加
    "TestRepo2": {
      "remote-path":"test.git"
    }
  }
}
```



================================================
FILE: docs/design/design.md
================================================
## 设计篇(略)
### 基本架构

================================================
FILE: docs/design/principle.md
================================================
## 基本原则

================================================
FILE: docs/design/workspace.md
================================================
### 工作区


================================================
FILE: docs/extension/cmd_ext.md
================================================
### 一、扩展指令


MGit 加载插件有**2种方式**:

- **ruby gem 包加载**
当运行 MGit 命令时,从所有的`ruby-gems`包中查找并加载 MGit 的插件,查找判定条件 gem 包中存在如下文件:
```
- m-git_plugin.rb
- mgit_plugin.rb
```
> Tips:插件功能开发完成后,打包为 gem 包后发布到 gem 源,MGit 执行命令时将自动被加载

- **inject 参数加载**
```
# 执行命令时加载插件
# path_to_plugin:可以是待加载的插件文件或文件夹,
# 如果是文件夹,则从中查找插件文件(m-git_plugin.rb、mgit_plugin.rb)并加载
$ mgit add --inject=${path_to_plugin}
```

### 1分钟极简扩展(以Demo多仓库示例,待修改demo url)
**需求:**假设我们要提供一个命令用于查看 MGit 管理的所有 Git 仓库,创建一个新的指令:hi
```
$ mgit hi //输出当前mgit管理的仓库列表
```
**Step0:**建议在一个新文件夹中拉取demo
```
mgit init -g https://github.com/baidu/m-git.git
```
**Step1:**新建一个文件:hi.rb (无所谓名称)
**Step2:**增加一个继承自 BaseCommand 类的子类`Hi`,指令默认使用子类的名称,可使用`self.cmd`方法重写
```
module MGit
  class Hi < BaseCommand
    def execute(argv)
	  # Do something for each repo.
      puts all_repos.map(&:name).join(',')
    end
  end
end
```

**测试:**
```
# inject 指向扩展命令的文件路径(或全路径)
mgit hi --inject=hi.rb
> m-git,DDParser
```

================================================
FILE: docs/extension/hooks.md
================================================
### hook

#### 一、什么是hook

hook是在MGit 仓库中特定事件触发后被调用的脚本。hook最常见的使用场景包括根据仓库状态改变项目环境、接入持续集成工作流等。 由于脚本是可以完全定制,所以你可以用hook来自动化或者优化你开发工作流中任意部分。

#### 二、MGit的hook

hook脚本位于多仓库目录下的`/.mgit/hooks`内,下表列出个各hook的作用

| hook脚本              | 作用                                                         |
| --------------------- | ------------------------------------------------------------ |
| pre_hook.rb           | 指令执行前hook(执行时机较早,内部只能获取当前指令和多仓库根目录) |
| post_hook.rb          | 指令执行后hook                                               |
| pre_exec_hook.rb      | 指令执行前hook(内部除当前指令和多仓库根目录外,还可获取执行仓库的 |
| manifest_hook.rb      | mgit读取配置文件前执行的hook                                 |
| post_download_hook.rb | mgit sync下载新仓库后执行的hook                              |
| pre_push_hook.rb      | mgit push前执行的hook(类似pre_exec_hook)                     |

【注意】执行顺序:

1. mgit指令pre_hook
2. mgit多仓库pre_hook
3. manifest_hook
4. pre_exec_hook/pre_push_hook/post_download_hook
5. git前置hook
6. git后置hook
7. mgit多仓库post_hook
8. mgit指令post_hook



如下代码是   pre_hook.rb 的模板:  

```

#coding=utf-8
module MGitTemplate
  class PreHook
    # hook接口,用于接受本次指令执行前的数据
    #
    # @param cmd [String] 本次执行指令
    #
    # @param opts [String] 本次执行指令参数
    #
    # @param mgit_root [String] mgit根目录
    #
    def self.run(cmd, opts, mgit_root)
    
    end
  end
end
```



================================================
FILE: docs/extension/plugins.md
================================================
### 插件
- 插件介绍,可以做什么
- 如何扩展插件
- 如何引用其他插件

================================================
FILE: docs/extension/submodule.md
================================================
### 用做子工具
- 介绍其他工具如何调用mgit作为模块

================================================
FILE: docs/faq/faq.md
================================================
## F.A.Q

================================================
FILE: docs/references.md
================================================
<center style="font-family:verdana">MGit相关文档</center>

* [介绍](../README.md)

* 使用篇
    * [快速开始](use/how-to-start.md)
    * [常用命令](use/common-commands.md)
* 配置篇
    * [Manifest](config/manifest.md)
    * [config.yml](config/config-env.md)
* 扩展篇
    * [扩展指令](extension/cmd_ext.md)
    * [MGit Hooks](extension/hooks.md)
    
      
    
* [F.A.Q](faq/faq.md)

* [Command-line Reference](commands/commands.md)

================================================
FILE: docs/use/common-commands.md
================================================
## 常用命令

> 指令描述符说明: 
>
> < > : 占位参数
>
> [  ]  : 可选组合
>
> (  )  :必选组合 
>
>  |     :  互斥参数 
>
> ···   :可重复指定前一个参数


### mgit    

` mgit [-l [-a]|-s] [-v] [-h]`

| 特有参数          | 说明                                                         |
| ----------------- | ------------------------------------------------------------ |
| `-a, --all`       | 指定操作所有(包含不被mgit管理)的仓库,可配合`-l`合并使用: `mgit -al` |
| `-h, --help`      | 显示帮助。                                                   |
| `-l, --list`      | 显示所有migt管理的仓库。                                     |
| `-s, --size`      | 显示所有仓库的磁盘占用量。                                   |
| `-v, --version`   | 显示mgit版本。                                               |
| `-w, --workspace` | 显示当前mgit工程管理根目录(`.mgit`所在目录)。                |

【注意】使用`-l, --list`参数列出所有被mgit管理的仓库,以表格的形式输出,其中表格头部为包含该仓库的目录(相对mgit根目录),同时标明本地缺失仓库。



### add  

` mgit add [<git-add-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

 添加文件到暂存区

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |

【注意】执行前会校验各个仓库所在分支是否一致,否则会提示是否继续:



### branch   

`mgit branch [<git-branch-option>|--compact] [(--mrepo|--el-mrepo) <repo>...] [--help]`

对多仓库进行分支操作

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--compact`  | 以紧凑的方式显示所有仓库的当前分支。                         |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |

【注意】在指定`--compact`的情况下,其余传入的原生`git-branch-option`指令将不生效。指定`--compact`时,会将所有仓库的当前分支做一个归类,便于检查哪些仓库当前处于不同分支,对于特殊状态(HEAD游离和被锁定的仓库)也会显示出来。
 ![图片](branch_command_01.png)

### checkout   

`mgit checkout [<git-checkout-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

克隆远程仓库

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |

【注意】

1. 若指定了配置仓库,执行该指令时会先checkout配置仓库仓库,如果它本地有修改,则切换失败,需要自行将配置仓库处理干净后重试,也可以选择跳过继续。如果使用checkout切分支,
   此处建议先**取消操作并清理配置仓库**的改动再重试,因为切换的目标分支上配置仓库中的配置文件可能与当前不同,不先完成它的切换,后续操作的仓库可能产生遗漏。
   ![图片](checkout_command_01.png)
2. 在配置仓库切换成功后,会根据配置仓库内的配置表来`checkout`其余仓库,此时如果有异常状态仓库,会给出提示:
   ![图片](checkout_command_02.png)
3. 在配置仓库切换成功后,配置表可能发生改变,此时可能会遇到仓库缺失问题,如:A分支10个仓库,B分支11个仓库,当前从A分支切换到B分支,B分支多的1个仓库本地没有,此时会自动下载缺失仓库,并根据当前多数仓库所处分支进行推荐切换。
   ![图片](checkout_command_03.png)
### clean

`mgit clean [(-m|-e) <repo>...] [-h]`

用于清空所有工作区和暂存区,即对所有仓库或指定仓库执行`git add . && git reset --hard`

| 特有参数         | 说明                                                         |
| ---------------- | ------------------------------------------------------------ |
| `-m, --mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `-e, --el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `-h, --help`     | 显示帮助。                                                   |



### commit

`mgit commit [<git-commit-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |

【注意】

- 如果当前仓库的暂存区没有待提交内容的话,则不会执行`commit`操作。
- 执行前会校验各仓库分支是否统一。
- 本指令不支持交互式操作,因此不支持`--ammend`。



### config 

```
mgit config (-m|-u) 
mgit config -c 
mgit config (-l | -s  )
mgit config -h

```

| 特有参数                | 说明                                                         |
| ----------------------- | ------------------------------------------------------------ |
| `-c, --create-local`    | 在指定目录下创建本地配置文件,若目录不存在则自动创建。如执行:`mgit config -c /a/b/c`,则生成本地配置文件:`/a/b/c/local_manifest.json`。如果未传入值,如:`mgit config -c`,那么会在配置仓库中创建本地配置文件(若配置仓库存在的话)。 |
| `-m, --update-manifest` | 该指令用于更新mgit所使用的配置文件,如:`mgit config -m manifest.json`。 |
| `-u, --update-local`    | 该指令用于更新mgit所使用的本地配置文件,如:`mgit config -u /local_manifest.json`。 |
| `-l, --list`            | 列出当前MGit所有配置,无参数,如:`mgit config -l`。         |
| `-s, --set`             | 对MGit进行配置,遵守格式:`mgit config -s  `,如:`mgit config -s maxconcurrentcount 5`。 |
| `-h, --help`            | 显示帮助。                                                   |


命令使用细节请查看[config配置文档](../config/config-env.md)



### delete 

`mgit delete <repo>... [-h]`

该指令用于删除单一仓库的,包括被mgit管理存在于.mgit文件夹中的git实体和存在.mgit外部的工程文件。

| 特有参数     | 说明       |
| ------------ | ---------- |
| `-h, --help` | 显示帮助。 |

【注意】如果需要删除某个仓库,仅仅手动删除.mgit外部的工程文件并不完整,还需要删除.mgit/source-git内的git实体,因此如果需要删除一个仓库的话,最好使用该指令来完整删除。



### fetch  

`mgit fetch [<git-fetch-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

 从远程仓库获取代码

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |



### forall  

`mgit forall -c '<instruction>' [(-m|-e) <repo>...] [-n] [-h]`

该指令用于在指定(或所有)仓库上执行自定义指令

| 特有参数           | 说明                                                         |
| ------------------ | ------------------------------------------------------------ |
| `-c, --command`    | 指定需要执行的shell命令,如:`mgit -c "git status -s"`(注意要带引号)。 |
| `-n, --concurrent` | 可选参数,若指定,则shell命令以多线程方式执行(若缺省则以单线程串行方式执行)。 |
| `-m, --mrepo`      | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `-e, --el-mrepo`   | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `-h, --help`       | 显示帮助。                                                   |



### info   

`mgit info <repo>... [-h]`

产看一个仓库的信息

| 特有参数     | 说明       |
| ------------ | ---------- |
| `-h, --help` | 显示帮助。 |



### log  

`mgit log <repo> [-n] [-h]`

 输出单一仓库的log

| 特有参数       | 说明                                         |
| -------------- | -------------------------------------------- |
| `-h, --help`   | 显示帮助。                                   |
| `-n, --number` | 指定需要显示的提交log个数,不指定则默认500。 |



### merge

```
mgit merge [] [--pull] [(--mrepo|--el-mrepo) ...] [--help]
mgit merge --continue
mgit merge --abort
```

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--pull`     | 可选参数,指定后在合并仓库前会自动拉取远程分支更新代码,否则会有交互式询问。如:`mgit merge --pull` |
| `--continue` | MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数只能单独使用,解决问题后可执行`mgit merge --continue`继续操作其余仓库。 |
| `--abort`    | Git原生参数,用于取消`git merge`中间态,但MGit做了增强,该参数可同时消除`mgit merge`中间态,且只能单独使用:`mgit merge --abort` |
| `--help`     | 显示帮助。                                                   |

【注意】

1. 若指定了配置仓库,执行该指令时会先merge配置仓库,然后根据它内部的配置表merge其余仓库。配置仓库merge完成后,配置表可能发生改变,此时可能会检测到部分仓库本地缺失,此时会自动下载并引导分支切换(提示如checkout指令所述)。
2. 执行前会校验各仓库分支是否统一。
3. 中间态:由于该操作分为两段式,第一阶段操作配置仓库,第二阶段操作子仓库,且在操作前均可能拉取最新代码,如果操作失败会进入`中间态`。根据提示解决问题后执行`mgit merge --continue`即可继续执行,也可执行`mgit merge --abort`放弃操作。
4. 该指令默认添加`--no-ff`参数,如果需要fast-forwarding,请手动添加`--ff`。



### pull

```
mgit pull [ [(--mrepo|--el-mrepo) ...] [--auto-exec] [--no-check] [--include-lock] [--help]
mgit pull --continue`
mgit pull --abort
```

| 特有参数         | 说明                                                         |
| ---------------- | ------------------------------------------------------------ |
| `--auto-exec`    | 指定该参数会跳过所有交互场景,并自动选择需要的操作执行。该参数主要用于脚本调用mgit进行自动化操作,日常RD开发不应当使用。 |
| `--mrepo`        | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo`     | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--no-check`     | 指定该参数意味着执行前跳过仓库的状态检查,直接对指定或所有仓库执行pull操作,有一定风险,请慎重执行。 |
| `--include-lock` | 指定该参数意味着同时也操作lock仓库。                         |
| `--continue`     | MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数只能单独使用,解决问题后可执行`mgit pull --continue`继续操作其余仓库。 |
| `--abort`        | MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数用于清除操作中间态,且只能单独使用:`mgit pull --abort`。 |
| `--help`         | 显示帮助。                                                   |

【注意】

1. 若指定了配置仓库,执行该指令时会先pull配置仓库,然后根据它内部的配置表pull其余仓库。
2. 执行时,会查看当前分支(如A)和远程分支(若有的话,如origin/A),只有当前分支(A)与远程分支(origin/A)出现落后或分叉的仓库才会执行指令。
3. 执行时,如果本地分支A和其跟踪的远程分支origin/A存在分叉,意味着执行pull之后会跟远程分支合并产生新节点,此时mgit会自动输入合法的log信息,并生成新提交。
4. 为了保证本地保存的远程分支(origin/A)为最新,执行前会先fetch一次。
5. 对于本地有异常的仓库,会提示执行操作,需要二次确认。
6. 执行前会校验各仓库分支是否统一。
7. 中间态:操作主仓库后如果导致配置文件冲突,会进入中间态。解决完冲突后,执行`mgit pull --continue`继续操作子仓库,或执行`mgit pull --abort`放弃本次操作。

### push

`mgit push [<git-push-option>|--auto] [(--mrepo|--el-mrepo) <repo>...] [--group-id] [--help]`

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--group-id` | 指定一个group id,可将本次提交在远程做归类,若未指定则自动生成。生成的id将自动附加在url后:`mgit push --group-id  = git push origin HEAD:refs/for/%topic=` |
| `--help`     | 显示帮助。                                                   |

【注意】

1. 执行时会查看当前分支(如A)和远程分支(若有的话,如origin/A),只有当前分支(A)超前远程分支(origin/A)的仓库才会执行指令。注意,为了保证本地保存到远程分支(origin/A)为最新,执行前会先fetch一次。
2. 对于本地有异常的仓库,会提示执行操作(如`pull`指令描述所示)。
3. 执行前会校验各仓库分支是否统一。



### rebase

```
mgit rebase [<git-rebase-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]
mgit rebase --continue
mgit rebase --abort
```

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--continue` | MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数只能单独使用,解决问题后可执行`mgit rebase --continue`继续操作其余仓库。 |
| `--abort`    | Git原生参数,用于取消`git rebase`中间态,但MGit做了增强,该参数可同时消除`mgit rebase`中间态,且只能单独使用:`mgit rebase --abort` |
| `--help`     | 显示帮助。                                                   |

【注意】

1. 执行前会校验各仓库分支是否统一。
2. 该操作禁止使用`-i`和`--interactive`参数。
3. 中间态:同merge操作。



### reset 

`mgit reset [<git-reset-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |

【注意】执行前会校验各仓库分支是否统一。



### snap

`mgit snap [-m <message>|-r <snapshot_id>|-l] [-h]`

该指令用于根据当前多仓库状态生成快照,也可以从快照中恢复。

| 特有参数        | 说明                                                         |
| --------------- | ------------------------------------------------------------ |
| `-r, --restore` | 从快照中恢复当前仓库,该操作执行前会清空所有未提交的仓库改动,请谨慎执行。用法:`mgit snap -r `。 |
| `-l, --list`    | 列出所有本地存储的快照。                                     |
| `-m, --message` | 执行本次快照时附带的说明信息。用法:`mgit snap -m 'version x'`。 |
| `-h, --help`    | 显示帮助。                                                   |



### stash

`mgit stash [<option> <value>...] [(--mrepo|--el-mrepo) <repo>...] [--help]`

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--apply`    | 恢复储藏,用法:`mgit stash --apply 'stash_name'`。          |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--clear`    | 清空所有储藏。                                               |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--push`     | 添加储藏,用法:`mgit stash --push 'stash_name'`。           |
| `--pop`      | 恢复储藏,用法:`mgit stash --pop 'stash_name'`。            |
| `--list`     | 显示储藏列表。                                               |
| `--help`     | 显示帮助。                                                   |



### status

`mgit status [(-m|-e) <repo>...] [-h]`

| 特有参数         | 说明                                                         |
| ---------------- | ------------------------------------------------------------ |
| `-m, --mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `-e, --el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `-h, --help`     | 显示帮助。                                                   |

status指令是一个比较重要的指令,MGit2.0版对其进行了加强,它的显示区域分为两部分:

- 文件状态:显示四个分类的文件状态列表(`暂存区`,`工作区`,`特殊`,`冲突`)
- 分支状态:显示当前分支和远程分支的对比状态(`超前`,`落后`,`同步`,`游离`)

【注意】

1. 文件状态显示区对所有文件进行归类,每个文件左侧会显示当前文件改动状态:`已修改`,`已添加`, `已删除`,`重命名`,`已拷贝`,`未跟踪`,`被忽略`,其中冲突区的文件会显示两个状态,如:`[添加|修改]`,左侧表示当前分支状态,右侧表示合并分支状态,即当前分支上的该文件为添加状态,合并过来的分支上的该文件为修改状态。
2. 对于干净的仓库,无输出。



###  sync 

`mgit sync [-a|-n|-c] [<repo>...] [-p] [-o] [-h]`

该指令用于根据配置文件同步被锁定的仓库,更新仓库和下载缺失仓库。

| 特有参数         | 说明                                                         |
| ---------------- | ------------------------------------------------------------ |
| `-a, --all`      | 对所有(包含**不被mgit管理**的)仓库操作:1.如果本地缺失则下载。2.如果本地存在且被锁定则同步到锁定状态。注意,如果需要下载仓库,需要配置仓库URL,否则跳过。 |
| `-c, --clone`    | 下载一组仓库(包含**不被mgit管理**的仓库),如: `mgit sync -c repo1 repo2...`。 |
| `-n, --new-repo` | 下载配置表中指定**被mgit管理**,但本地不存在的仓库,已有仓库不做任何处理。 |
| `-o, --no-link`  | 指定后,对于新下载的仓库不会将`.git`实体迁移到.mgit/source-git文件夹中管理。该参数适合开发中途接入mgit的用户使用。 |
| `-p, --pull`     | 若被操作仓库本地存在的话,则进一步执行pull操作更新,配合其他指令使用,如: mgit sync -ap。 |
| `-h, --help`     | 显示帮助。                                                   |

特殊用法:

- `mgit sync`:作用同`-a`,区别是只操作**被mgit管理的仓库**。
- `mgit sync ...`:作用同`-a`,区别是只操作指定(包含**不被mgit管理**的)仓库。

操作仓库作用域总结:

| 指令            | 被mgit管理的仓库 | 不被mgit管理仓库 |
| --------------- | ---------------- | ---------------- |
| `mgit sync`     | ✔                | ✘                |
| `mgit sync ...` | ✔                | ✔                |
| `mgit sync -a`  | ✔                | ✔                |
| `mgit sync -c`  | ✔                | ✔                |
| `mgit sync -n`  | ✔                | ✘                |

【注意】

1. **同步**:是指仓库存在且被锁定(指定了`lock`字段)的情况下,将仓库切换到配置文件指定的锁定状态(`branch`,`tag`或`commit-id`),此时:
2. 需要切换分支的话,若有本地改动,则切换失败;若无本地分支,则提示是否创建并切换;若有对应分支,则直接切换
3. 需要切到具体`tag`或`commit-id`的话,若仓库不存在对应值则失败。
4. 在处理缺失仓库时,如果.mgit/source-git文件夹下有缺失的git实体,那么将直接从该实体clone出来(并将remote url修改为远程地址),否则直接从远程clone。



### tag 

`mgit tag [<git-tag-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]`

| 特有参数     | 说明                                                         |
| ------------ | ------------------------------------------------------------ |
| `--mrepo`    | 指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。 |
| `--el-mrepo` | 指定需要排除执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:`--el-mrepo boxapp BBAAccount`,若缺省则对所有仓库执行指令。与`--mrepo`同时指定时无效。 |
| `--help`     | 显示帮助。                                                   |



## 一些使用技巧

| 特有参数                                     | 说明                      |
| -------------------------------------------- | ------------------------- |
| mgit -l                                      | 查看当前被管理的仓库      |
| mgit info <repo1> <repo2>... #repo大小写均可 | 查看某个仓库的详细信息    |
| mgit branch --compact                        | 查看归并分支              |
| mgit clean                                   | 一键清空工作区            |
| mgit status                                  | 产看仓库分支超前/落后情况 |
| mgit push                                    | 一键推动新分支、新提交    |



================================================
FILE: docs/use/how-to-start.md
================================================
## 快速开始


### 1、安装 MGit 工具

环境要求:

- 系统:支持 macOS、Ubuntu,暂时不支持 window-
- Ruby版本: >= 2.3.7

```ruby
$ gem install m-git
```

### 2、初始化多仓库Demo

初始化多仓库使用 `mgit init` 命令;

类似于 Git 从远程 clone 新仓库, 会将多个仓库 clone 到本地;

下面通过一个demo体验一下MGit命令:

```ruby
# 2.1 建议在一个新文件夹中拉取demo
$ mgit init -g https://github.com/baidu/m-git.git

# 2.2 体验一下mgit命令
$ mgit -l                 显示所有mgit管理的仓库
$ mgit branch --compact   查看多仓库的分支
$ mgit status             产看仓库分支超前/落后情况
```

### 3、已有多仓库如何迁移到 MGit 管理

- 根据文档[配置 manifest.json](../config/manifest.md)

  将要管理的仓库都配置到 manifest.json 中
  
- 将 manifest.json 放到一个仓库中管理

  这个仓库同样会在 manifest.json 中描述,并且需要配置 "config-repo": true 
  
  这个仓库称为配置仓库,也叫做**主仓库**,其他仓库叫做**子仓库**
     
- 使用 `mgit init -f manifest文件路径`  命令初始化多仓库,测试 manifest.json 配置是否正常

  注意这个命令不会重复拉取主仓库,只会拉取所有的子仓库到当前目录,并在当前目录创建一个.mgit
  
  你可以在当前目录中看到每个仓库的源码,他们的路径可以通过  manifest.json 的 dest字段配置
  
  你也可以在 .mgit/source-git/ 下看到所有仓库的.git, 这是 MGit 对所有仓库的托管
  
- 本地测试成功后,你可以提交主仓库中的 manifest.json,推送主仓库的变更到远端
  
- 通过 `mgit init -g 主仓库地址` 命令初始化多仓库
  

     
### 4、使用 MGit 管理多仓库的案例
  
  推荐使用**同名分支原则**管理多仓库: 子仓库的分支与主仓库保持一致(子仓库单独锁定分支的情况除外)
   
  推荐通过在主仓库中[配置 local_manifest.json](../config/manifest.md#3--local_manifest), 控制要同时操作哪些仓库

  例如: 一个工程中有 a、b、c、d 、e、f、g等多个仓库, 当一个需求 A 涉及到三个仓库 a 、b、 c时
  
  - 从 master 新建开发分支 feature_A
    - 拉取主仓库到开发分支 feature_A `git checkout -b feature_A`(操作单仓库时可以直接使用git命令)
    - [创建 local_manifest.json](../config/manifest.md#3--local_manifest) 在 local_manifest.json 中配置 MGit 只管理主仓库和子仓库 a 、b、 c 
      ```ruby
          {
            "mgit-excluded": true,
            "repositories": {
              "config_repo": {
                "mgit-excluded": false
              },
              "a": {
                "mgit-excluded": false
               },
              "b": {
                "mgit-excluded": false
               },
              "c": {
                "mgit-excluded": false
               }
            }
          }
      ```
    - push 主仓库的变更到远程 (包含 local_manifest.json配置的变更)
    - 拉取子仓库 a 、b、 c 的开发分支 feature_A  `mgit checkout -b feature_A`
    - 使用 `mgit branch --compact` 命令查看分支状态
    
  - 在 feature_A 分支开发需求
    - `mgit status`  查看多仓库状态
    - `mgit add .`   添加到暂存区
    - `mgit commit -m 'xxx'` 提交多仓库的变更
    - `mgit push`    推送多仓库到远程
    
  - 合并 feature_A 到 master 分支
    - `mgit merge feature_A -m "xxx comment..."` 将代码 merge 回主干分支
    - 删除主仓库中的 local_manifest.json 文件(如果有增、删、改仓库配置的情况,需要更新到 manifest.json)
    
  


================================================
FILE: lib/m-git/argv/opt.rb
================================================
#coding=utf-8

module MGit

  class ARGV
    # 单个选项, 如‘--k1=v1 --k2 v2 v3 --flag’
    # '--k1=v1'     key: '--k1', value: 'v1'
    # '--k2 v2 v3'  key: '--k2', value: ['v2', 'v3']
    # '--flag'      key: '--flag', value: true
    class Opt
      attr_reader   :key
      attr_accessor :value
      attr_accessor :short_key        # 短参数格式,如’--command’对应的‘-c’
      attr_accessor :priority         # 参数解析优先级
      attr_accessor :info             # 参数说明

      # @!attribute value值类型
      # :array  :string  :boolean
      attr_accessor :value_type

      def initialize(key, default:nil, short_key:nil, priority:-1, info:nil, type: :array)
        raise("初始化Opt选项必须有key") if key.nil?
        @key, @value, @short_key, @priority, @info = key, default, short_key, priority, info
        @value_type = type
      end

      def empty?
        value.nil? || value == '' || value == [] || value == false
      end

      def validate?
        return false if empty?
        value.is_a?(value_type)
      end
    end
  end

end


================================================
FILE: lib/m-git/argv/opt_list.rb
================================================
#coding=utf-8

module MGit
  class ARGV
    # 参数对象列表
    class OptList

      # [Array<ARGV::Opt>] 参数对象数组
      attr_accessor :opts

      # attr_reader :valid_opts

      def initialize(opts)
        @opts = opts
      end

      def valid_opts
        @opts.select { |e| !e.empty? }
      end

      # 获取某个参数对象
      #
      # @param key [String] 参数名,如‘--key’
      #
      # @return [ARGV::Opt] 参数对象,若参数未设置过则返回nil
      #
      def opt(key)
        valid_opts.find { |e| (e.key == key || e.short_key == key) }
      end
      alias_method :opt_with, :opt

      # 判断参数是否设置过值
      #
      # @return [Boolean] 参数是否设置过值
      #
      def did_set_opt?(key)
        !opt(key).nil?
      end

      ## all opts ###

      # 返回某个注册过的参数对象
      #
      # @param key [String] 参数名,如‘--key’
      #
      # @return [ARGV::Opt] 参数对象,无论参数是否设置过,只要注册过就返回
      #
      def registered_opt(key)
        @opts.find { |e| (e.key == key || e.short_key == key) }
      end

      # 判断参数是否注册过
      #
      # @return [Boolean] 参数是否注册过
      #
      def did_register_opt?(key)
        !registered_opt(key).nil?
      end

      # 将参数根据优先级排序(逆序)后返回
      #
      # @return [Array<ARGV::Opt>] 包含参数对象的数组
      #
      def opts_ordered_by_priority
        # 按照优先级进行降序排序
        opts.sort_by { |e| e.priority }.reverse
      end
    end
  end

end


================================================
FILE: lib/m-git/argv/parser.rb
================================================

module MGit
  class ARGV
    module Parser

      # @param [Array] argv
      #
      # @return [String, Array, Array]
      # 返回cmd 和 参数列表
      # # 将参数初步分解,便于后续处理
      # 如:argv = [command zzzz -u sdfsd sdf --mmmm yoo ssss qqq --test asdfa asd ad f --xxx asd as dfa --yoo="ajsdaf" --ppp -abc]
      # 分解为:command, [zzzz], [[-u, sdfsd, sdf], [--mmmm, yoo, ssss, qqq], [--test, asdfa, asd, ad, f], [--xxx, asd, as, dfa], [--yoo, "ajsdaf"], [--ppp], [-a], [-b], [-c]]
      #
      # 初步解析参数
      def self.parse(argv)
        absolute_cmd = argv.join(' ')
        cmd = argv.shift
        pure_opts = argv.join(' ')

        # 将参数初步分解,便于后续处理
        # 如:zzzz -u sdfsd sdf --mmmm yoo ssss qqq --test asdfa asd ad f --xxx asd as dfa --yoo="ajsdaf" --ppp -abc
        # 分解为:[[zzzz], [-u, sdfsd, sdf], [--mmmm, yoo, ssss, qqq], [--test, asdfa, asd, ad, f], [--xxx, asd, as, dfa], [--yoo, "ajsdaf"], [--ppp], [-a], [-b], [-c]]
        temp = []
        raw_opts = []
        argv.each_with_index { |e, idx|

          Foundation.help!("参数\"#{e}\"格式错误,请使用格式如:\"--long\"或\"-s\"") if (e =~ /---/) == 0

          # 检查是否是带'--'或'-'的参数
          if (e =~ /-/) == 0
            # 回收缓存
            raw_opts.push(temp) if temp.length != 0
            # 清空临时缓存
            temp = []

            # 如果是合并的短指令,如'-al = -a + -l',则分拆后直接装入raw_opts数组
            # 因为只有不需要传入值的短参数才能合并,因此不利用临时缓存读取后续参数值
            if e.length > 2 && (e =~ /--/).nil? && !e.include?('=')
              e.split('')[1..-1].each { |s|
                raw_opts.push(["-#{s}"])
              }
              next
            end

            # 处理带‘=’的传值参数
            loc = (e =~ /=/)
            if loc
              temp.unshift(e[(loc + 1)..-1])
              temp.unshift(e[0..loc - 1])
            elsif e.length != 0
              temp.push(e)
            end

          elsif e.length != 0
            temp.push(e)
          end

          if idx == argv.length - 1
            raw_opts.push(temp)
          end
        }
        ARGV.new(cmd, pure_opts, absolute_cmd, raw_opts)
      end
    end
  end
end


================================================
FILE: lib/m-git/argv.rb
================================================
#coding=utf-8

module MGit

  # 参数处理类
  class ARGV

    require 'm-git/argv/opt'
    require 'm-git/argv/opt_list'
    require 'm-git/argv/parser'

    # 指令名,如:"mgit checkout -b branch_name"的"checkout"
    attr_reader :cmd

    # 所有参数,如:"mgit checkout -b branch_name"的"checkout -b branch_name"
    attr_reader :pure_opts

    # 完整指令,如:"mgit checkout -b branch_name"
    attr_reader :absolute_cmd

    # 本次传入的mgit指令中自定义的部分,如:"mgit checkout -b branch_name --mrepo boxapp BBAAccount --command test"的"[[--mrepo boxapp BBAAccount],[--command test]]"
    attr_reader :raw_opts

    # 本次传入的mgit指令中git透传的部分,如:"mgit checkout -b branch_name --mrepo boxapp BBAAccount"的"[[-b branch_name]]"
    # has define method git_opts

    # 所有已注册的参数列表
    attr_reader :opt_list

    def initialize(cmd, pure_opts, absolute_cmd, raw_opts)
      @cmd = cmd
      @pure_opts = pure_opts
      @absolute_cmd = absolute_cmd
      @raw_opts = raw_opts
      @git_opts = []
    end

    def register_opts(opts)
      return if opts.nil?
      @opt_list = OptList.new(opts)
    end

    # 注册解析指令
    def resolve!
      @raw_opts.each { |raw_opt|
        next if @opt_list.did_register_opt?(raw_opt.first)
        @git_opts.push(raw_opt)
      }

      @raw_opts -= @git_opts

      __resolve_git_opts
      __resolve_raw_opts
    end

    # 更新指令值
    # @!attribute [Array / String / true / false] value
    #
    def update_opt(key, value, priority:nil, info:nil)
      return unless @opt_list.did_register_opt?(key)
      opt = @opt_list.registered_opt(key)
      case opt.value_type
      when Array
        opt.value = Array(value)
      when String
        opt.value = value.is_a?(Array) ? value.first.to_s : value.to_s
      else # boolean
        opt.value = value
      end

      opt.priority = priority if !priority.nil?
      opt.info = info if info.is_a?(String)
    end

    # 获取某个option
    def opt(key)
      @opt_list.opt(key)
    end

    # 获取某个option的描述信息
    def info(key)
      return '' unless @opt_list.did_register_opt?(key)
      @opt_list.registered_opt(key)&.info
    end

    # 获取原生git指令(非自定义的指令)
    #
    # @param raw [Boolean] default: true,true:用空格拼接成一个字符串返回。false:直接返回数组,如‘-k k1 k2’ -> 【'-k','k1','k2'】
    #
    # @return [Type] description_of_returned_object
    #
    def git_opts(raw: true)
      return @git_opts unless raw
      opts = []
      @git_opts.each { |e_arr|
        opts += e_arr
      }
      opts.join(' ')
    end

    # 遍历本次调用中传入过值(或有默认值)的选项,未传入值且无默认值则不遍历
    def enumerate_valid_opts
      @opt_list.opts_ordered_by_priority.each { |opt|
        next unless @opt_list.did_set_opt?(opt.key)
        yield(opt) if block_given?
      }
    end

    # 判断一个字符串是否是option(以'--'或'-'开头)
    def is_option?(opt_str)
      (opt_str =~ /-/) == 0 || (opt_str =~ /--/) == 0
    end

    # 输出本次指令的具体值信息,调试时使用
    def show_detail
      @opt_list.opts.each { |opt|
        puts '======='
        puts "key:#{opt.key}"
        puts "value:#{opt.value}"
        puts "info:#{opt.info}"
        puts "\n"
      }
    end

    # 输出参数说明信息
    def show_info
      @opt_list.opts.each { |opt|
        short_key = "#{opt.short_key}, " if !opt.short_key.nil?
        puts "\n"
        puts Output.blue_message("[#{short_key}#{opt.key}]")
        puts "#{opt.info}"
      }
    end

    private

    def __resolve_git_opts
      # 统一将值用双引号括起来,避免空格和特殊字符等引起错误
      @git_opts.each { |e_arr|
        next unless is_option?(e_arr.first)
        e_arr.map!.with_index { |e, i|
          # 如果是 -- / - 开始的option,则增加"" 避免特殊字符的错误,eg: --yoo=ajsdaf  => --yoo="ajsdaf"
          # 如果是 repo1 repo2 这样的option,则不作处理,eg: yoo ajsdaf => yoo ajsdaf
          e = "\"#{e}\"" if !is_option?(e) && i > 0
          e
        }
      }
    end

    def __resolve_raw_opts
      @raw_opts.each { |raw_opt|
        key = raw_opt.first
        opt = @opt_list.registered_opt(key)

        case opt.value_type
        when :boolean
          raise "参数#{key}格式错误,禁止传入参数值!(用法:#{key})" if raw_opt.count != 1
          opt.value = true
        when :string
          raise "参数#{key}格式错误,只能传入一个参数值!(用法:#{key} xxx)" if raw_opt.count != 2
          opt.value = raw_opt.last.to_s
        when :array
          raise "参数#{key}格式错误,至少传入一个参数值!(用法:#{key} xxx xxx ...)" if raw_opt.count < 2
          opt.value = raw_opt[1..-1]
        end
      }
    end
  end

end


================================================
FILE: lib/m-git/base_command.rb
================================================
#coding=utf-8
#

require 'm-git/command_manager'

module MGit
  class BaseCommand

    def self.inherited(sub_klass)
      CommandManager.register_command(sub_klass.cmd, sub_klass)
    end

    # 当前命令行的命令比如checkout / status /..
    def self.cmd
      name.split('::').last.downcase
    end

    # 引入所有自定义指令
    Dir[File.join(__dir__, 'command', '*.rb')].each { |cmd|
      require cmd
    }

    HIGH_PRIORITY_OPT_LIST = {
      :help         =>  '--help',
      :help_s       =>  '-h',
      :auto_exec    => '--auto-exec'
    }.freeze

    # 注意使用时跟指令自身设置的参数对比,避免冲突
    SELECTABLE_OPT_LIST = {
      :mrepo            =>  '--mrepo',
      :mrepo_s          =>  '-m',
      :exclude_mrepo    =>  '--el-mrepo',
      :exclude_mrepo_s  =>  '-e',
      :include_lock     =>  '--include-lock',
      :include_lock_s   =>  '-i',
      :continue         =>  '--continue',
      :abort            =>  '--abort',
    }.freeze

    # 初始化
    #
    # @param argv [ARGV::Opt] 输入参数对象
    #
    def initialize(argv)
      # 指令解析
      setup_argv(argv)
      process_highest_priority_option(argv)
      validate(argv)
      @argv = argv
    end

    #--- 禁止覆写 ---
    # 执行主方法
    def run
      begin
        __config_repo_filter
        pre_exec
        execute(@argv)
        post_exec
      rescue SystemExit, Interrupt
        did_interrupt
      end
    end
    #---------------
    # Workspace Bridge
    def all_repos(except_config:false)
      Workspace.all_repos(except_config: except_config)
    end

    def locked_repos
      Workspace.locked_repos
    end

    def exec_light_repos
      Workspace.exec_light_repos
    end

    def generate_config_repo
      Workspace.generate_config_repo
    end
    # -----------
    private
    def __config_repo_filter
      cfg = Workspace.filter_config
      cfg.include_lock = !@argv.opt(SELECTABLE_OPT_LIST[:include_lock]).nil? || include_lock_by_default
      cfg.select_repos = @argv.opt(SELECTABLE_OPT_LIST[:mrepo])
      cfg.exclude_repos = @argv.opt(SELECTABLE_OPT_LIST[:exclude_mrepo])
      cfg.auto_exec = @argv.opt_list.did_set_opt?(HIGH_PRIORITY_OPT_LIST[:auto_exec])
    end
    #--- 基类调用,禁止覆写 ---
    # 配置参数对象
    def setup_argv(argv)
      argv.register_opts(options)
      argv.resolve!

      argv.opt_list.opts.each do |opt|
        next if opt.empty?
        revise_option_value(opt)
      end
    end

    # 处理最高优指令
    def process_highest_priority_option(argv)
      if argv.opt_list.did_set_opt?(HIGH_PRIORITY_OPT_LIST[:help])
        usage(argv)
        exit
      end
    end

    #-------------------------------------------------------

    #--- 可选覆写 ---
    # 此处有pre hook,覆写时需要先调用super, 特殊指令除外
    def pre_exec
      # 开始计时
      MGit::DurationRecorder.start
      # 配置根目录
      Workspace.setup_multi_repo_root

      # 配置log
      MGit::Loger.config(Workspace.root)
      MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")

      # 执行前置hook
      HooksManager.execute_mgit_pre_hook(@argv.cmd, @argv.pure_opts)

      # 解析配置文件
      Workspace.setup_config

      # 校验实体仓库
      Workspace.setup_all_repos
    end

    # 此处有post hook,覆写时需要最后调用super, 特殊指令除外
    def post_exec
      # 执行后置hook
      HooksManager.execute_mgit_post_hook(@argv.cmd, @argv.pure_opts, Workspace.exec_light_repos)
      # 打点结束
      duration = MGit::DurationRecorder.end
      MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
    end

    # 【子类按需覆写修改返回值】返回true表示可以支持自动执行
    def enable_auto_execution
      false
    end

    # 【子类按需覆写修改返回值】返回true表示可以支持"--mrepo"等可选选项
    def enable_repo_selection
      false
    end

    # 【子类按需覆写修改返回值】是否使默认添加选项(如‘--help’)和可选添加的选项(如‘--mrepo’)支持短指令
    def enable_short_basic_option
      false
    end

    # 【子类按需覆写修改返回值】是否添加操作lock仓库的选项(如‘--include-lock’)
    def enable_lock_operation
      false
    end

    # 【子类按需覆写修改返回值】是否自动将lock仓库加入到操作集中
    def include_lock_by_default
      false
    end

    # 【子类按需覆写修改返回值】是否添加‘--continue’参数,要在指令中自行控制中间态操作
    def enable_continue_operation
      false
    end

    # 【子类按需覆写修改返回值】是否添加‘--abort’参数
    def enable_abort_operation
      false
    end

    # 【子类按需覆写修改实现】按下ctrl+c后调用
    def did_interrupt
    end

    # 可覆写该方法,返回该指令的描述
    def self.description
    end

    # 可覆写该方法,返回该指令的用法
    def self.usage
    end
    #---------------

    #--- 强制覆写 ---
    # 子类指令执行主方法
    def execute(argv)
      Foundation.help!("请覆写父类方法: \"execute(argv)\"")
    end
    #---------------

    #--- 如果要接管某个指令(即为它添加自定义参数) ---
    # --- 强制覆写(若该指令不带任何自定义参数,则无须覆写) ---
    # 注册选项,覆写时注意返回"[...].concat(super)"
    def options
      opts = [
          ARGV::Opt.new(HIGH_PRIORITY_OPT_LIST[:help],
                        short_key:(HIGH_PRIORITY_OPT_LIST[:help_s] if enable_short_basic_option),
                        info:"显示帮助。",
                        type: :boolean)
      ]

      opts.push(
          ARGV::Opt.new(HIGH_PRIORITY_OPT_LIST[:auto_exec],
                        info:'指定该参数会跳过所有交互场景,并自动选择需要的操作执行。该参数主要用于脚本调用mgit进行自动化操作,日常RD开发不应当使用。',
                        type: :boolean)
      ) if enable_auto_execution

      opts.push(
          ARGV::Opt.new(SELECTABLE_OPT_LIST[:mrepo],
          short_key:(SELECTABLE_OPT_LIST[:mrepo_s] if enable_short_basic_option),
          info:'指定需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:"--mrepo boxapp BBAAccount",若缺省则对所有仓库执行指令。'),
          ARGV::Opt.new(SELECTABLE_OPT_LIST[:exclude_mrepo],
          short_key:(SELECTABLE_OPT_LIST[:exclude_mrepo_s] if enable_short_basic_option),
          info:'指定不需要执行该指令的仓库,可指定一个或多个,空格隔开,大小写均可,如:"--el-mrepo boxapp BBAAccount",若缺省则对所有仓库执行指令。与"--mrepo"同时指定时无效。')
      ) if enable_repo_selection

      opts.push(
          ARGV::Opt.new(SELECTABLE_OPT_LIST[:include_lock], info:'指定该参数意味着同时也操作lock仓库。',
                        type: :boolean)
      ) if enable_lock_operation

      opts.push(
          ARGV::Opt.new(SELECTABLE_OPT_LIST[:continue],
                        info:'MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数只能单独使用,解决问题后可执行"mgit <cmd> --continue"继续操作其余仓库。',
                        type: :boolean)
      ) if enable_continue_operation

      opts.push(
          ARGV::Opt.new(SELECTABLE_OPT_LIST[:abort],
                        info:'MGit自定义参数,仅在操作多仓库过程中出现问题停止,执行状态进入中间态后可用。该参数用于清除操作中间态,且只能单独使用:"mgit <cmd> --abort"。',
                        type: :boolean)
      ) if enable_abort_operation

      opts
    end

    # 解析参数并更新参数值到列表中
    def revise_option_value(opt)
    end

    # --- 可选覆写(若该指令不带任何自定义参数,则无须覆写) ---
    # 判断是否有必须参数漏传或数据格式不正确
    def validate(argv)
    end

    # mgit是否输入--continue希望继续上次操作
    def mgit_try_to_continue?
      @argv.opt_list.did_set_opt?(SELECTABLE_OPT_LIST[:continue])
    end

    def mgit_try_to_abort?
      @argv.opt_list.did_set_opt?(SELECTABLE_OPT_LIST[:abort])
    end

    # 显示指令使用信息
    def usage(argv)
      puts "#{Output.blue_message("[指令说明]")}\n#{self.class.description}"
      puts "\n#{Output.blue_message("[指令格式]")}\n#{self.class.usage}"
      argv.show_info
    end

  end
end


================================================
FILE: lib/m-git/command/add.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] add
  # follow git add
  # eg: mgit add .
  #
  class Add < BaseCommand

    # @overload
    #
    def execute(argv)
      Workspace.check_branch_consistency
      Output.puts_start_cmd
      _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)
      Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
    end

    # @overload
    # @return [Boolean]
    #
    def enable_repo_selection
      true
    end

    # @overload
    #
    def self.description
      "将文件改动加入暂存区。"
    end

    # @overload
    #
    def self.usage
      "mgit add [<git-add-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/branch.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] branch
  # follow git branch
  # eg: mgit branch --compact
  #
  class Branch < BaseCommand

    OPT_LIST = {
      :compact    =>  '--compact',
    }.freeze

    def options
      [
          ARGV::Opt.new(OPT_LIST[:compact], info:"以归类的方式显示所有仓库的当前分支。", type: :boolean)
      ].concat(super)
    end

    def execute(argv)
      Output.puts_start_cmd

      if argv.opt(OPT_LIST[:compact])
        show_compact_branches(argv)
        return
      end

      # 无自定义参数则透传
      extcute_as_common(argv)
    end

    # 常规执行
    def extcute_as_common(argv)
      error_repos = {}
      all_repos.sort_by { |repo| repo.name }.each { |repo|
        success, output = repo.execute_git_cmd(argv.cmd, argv.git_opts)
        if success && output.length > 0
          puts Output.generate_title_block(repo.name) {
            output
          } + "\n"
        elsif !success
          error_repos[repo.name] = output
        end
      }
      if error_repos.length > 0
        Workspace.show_error(error_repos)
      else
        Output.puts_succeed_cmd(argv.absolute_cmd)
      end
    end

    # 以紧凑模式执行
    def show_compact_branches(argv)
      show_branches_for_repos(all_repos, false)
      show_branches_for_repos(locked_repos, true)
      Output.puts_succeed_cmd(argv.absolute_cmd)
    end

    # 紧凑地显示一组仓库分支
    def show_branches_for_repos(repos, locked)
      return if repos.nil?

      list = {}
      repos.sort_by { |repo| repo.name }.each { |repo|
        branch = repo.status_checker.current_branch(strict_mode:false)
        branch = 'HEAD游离,不在任何分支上!' if branch.nil?
        list[branch] = [] if list[branch].nil?
        list[branch].push(repo.name)
      }
      list.each { |branch, repo_names|
        Output.puts_remind_block(repo_names, "以上仓库的当前分支:#{branch}#{' [锁定]' if locked}")
        puts "\n"
      }
    end

    def enable_repo_selection
      true
    end

    def self.description
      "创建、显示、删除分支。"
    end

    def self.usage
      "mgit branch [<git-branch-option>|--compact] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end
  end

end


================================================
FILE: lib/m-git/command/checkout.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] checkout
  # follow git checkout
  # eg: mgit checkout master
  #
  class Checkout < BaseCommand

    def execute(argv)
      Output.puts_start_cmd

      # 优先checkout配置仓库
      config_repo = generate_config_repo
      checkout_config_repo(argv.cmd, argv.git_opts, config_repo)

      do_repos = []
      dirty_repos = []

      Output.puts_processing_message("检查各仓库状态...")
      Workspace.serial_enumerate_with_progress(all_repos) { |repo|
        if !config_repo.nil? && repo.name == config_repo.name
          next
        elsif repo.status_checker.status != Repo::Status::GIT_REPO_STATUS[:dirty]
          do_repos.push(repo)
        else
          dirty_repos.push(repo)
        end
      }
      Output.puts_success_message("检查完成!\n")

      if dirty_repos.length > 0
        remind_repos = []
        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 += dirty_repos
            do_repos.uniq! { |repo| repo.name }
          elsif input == 'c' || input != 'a'
            Output.puts_cancel_message
            return
          end
        }
      end

      if do_repos.length == 0
        if config_repo.nil?
          Output.puts_nothing_to_do_cmd
        else
          Output.puts_succeed_cmd(argv.absolute_cmd)
        end
      else
        Output.puts_processing_message("开始checkout子仓库...")
        _, 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
      end
    end

    def checkout_config_repo(cmd, opts, repo)
      return if repo.nil?
      if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
        remind_config_repo_fail("主仓库\"#{repo.name}\"有改动,无法执行!")
      else
        Output.puts_processing_message("开始checkout主仓库...")
        success, output = repo.execute_git_cmd(cmd, opts)
        if !success
          remind_config_repo_fail("主仓库\"#{repo.name}\"执行\"#{cmd}\"失败:\n#{output}")
        else
          Output.puts_success_message("主仓库checkout成功!\n")
        end

        # 刷新配置表
        Workspace.update_config { |missing_repos|
          if missing_repos.length > 0
            all_repos.concat(missing_repos)
            # missing_repos包含新下载的和当前分支已有的新仓库,其中已有仓库包含在@all_repos内,需要去重
            all_repos.uniq! { |repo| repo.name }
          end
        }

      end
    end

    def remind_config_repo_fail(msg)
      Output.puts_fail_message(msg)
      return if Output.continue_with_user_remind?("是否继续操作其余仓库?")
      Output.puts_cancel_message
      exit
    end

    def enable_repo_selection
      true
    end

    def self.description
      "切换分支或恢复工作区改动。"
    end

    def self.usage
      "mgit checkout [<git-checkout-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/clean.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] clean 清除所有仓库中工作区的变更
  # follow git combinatorial command
  # eg: git add . && git reset --hard
  #
  class Clean < BaseCommand

    def execute(argv)

      Output.puts_start_cmd

      # 清除中间态
      OperationProgressManager::PROGRESS_TYPE.each { |type, type_value|
        if OperationProgressManager.is_in_progress?(Workspace.root, type_value)
          Output.puts_processing_message("清除#{type.to_s}中间态...")
          OperationProgressManager.remove_progress(Workspace.root, type_value)
          Output.puts_success_message("清除成功!")
        end
      }

      do_repos = []
      all_repos.each { |repo|
        do_repos.push(repo) if repo.status_checker.status != Repo::Status::GIT_REPO_STATUS[:clean]
      }

      if do_repos.length > 0
        Workspace.check_branch_consistency
        Output.puts_processing_message("正在将改动加入暂存区...")
        _, error_repos1 = Workspace.execute_git_cmd_with_repos('add', '.', do_repos)
        Output.puts_processing_message("正在重置...")
        _, error_repos2 = Workspace.execute_git_cmd_with_repos('reset', '--hard', do_repos)
        Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos1.length + error_repos2.length == 0
      else
        Output.puts_success_message("所有仓库均无改动,无须执行。")
      end

    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if argv.git_opts.length > 0
    end

    def enable_repo_selection
      true
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "强制清空暂存区和工作区,相当于对指定或所有仓库执行\"git add . && git reset --hard\"操作"
    end

    def self.usage
      "mgit clean [(-m|-e) <repo>...] [-h]"
    end

  end

end


================================================
FILE: lib/m-git/command/commit.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] commit
  # follow git commit
  # eg: mgit commit -m 'Just for fun'
  #
  class Commit < BaseCommand

    private def validate(argv)
      super
      # 禁用--amend
      if argv.git_opts.include?('--amend')
        Output.puts_fail_message("MGit不支持\"--amend\"操作,请重试。")
        Output.puts_cancel_message
        exit
      end
    end

    def execute(argv)
      Workspace.check_branch_consistency

      Output.puts_start_cmd

      do_repos = []
      remind_repos = []
      do_nothing_repos = []

      Output.puts_processing_message("检查各仓库状态...")
      Workspace.serial_enumerate_with_progress(all_repos) { |repo|
        if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
          index_dirty_mask = Repo::Status::GIT_REPO_STATUS_DIRTY_ZONE[:index]
          # 仅在暂存区有未提交的改动时执行
          if repo.status_checker.dirty_zone & index_dirty_mask == index_dirty_mask
            do_repos.push(repo)
          else
            remind_repos.push(repo.name)
          end
        else
          do_nothing_repos.push(repo.name)
        end
      }
      Output.puts_success_message("检查完成!\n")

      if remind_repos.length > 0 && !Output.continue_with_interact_repos?(remind_repos, "以上仓库暂存区无可提交内容,仅存在工作区改动或未跟踪文件,若需要提交这些改动请先add到暂存区。是否跳过并继续?")
        Output.puts_cancel_message
        return
      end

      if do_repos.length != 0
        if argv.git_opts.include?('-m ') || !Output.continue_with_user_remind?("未添加\"-m\"参数,请使用如[ mgit commit -m \"my log\" ]的形式提交。是否取消执行并重新输入(若确实有意执行该指令请忽略本提示)?")

          # commit前调用hook
          HooksManager.execute_mgit_pre_exec_hook(argv.cmd, argv.pure_opts, do_repos.map { |e| e.config })

          msg = ",另有#{do_nothing_repos.length}个仓库暂存区无待提交内容,无须执行" if do_nothing_repos.length > 0
          Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始commit以上仓库#{msg}...")
          _, 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_cancel_message
          return
        end
      else
        Output.puts_remind_message("所有仓库均无改动,无须执行!")
      end
    end

    def enable_repo_selection
      true
    end

    def self.description
      "将修改记录到版本库。"
    end

    def self.usage
      "mgit commit [<git-commit-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/config.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] config 配置 .mgit/config.yml 文件信息
  #
  # eg: mgit config -s key 'value'
  #
  class Config < BaseCommand

    OPT_LIST = {
      :create_local          =>  '--create-local',
      :create_local_s        =>  '-c',
      :update_manifest       =>  '--update-manifest',
      :update_manifest_s     =>  '-m',
      :update_local          =>  '--update-local',
      :update_local_s        =>  '-u',
      :list                  =>  '--list',
      :list_s                =>  '-l',
      :set                   =>  '--set',
      :set_s                 =>  '-s'
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:update_manifest],
                        short_key:OPT_LIST[:update_manifest_s],
                        info:"该指令用于更新mgit所使用的配置文件,如:\"mgit config -m <new_path>/manifest.json\"。",
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:update_local],
                        short_key:OPT_LIST[:update_local_s],
                        info:"该指令用于更新mgit所使用的本地配置文件,如:\"mgit config -u <new_path>/local_manifest.json\"。",
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:create_local],
                        short_key:OPT_LIST[:create_local_s],
                        info:"在指定目录下创建本地配置文件,若目录不存在则自动创建。如执行:\"mgit config -c /a/b/c\",则生成本地配置文件:\"/a/b/c/local_manifest.json\"。如果未传入值,如:\"mgit config -c\",那么若配置仓库存在的话,会在配置仓库中创建本地配置文件。",
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:list],
                        short_key:OPT_LIST[:list_s],
                        info:"列出当前MGit所有配置,无参数,如:\"mgit config -l\"。",
                        type: :boolean),
          ARGV::Opt.new(OPT_LIST[:set],
                        short_key:OPT_LIST[:set_s],
                        info:"对MGit进行配置,遵守格式:\"mgit config -s <key> <value>\",如:\"mgit config -s maxconcurrentcount 5\"。")
      ].concat(super)
    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if argv.git_opts.length > 0

      if set_kv = argv.opt(OPT_LIST[:set])
        Foundation.help!("参数#{OPT_LIST[:set]}格式错误,只需传入key和value两个值!") if set_kv.value.count != 2
      end
    end

    # --- 覆写前后hook,不需要预设操作 ---
    def pre_exec
      # 开始计时
      MGit::DurationRecorder.start
      Workspace.setup_multi_repo_root
      # 配置log
      MGit::Loger.config(Workspace.root)
      MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
    end

    def post_exec
      # 打点结束
      duration = MGit::DurationRecorder.end
      MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
    end
    # --------------------------------

    def execute(argv)
      argv.enumerate_valid_opts { |opt|
        if opt.key == OPT_LIST[:update_manifest]
          update_mgit_config(opt.value)
          return
        elsif opt.key == OPT_LIST[:update_local]
          update_local_config(opt.value)
          return
        elsif opt.key == OPT_LIST[:create_local]
          dir = opt.value
          if opt.value.is_a?(TrueClass)
            Workspace.setup_config
            if Workspace.config.config_repo.nil?
              Foundation.help!("未找到配置仓库,请为参数\"--create-local\"或\"-c\"指定一个具体文件夹目录并重试!")
            else
              dir = Workspace.config.config_repo.abs_dest(Workspace.root)
            end
          end
          create_local_config(dir)
          return
        elsif opt.key == OPT_LIST[:list]
          dump_config
        elsif opt.key == OPT_LIST[:set]
          set_config(opt.value)
        end
      }
    end

    # 更新配置表软链接
    def update_mgit_config(config_path)
      config = Manifest.parse(Utils.expand_path(config_path))
      Utils.execute_under_dir("#{File.join(Workspace.root, Constants::PROJECT_DIR[:source_config])}") {
        mgit_managed_config_link_path = File.join(Dir.pwd, Constants::CONFIG_FILE_NAME[:manifest])
        mgit_managed_config_cache_path = File.join(Dir.pwd, Constants::CONFIG_FILE_NAME[:manifest_cache])

        # 在.mgit/source-config文件夹下创建原始配置文件的软连接
        if config.path != mgit_managed_config_link_path
          Utils.link(config.path, mgit_managed_config_link_path)
        end

        # 将配置缓存移动到.mgit/source-config文件夹下
        if config.cache_path != mgit_managed_config_cache_path
          FileUtils.rm_f(mgit_managed_config_cache_path) if File.exist?(mgit_managed_config_cache_path)
          FileUtils.mv(config.cache_path, Dir.pwd)
        end

        Output.puts_success_message("配置文件更新完毕!")
      }
    end

    # 更新本地配置表软链接
    def update_local_config(config_path)
      config_path = Utils.expand_path(config_path)
      Utils.execute_under_dir("#{File.join(Workspace.root, Constants::PROJECT_DIR[:source_config])}") {
        mgit_managed_local_config_link_path = File.join(Dir.pwd, Constants::CONFIG_FILE_NAME[:local_manifest])
        # 在.mgit/source-config文件夹下创建原始本地配置文件的软连接
        if config_path != mgit_managed_local_config_link_path
          Utils.link(config_path, mgit_managed_local_config_link_path)
        end

        Output.puts_success_message("本地配置文件更新完毕!")
      }
    end

    # 新建本地配置表软链接
    def create_local_config(dir)
      path = Utils.expand_path(File.join(dir, Constants::CONFIG_FILE_NAME[:local_manifest]))
      if File.exist?(path) && !Output.continue_with_user_remind?("本地配置文件\"#{path}\"已经存在,是否覆盖?")
        Output.puts_cancel_message
        return
      end

      FileUtils.mkdir_p(dir)
      file = File.new(path, 'w')
      if !file.nil?
        file.write(Template.default_template)
        file.close
      end

      Utils.link(path, File.join(Workspace.root, Constants::PROJECT_DIR[:source_config], Constants::CONFIG_FILE_NAME[:local_manifest]))
      Output.puts_success_message("本地配置文件生成完毕:#{path}")
    end

    # 列出所有配置
    def dump_config
      begin
        MGitConfig.dump_config(Workspace.root)
      rescue Error => e
        Foundation.help!(e.msg)
      end
    end

    # 设置配置
    def set_config(key_value_arr)
      key = key_value_arr.first
      value = key_value_arr.last
      begin
        MGitConfig.update(Workspace.root) { |config|
          if MGitConfig::CONFIG_KEY.keys.include?(key.to_sym)
            valid_value = MGitConfig.to_suitable_value_for_key(Workspace.root, key, value)
            if !valid_value.nil?
              config[key] = valid_value
            else
              type = MGitConfig::CONFIG_KEY[key.to_sym][:type]
              Foundation.help!("#{value}不匹配类型:#{type},请重试。")
            end
          else
            Foundation.help!("非法key值:#{key}。使用mgit config -l查看所有可配置字段。")
          end
        }
        Output.puts_success_message("配置成功!")
      rescue Error => e
        Foundation.help!(e.msg)
      end
    end

    # 允许使用短命令
    def enable_short_basic_option
      true
    end

    def self.description
      "用于更新多仓库配置信息。"
    end

    def self.usage
      "mgit config [-s <config_key> <config_value>] [-l]\nmgit config [(-m|-u) <path_to_manifest> | -c <dir_contains_local>]\nmgit config [-h]"
    end
  end
end


================================================
FILE: lib/m-git/command/delete.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] delete 删除某个仓库的`所有`文件,包括工作区、暂存区和版本库
  #
  # eg: mgit delete subA
  #
  class Delete < BaseCommand

    # --- 覆写前后hook,不需要预设操作 ---
    def pre_exec
      MGit::DurationRecorder.start
      Workspace.setup_multi_repo_root
      # 配置log
      MGit::Loger.config(Workspace.root)
      MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
      Workspace.setup_config
    end

    def post_exec
      # 打点结束
      duration = MGit::DurationRecorder.end
      MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
    end
    # --------------------------------

    def execute(argv)
      delete_repo_names = parse_repo_name(argv)
      include_central = Workspace.config.light_repos.find do |e|
        delete_repo_names.include?(e.name.downcase) && e.is_config_repo
      end
      Foundation.help!("禁止删除配置仓库=> #{include_central.name}") if include_central

      Output.puts_start_cmd
      delete_light_repos = Workspace.config.light_repos.select { |e| delete_repo_names.include?(e.name.downcase) }
      extra_repo_names = delete_repo_names - delete_light_repos.map { |e| e.name.downcase}
      if delete_light_repos.length > 0
        error_repos = {}
        delete_light_repos.each { |light_repo|
          begin
            git_dir = light_repo.git_store_dir(Workspace.root)
            repo_dir = light_repo.abs_dest(Workspace.root)
            if !Dir.exist?(git_dir) && !Dir.exist?(repo_dir)
              Output.puts_remind_message("#{light_repo.name}本地不存在,已跳过。")
            end

            # 删除git实体
            if Dir.exist?(git_dir)
              Output.puts_processing_message("删除仓库#{light_repo.name}的.git实体...")
              FileUtils.remove_dir(git_dir, true)
            end

            # 删除工作区文件
            if Dir.exist?(repo_dir)
              Output.puts_processing_message("删除仓库#{light_repo.name}工作区文件...")
              FileUtils.remove_dir(repo_dir, true)
            end
          rescue => e
            error_repos[light_repo.name] = e.message
          end
        }
        if error_repos.length > 0
          Workspace.show_error(error_repos)
        else
          Output.puts_succeed_cmd(argv.absolute_cmd)
        end
      end

      if extra_repo_names.length > 0
        Output.puts_fail_block(extra_repo_names, "以上仓库配置表中未定义,请重试!")
      end
    end

    def parse_repo_name(argv)
      return if argv.git_opts.nil?

      repos = argv.git_opts.split(' ')
      extra_opts = repos.select { |e| argv.is_option?(e) }
      Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
      Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit info repo1 repo2 ...") if repos.length == 0
      repos.map { |e| e.downcase }
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "删除指定单个或多个仓库(包含被管理的.git文件和工程文件以及跟该.git关联的所有缓存)。"
    end

    def self.usage
      "mgit delete <repo1> <repo2>... [-h]"
    end

  end

end


================================================
FILE: lib/m-git/command/fetch.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] fetch
  # follow git fetch
  # eg: mgit fetch
  #
  class Fetch < BaseCommand
    def execute(argv)
      Output.puts_start_cmd
      _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)
      if error_repos.length == 0
        Output.puts_succeed_cmd(argv.absolute_cmd)
        Timer.show_time_consuming_repos
      end
    end

    def enable_repo_selection
      true
    end

    def self.description
      "与远程仓库同步分支引用和数据对象。"
    end

    def self.usage
      "mgit fetch [<git-fetch-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end
  end

end


================================================
FILE: lib/m-git/command/forall.rb
================================================
#coding=utf-8

module MGit

  # @!scope [command] forall 对管理的仓库依次执行shell命令
  #
  # eg: mgit forall -c 'git status'
  #
  class Forall < BaseCommand

    OPT_LIST = {
      :command      => '--command',
      :command_s    => '-c',
      :concurrent   => '--concurrent',
      :concurrent_s => '-n',
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:command],
                        short_key:OPT_LIST[:command_s],
                        info:'必须参数,指定需要执行的shell命令,如:"mgit forall -c \'git status -s\'"(注意要带引号)。',
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:concurrent],
                        short_key:OPT_LIST[:concurrent_s],
                        info:'可选参数,若指定,则shell命令以多线程方式执行。',
                        type: :boolean)
      ].concat(super)
    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if argv.git_opts.length > 0
      Foundation.help!("请输入必须参数--command,示例:mgit forall -c 'git status'") if argv.opt(OPT_LIST[:command]).nil?
    end

    def execute(argv)
      # 校验分支统一
      Workspace.check_branch_consistency

      Output.puts_start_cmd
      for_all_cmd = argv.opt(OPT_LIST[:command]).value

      use_concurrent = !argv.opt(OPT_LIST[:concurrent]).nil?
      if use_concurrent
        succeed_repos, error_repos = Workspace.execute_common_cmd_with_repos_concurrent(for_all_cmd, all_repos)
      else
        succeed_repos, error_repos = Workspace.execute_common_cmd_with_repos(for_all_cmd, all_repos)
      end

      no_output_repos = []
      succeed_repos.each { |repo_name, output|
        if output.length > 0
          puts Output.generate_title_block(repo_name) { output } + "\n"
        else
          no_output_repos.push(repo_name)
        end
      }

      Output.puts_remind_block(no_output_repos, "以上仓库无输出!") if no_output_repos.length > 0
      Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
    end

    def enable_repo_selection
      true
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "对多仓库批量执行指令。"
    end

    def self.usage
      "mgit forall -c '<instruction>' [(-m|-e) <repo>...] [-n] [-h]"
    end

  end

end


================================================
FILE: lib/m-git/command/info.rb
================================================
#coding=utf-8

module MGit

  # @!scope 指定仓库的信息
  #
  class Info < BaseCommand

    def execute(argv)
      Output.puts_start_cmd

      query_repo_names = parse_repo_name(argv)
      quere_repos = (all_repos + locked_repos).select { |e| query_repo_names.include?(e.name.downcase) }
      if quere_repos.length > 0
        quere_repos.each { |repo|
          puts Output.generate_title_block(repo.name) {
            info = []
            info.push(['仓库位置'.bold, ["#{repo.path}"]])
            info.push(['占用磁盘大小'.bold, ["#{calculate_size(repo)}"]])
            info.push(['创建时间'.bold, ["#{File.ctime(repo.path)}"]])

            current_branch = repo.status_checker.current_branch(strict_mode:false)
            branch_message = repo.status_checker.branch_message
            info.push(['当前分支'.bold, ["#{current_branch.nil? ? '无' : current_branch}"]])
            info.push(['分支状态'.bold, ["#{branch_message}"]])

            info.push(['文件改动'.bold, ["#{repo.status_checker.status != Repo::Status::GIT_REPO_STATUS[:clean] ? '有本地改动,请用status指令查看细节' : '本地无修改'}"]])
            info.push(['Stash状态'.bold, ["#{check_stash(repo)}"]])
            Output.generate_table_combination(info) + "\n\n"
          }
        }
        Output.puts_succeed_cmd(argv.absolute_cmd)
      else
        Output.puts_fail_message("未找到与输入仓库名匹配的仓库,请重试!")
      end

    end

    def parse_repo_name(argv)
      return nil if argv.git_opts.nil?
      repos = argv.git_opts.split(' ')
      extra_opts = repos.select { |e| argv.is_option?(e) }
      Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
      Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit info repo1 repo2 ...") if repos.length == 0
      repos.map { |e| e.downcase }
    end

    def calculate_size(repo)
      success, output = repo.execute("du -sh #{repo.path} | awk '{print $1}'")
      return '计算失败'.red unless success
      output.chomp
    end

    def check_stash(repo)
      success, output = repo.execute("git -C \"#{repo.path}\" stash list")
      return "查询失败".red unless success
      output.length > 0 ? '有内容' : '无内容'
    end

    def enable_short_basic_option
      false
    end

    def self.description
      "输出指定仓库的信息。"
    end

    def self.usage
      "mgit info <repo>... [-h]"
    end

  end

end


================================================
FILE: lib/m-git/command/init.rb
================================================
#coding=utf-8

module MGit

  # @!scope 初始化多仓库命令
  #
  class Init < BaseCommand

    OPT_LIST = {
      :git_source       =>  '--git-source',
      :git_source_s     =>  '-g',
      :config_source    =>  '--config-source',
      :config_source_s  =>  '-f',
      :branch           =>  '--branch',
      :branch_s         =>  '-b',
      :local_config     =>  '--local-config',
      :local_config_s   =>  '-l',
      :all              =>  '--all',
      :all_s            =>  '-a'
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:git_source],
                        short_key:OPT_LIST[:git_source_s],
                        info:'通过包含多仓库配置表的git仓库来初始化,传入远程仓库地址,如:"mgit init -g https://someone@bitbucket.org/someone"。不可与"-f","--config-source"同时指定。',
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:config_source],
                        short_key:OPT_LIST[:config_source_s],
                        info:'通过本地的多仓库配置表来初始化,传入本地配置文件路径,如:"mgit init -f <local_config_path>/manifest.json"。不可与"-g","--git-source"同时指定。',
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:branch],
                        short_key:OPT_LIST[:branch_s],
                        info:'指定配置仓库克隆分支。',
                        type: :string),
          ARGV::Opt.new(OPT_LIST[:local_config],
                        short_key:OPT_LIST[:local_config_s],
                        info:'指定是否自动生成本地配置文件模版。指定后会在主仓库下生成名为local_manifest.json的本地配置文件,其内容只包含主仓库信息,并指定其余仓库不纳入mgit管理。初始化时指定该参数将只下载主仓库,若同时指定了"-a"或"--all",则其余仓库也会被下载。',
                        type: :boolean),
          ARGV::Opt.new(OPT_LIST[:all],
                        short_key:OPT_LIST[:all_s],
                        info:'指定后会下载所有在配置表中配置了远程地址的仓库,无论该仓库是否被纳入mgit管理(无论是否指定"mgit_excluded:true")。',
                        type: :boolean)
      ].concat(super)
    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}") if argv.git_opts.length > 0

      git_source_opt = argv.opt(OPT_LIST[:git_source])
      file_source_opt = argv.opt(OPT_LIST[:config_source])
      if !git_source_opt.nil? && !file_source_opt.nil?
        Foundation.help!("不能同时指定参数\"#{OPT_LIST[:git_source]}\"和\"#{OPT_LIST[:config_source]}!\"")
      elsif git_source_opt.nil? && file_source_opt.nil?
        Foundation.help!("缺失参数\"#{OPT_LIST[:git_source]}\"或\"#{OPT_LIST[:config_source]}!\"")
      end
    end

    # --- 覆写前后hook,不需要预设操作 ---
    def pre_exec
      Output.puts_processing_message("开始初始化多仓库...")
      # 开始计时
      MGit::DurationRecorder.start

      initial_multi_repo_root

      # 配置log
      MGit::Loger.config(Workspace.root)
      MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
    end

    def post_exec
      Output.puts_success_message("多仓库初始化成功!")
      # 打点结束
      duration = MGit::DurationRecorder.end
      MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
    end

    # --------------------------------

    def execute(argv)
      init_dir

      begin
        git_url_opt = argv.opt(OPT_LIST[:git_source])
        local_url_opt = argv.opt(OPT_LIST[:config_source])
        clone_all = argv.opt_list.did_set_opt?(OPT_LIST[:all])
        if !git_url_opt.nil?
          git_url = git_url_opt.value
          branch = argv.opt(OPT_LIST[:branch]).value if argv.opt_list.did_set_opt?(OPT_LIST[:branch])
          use_local = argv.opt_list.did_set_opt?(OPT_LIST[:local_config])
          clone_with_git_url(git_url, branch, use_local, clone_all)
        elsif !local_url_opt.nil?
          clone_with_local_config(local_url_opt.value, clone_all)
        end
      rescue Interrupt => e
        terminate!(e.message)
      end
    end

    def init_dir
      Constants::PROJECT_DIR.each { |key, relative_dir|
        abs_dir = File.join(Workspace.root, relative_dir)
        FileUtils.mkdir_p(abs_dir)
        if key == :hooks
          setup_hooks(File.join(abs_dir, Constants::HOOK_NAME[:pre_hook]),
          File.join(abs_dir, Constants::HOOK_NAME[:post_hook]),
          File.join(abs_dir, Constants::HOOK_NAME[:manifest_hook]),
          File.join(abs_dir, Constants::HOOK_NAME[:post_download_hook]))
        end
      }
    end

    def write_content(path, content)
      file = File.new(path, 'w')
      if !file.nil?
        file.write(content)
        file.close
      end
    end

    def setup_hooks(pre_hook_path, post_hook_path, manifest_hook_path, post_download_hook)
      write_content(pre_hook_path, Template::PRE_CUSTOMIZED_HOOK_TEMPLATE)
      write_content(post_hook_path, Template::POST_CUSTOMIZED_HOOK_TEMPLATE)
      write_content(manifest_hook_path, Template::MANIFEST_HOOK_TEMPLATE)
      write_content(post_download_hook, Template::POST_DOWNLOAD_HOOK_TEMPLATE)
    end

    def initial_multi_repo_root

      if exist_root = Workspace.multi_repo_root_path
        Foundation.help!("当前已在多仓库目录下,请勿重复初始化!\n`#{exist_root}`")
      end

      @origin_root = Dir.pwd
      tmp_root = Utils.generate_init_cache_path(@origin_root)
      FileUtils.mkdir_p(tmp_root)
      Workspace.setup_multi_repo_root(tmp_root)
    end

    def setup_local_config(path, config_repo, use_local)
      if use_local
        content = Template.local_config_template(config_repo)
      else
        content = Template.default_template
      end

      write_content(path, content)
      Utils.link(path, File.join(Workspace.root, Constants::PROJECT_DIR[:source_config], Constants::CONFIG_FILE_NAME[:local_manifest]))
    end

    def clone_with_git_url(git_url, branch, use_local, clone_all)
      # 先将主仓库clone到mgit root目录下
      central_repo_temp_path = File.join(Workspace.root, Constants::CENTRAL_REPO)
      Output.puts_processing_message("正在克隆主仓库...")
      Utils.execute_shell_cmd("git clone -b #{branch.nil? ? 'master' : branch} -- #{git_url} #{central_repo_temp_path}") { |stdout, stderr, status|
        if status.success?
          # 获取主仓库中的配置文件
          begin
            config = Manifest.parse(central_repo_temp_path, strict_mode:false)
          rescue Error => e
            terminate!(e.msg)
          end
          central_light_repo = config.config_repo
          terminate!("配置文件中未找到主仓库配置,请添加后重试!") if central_light_repo.nil?
          terminate!("配置文件中主仓库url与传入的url不一致, 请处理后重试!") if git_url != central_light_repo.url

          central_repo_dest_path = central_light_repo.abs_dest(Workspace.root)

          # 如果不是子目录则删除已有
          if Dir.exist?(central_repo_dest_path) && !central_repo_temp_path.include?(central_repo_dest_path)
            FileUtils.remove_dir(central_repo_dest_path, true)
          end
          FileUtils.mkdir_p(central_repo_dest_path)
          mv_cmd = "mv #{central_repo_temp_path + '/{*,.[^.]*}'} #{central_repo_dest_path}"

          `#{mv_cmd}`
          FileUtils.rm_rf(central_repo_temp_path)

          # 链接.git实体
          Utils.link_git(central_repo_dest_path, central_light_repo.git_store_dir(Workspace.root))
          Output.puts_success_message("主仓库克隆完成!")

          # 链接本地配置文件
          setup_local_config(File.join(central_repo_dest_path, Constants::CONFIG_FILE_NAME[:local_manifest]), central_light_repo.name, use_local)

          # 由于更新了名字和可能的位置移动,重新解析配置文件
          begin
            config = Manifest.parse(central_repo_dest_path, strict_mode:false)
          rescue Error => e
            terminate!(e.msg)
          end

          # clone其余仓库
          clone_sub_repos(config.repo_list(exclusion:[config.config_repo.name], all:clone_all), branch)
          finish_init(config)
        else
          terminate!("主仓库克隆失败,初始化停止,请重试:\n#{stderr}")
        end
      }
    end

    def clone_with_local_config(config_path, clone_all)
      terminate!("指定路径\"#{config_path}\"文件不存在!") if !File.exist?(config_path)

      begin
        config = Manifest.parse(Utils.expand_path(config_path), strict_mode:false)
      rescue Error => e
        terminate!(e.msg)
      end

      if !config.config_repo.nil?
        clone_list = config.repo_list(exclusion: [config.config_repo.name], all:clone_all)
      else
        clone_list = config.repo_list(all:clone_all)
      end

      clone_sub_repos(clone_list, 'master')
      finish_init(config)
    end

    def clone_sub_repos(repo_list, default_branch)
      if repo_list.length != 0
        Output.puts_processing_message("正在克隆子仓库...")
        try_repos = repo_list
        retry_repos = []
        error_repos = {}

        mutex = Mutex.new
        mutex_progress = Mutex.new

        task_count = 0
        total_try_count = 4
        total_retry_count = total_try_count - 1
        # 保证失败仓库有3次重新下载的机会
        total_try_count.times { |try_count|
          Workspace.concurrent_enumerate(try_repos) { |light_repo|
            # 如果mainfest中指定了分支,就替换default branch
            branch = light_repo.branch ? light_repo.branch : default_branch
            Utils.execute_shell_cmd(light_repo.clone_url(Workspace.root, clone_branch:branch)) { |stdout, stderr, status|
              repo_dir = light_repo.abs_dest(Workspace.root)
              if status.success?
                Utils.link_git(repo_dir, light_repo.git_store_dir(Workspace.root))

                mutex_progress.lock
                task_count += 1
                Output.puts_success_message("(#{task_count}/#{try_repos.length}) \"#{light_repo.name}\"克隆完成!")
                mutex_progress.unlock
              else
                Output.puts_remind_message("\"#{light_repo.name}\"克隆失败,已加入重试队列。") if try_count < total_retry_count
                mutex.lock
                retry_repos.push(light_repo)
                error_repos[light_repo.name] = stderr
                FileUtils.remove_dir(repo_dir, true) if Dir.exist?(repo_dir)
                mutex.unlock
              end
            }
          }

          if retry_repos.length == 0 || try_count >= total_retry_count
            break
          else
            Output.puts_processing_block(error_repos.keys, "以上仓库克隆失败,开始第#{try_count + 1}次重试(最多#{total_retry_count}次)...")
            try_repos = retry_repos
            retry_repos = []
            error_repos = {}
            task_count = 0
          end
        }

        if error_repos.length > 0
          Workspace.show_error(error_repos)
          terminate!("初始化停止,请重试!")
        end
      end
    end

    def link_config(config_path, config_cache_path, root)
      Utils.execute_under_dir("#{File.join(root, Constants::PROJECT_DIR[:source_config])}") {
        # 在.mgit/source-config文件夹下创建原始配置文件的软连接
        config_link_path = File.join(Dir.pwd, Constants::CONFIG_FILE_NAME[:manifest])
        Utils.link(config_path, config_link_path) if config_path != config_link_path

        # 将配置缓存移动到.mgit/source-config文件夹下
        FileUtils.mv(config_cache_path, Dir.pwd) if File.dirname(config_cache_path) != Dir.pwd
      }
    end

    def terminate!(msg)
      Output.puts_fail_message(msg)
      Output.puts_processing_message("删除缓存...")
      FileUtils.remove_dir(Workspace.root, true) if Dir.exist?(Workspace.root)
      Output.puts_success_message("删除完成!")
      exit
    end

    def move_project_to_root
      Dir.foreach(Workspace.root) { |item|
        if item != '.' && item != '..' && item != '.DS_Store'
          FileUtils.mv(File.join(Workspace.root, item), @origin_root)
        end
      }
      FileUtils.remove_dir(Workspace.root, true) if Dir.exist?(Workspace.root)
    end

    def finish_init(config)
      move_project_to_root
      config_path, config_cache_path = config.path.sub(Workspace.root, @origin_root), config.cache_path.sub(Workspace.root, @origin_root)
      link_config(config_path, config_cache_path, @origin_root)
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "初始化多仓库目录。"
    end

    def self.usage
      "mgit init (-f <path> | -g <url> [-b <branch>] [-l]) [-a]"
    end
  end
end


================================================
FILE: lib/m-git/command/log.rb
================================================
#coding=utf-8

module MGit

  # @!scope 查询多仓库的日志
  #
  class Log < BaseCommand

    OPT_LIST = {
      :number    =>  '--number',
      :number_s  =>  '-n'
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:number], short_key:OPT_LIST[:number_s], default:500, info:"指定需要显示的提交log个数,默认500。", type: :string)
      ].concat(super)
    end

    def revise_option_value(opt)
      opt.value = Integer(opt.value) if opt.key == OPT_LIST[:number]
    end

    def execute(argv)
      repo_name = parse_repo_name(argv)
      repo = all_repos.find { |e| e.name.downcase == repo_name.downcase }
      number = argv.opt(OPT_LIST[:number]).value
      if repo.nil?
        Output.puts_fail_message("未找到与输入仓库名\"#{repo_name}\"匹配的仓库,请重试!") && return
        return
      end
      # print(Output.processing_message("正在提取#{repo.name}最新的#{number}条log信息..."))
      success, output = repo.execute_git_cmd(argv.cmd, "-n #{number}")
      if success
        if output.length > 0
          Output.puts_in_pager(output.gsub(/commit.*/) { |s| Output.yellow_message(s) })
          # print("\r")
        else
          Output.puts_remind_message("无提交记录")
        end
      else
        Output.puts_fail_message("执行失败:#{output}")
      end
    end

    def parse_repo_name(argv)
      return nil if argv.git_opts.nil?
      repo = argv.git_opts.split(' ')
      extra_opts = repo.select { |e| argv.is_option?(e) }
      Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
      Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit log some_repo") if repo.length == 0
      Foundation.help!("仅允许查询一个仓库!") if repo.length > 1
      repo.first
    end

    def is_integer?(string)
      true if Integer(string) rescue false
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "输出指定(单个)仓库的提交历史。"
    end

    def self.usage
      "mgit log <repo> [-n] [-h]"
    end
  end

end


================================================
FILE: lib/m-git/command/merge.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git merge
  #
  class Merge < BaseCommand

    PROGRESS_STAGE = {
      :new_start          => 0,
      :did_pull_config    => 1,
      :did_refresh_config => 2,
      :did_pull_sub       => 3
    }.freeze
    PROGRESS_STAGE_KEY = 'progress_stage'

    PROGRESS_AUTO = 'auto_exec'

    OPT_LIST = {
      :pull      => '--pull'
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:pull], info:'可选参数,指定后在合并仓库前会自动拉取远程分支更新代码,否则会有交互式询问。如:"mgit merge --pull"。', type: :boolean)
      ].concat(super)
    end

    def __progress_type
      OperationProgressManager::PROGRESS_TYPE[:merge]
    end

    def execute(argv)
      return if do_abort(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
        if !opts.include?('--continue') && !opts.include?('--abort')
          # 自动添加--no-ff
          if !opts.include?('--no-ff') && !opts.include?('--ff') && !opts.include?('--ff-only') && !opts.include?('--squash')
            opts += ' --no-ff'
          end

          # 检测提示
          if !opts.include?('--no-commit') &&
            !opts.include?('--commit') &&
            !opts.include?('-m ') &&
              Output.continue_with_user_remind?("未添加\"-m\"或\"--no-commit\",建议使用:\n     1. mgit merge <branch> -m \"xxx comment...\"\n     2. 或 mgit merge <branch> --no-commit\n    更新本次提交的comment,是否取消并重新输入?")

            Output.puts_cancel_message
            return

          end
        end

        # 优先操作配置仓库
        config_error = merge_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

      error_repos = {}
      if do_repos.length == 0
        Output.puts_remind_message("没有仓库需要执行merge指令!") if config_repo.nil?
      else
        Output.puts_processing_message("开始merge子仓库...")
        _, error_repos = Workspace.execute_git_cmd_with_repos(cmd, opts, do_repos)
      end

      if config_error.nil? && error_repos.length == 0
        Output.puts_succeed_cmd("#{cmd} #{opts}")
      end

      # 清除中间态
      OperationProgressManager.remove_progress(Workspace.root, __progress_type)
    end

    # 合并主仓库
    #
    # @param cmd [String] 合并指令
    #
    # @param opts [String] 合并参数
    #
    # @param repo [Repo] 配置仓库对象
    #
    # @param exec_repos [Array<Repo>] 本次操作的所有仓库(含配置仓库)
    #
    def merge_config_repo(cmd, opts, repo, auto_update)
      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
    end

    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 is_all
        # 更新子仓库
        update_subrepos(exec_subrepos, context, auto_update)
      end

      config_error
    end

    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

    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
      return error
    end

    # 刷新配置表
    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

    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

    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

    def remind_config_repo_fail(msg)
      Output.puts_fail_message(msg)
      if Output.continue_with_user_remind?("是否继续操作其余仓库?")
        return
      else
        Output.puts_cancel_message
        exit
      end
    end

    def do_abort(argv)
      if 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_merge_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

        return true
      else
        return false
      end
    end

    def show_progress_error(summary, detail)
      error = "#{summary} 已进入操作中间态。

原因:
  #{detail}

可选:
  - 使用\"mgit merge --continue\"继续合并。
  - 使用\"mgit merge --abort\"取消合并。"
      Foundation.help!(error, title:'暂停')
    end

    def enable_repo_selection
      return true
    end

    def enable_continue_operation
      return true
    end

    def self.description
      return "合并两个或多个开发历史。"
    end

    def self.usage
      return "mgit merge [<git-merge-option>] [--pull] [(--mrepo|--el-mrepo) <repo>...] [--help]\nmgit merge --continue\nmgit merge --abort"
    end

    # 判断是否需要fast forward
    # def is_fast_forward?(argv, repos)
    #   git_opts = argv.git_opts(raw:false)
    #   git_opts.each { |opt_arr|
    #     if !argv.is_option?(opt_arr.first)
    #       repos.each { |repo|
    #         opt_arr.each { |branch|
    #           if repo.status_checker.is_ancestor_of_branch?(branch)
    #             return true
    #           end
    #         }
    #       }
    #     end
    #   }
    #   return false
    # end

  end

end


================================================
FILE: lib/m-git/command/pull.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git pull
  #
  class Pull < BaseCommand

    OPT_LIST = {
      :no_check    => '--no-check',
    }.freeze

    def options
      [
          ARGV::Opt.new(OPT_LIST[:no_check], info:'指定该参数意味着执行前跳过仓库的状态检查,直接对指定或所有仓库执行pull操作,有一定风险,请慎重执行。', type: :boolean),
      ].concat(super)
    end

    def __progress_type
      OperationProgressManager::PROGRESS_TYPE[:pull]
    end

    def execute(argv)
      if argv.opt(OPT_LIST[:no_check])
        simple_pull(argv)
        return
      end

      verbose_pull(argv)
    end

    def verbose_pull(argv)
      return if do_abort(argv)

      Output.puts_start_cmd

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

      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

        Output.puts_success_message("已跳过主仓库。")

      else

        # 处于中间态则提示
        if OperationProgressManager.is_in_progress?(Workspace.root, __progress_type)
          if Output.continue_with_user_remind?("当前处于操作中间态,建议取消操作并执行\"mgit pull --continue\"继续操作子仓库。\n    继续执行将清除中间态并重新操作所有仓库,是否取消?")
            Output.puts_cancel_message
            return
          end
        end

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

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

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

        Timer.start(repo.name)
        status = repo.status_checker.status
        branch_status = repo.status_checker.branch_status

        if branch_status == Repo::Status::GIT_BRANCH_STATUS[:up_to_date] || branch_status == Repo::Status::GIT_BRANCH_STATUS[:ahead]
          # 领先和最新的仓库均不操作
          do_nothing_repos.push(repo)
        else

          url_consist = repo.url_consist?
          is_dirty = status == Repo::Status::GIT_REPO_STATUS[:dirty]
          dirty_repos.push(repo) if is_dirty
          remote_inconsist_repos.push(repo) if !url_consist

          # 仅有分叉或落后,且工作区干净的仓库直接加入到操作集
          if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged] && !is_dirty && url_consist
            do_repos.push(repo)
            diverged_repos.push(repo.name)
          elsif branch_status == Repo::Status::GIT_BRANCH_STATUS[:behind] && !is_dirty && 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)
          end

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

      if Workspace.filter_config.auto_exec
        do_repos += dirty_repos
        do_repos.uniq! { |repo| repo.name }
      elsif no_remote_repos.length > 0 ||
        dirty_repos.length > 0 ||
        detached_repos.length > 0 ||
        no_tracking_repos.length > 0 ||
        remote_inconsist_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(['有本地改动', dirty_repos.map { |e| e.name }]) if dirty_repos.length > 0
        remind_repos.push(['HEAD游离,当前不在任何分支上', detached_repos.map { |e| e.name }]) if detached_repos.length > 0
        remind_repos.push(['实际url与配置不一致', remote_inconsist_repos.map { |e| e.name }]) if remote_inconsist_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_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
        error_repos = []
        # 如果不带任何参数,则将pull分解为fetch+merge执行, fetch已经执行,此处执行merge。带参数则透传。
        if argv.git_opts.length == 0
          # 排除HEAD游离,无远程分支,未追踪远程分支的仓库,这三种仓库是无法强制执行git pull的(但是可以执行如:git pull origin master,因此透传不做此校验)
          skip_repos = do_repos.select { |repo|
            branch_status = repo.status_checker.branch_status
            branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote] ||
            branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_tracking] ||
            branch_status == Repo::Status::GIT_BRANCH_STATUS[:detached]
          }
          if skip_repos.length > 0
            Output.puts_remind_block(skip_repos.map { |e| e.name }, "以上仓库无法强制执行,已跳过。")
            do_repos -= skip_repos
            if do_repos.length == 0
              Output.puts_success_message("仓库均为最新,无须执行!")
              return
            end
          end

          count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
          Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始为以上仓库合并远程分支#{count_msg}...")
          _, error_repos = Workspace.execute_git_cmd_with_repos('', '', do_repos) { |repo|
            msg = nil
            branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
            tracking_branch = repo.status_checker.tracking_branch(branch, use_cache:true)
            # 如果产生分叉,为生成的新节点提供log
            msg = "-m \"【Merge】【0.0.0】【#{branch}】合并远程分支'#{tracking_branch}'。\"" if diverged_repos.include?(repo.name)
            ["merge", "#{tracking_branch} #{msg}"]
          }
        else
          count_msg = ",另有#{do_nothing_repos.length}个仓库无须执行" if do_nothing_repos.length > 0
          Output.puts_remind_block(do_repos.map { |repo| repo.name }, "开始pull以上仓库#{count_msg}...")
          _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, do_repos)
        end

        if config_error.nil? && error_repos.length == 0
          Output.puts_succeed_cmd(argv.absolute_cmd)
          Timer.show_time_consuming_repos
        end

      else
        Output.puts_success_message("仓库均为最新,无须执行!")
      end

      # 清除中间态
      OperationProgressManager.remove_progress(Workspace.root, __progress_type)
    end

    def simple_pull(argv)
      Output.puts_start_cmd

      # 优先pull配置仓库
      config_repo = generate_config_repo
      config_error = pull_config_repo(argv.cmd, argv.git_opts, config_repo)

      Output.puts_processing_message("开始pull子仓库...")
      _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)

      if config_error.nil? && error_repos.length == 0
        Output.puts_succeed_cmd(argv.absolute_cmd)
      elsif !config_error.nil?
        Output.puts_fail_block([config_repo.name], "主仓库操作失败:#{config_error}")
      end

      # 情况中间态
      OperationProgressManager.remove_progress(Workspace.root, __progress_type)
    end

    def pull_config_repo(cmd, opts, repo)
      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 branch_status == Repo::Status::GIT_BRANCH_STATUS[:no_remote]
          remind_config_repo_fail("主仓库\"#{repo.name}\"远程分支不存在,无法执行!")
        # elsif repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:dirty]
        #   remind_config_repo_fail("主仓库\"#{repo.name}\"有改动,无法执行!")
        else

          Output.puts_processing_message("开始操作主仓库...")

          # 现场信息
          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)

          # 如果不带任何参数,则将pull分解为fetch+merge执行, fetch已经执行,此处执行merge。带参数则透传。
          if opts.length == 0
            branch = repo.status_checker.current_branch(strict_mode:false, use_cache:true)
            tracking_branch = repo.status_checker.tracking_branch(branch, use_cache:true)
            msg = "-m \"【Merge】【0.0.0】【#{branch}】合并远程分支'#{tracking_branch}'。\"" if branch_status == Repo::Status::GIT_BRANCH_STATUS[:diverged]
            cmd, opts = "merge", "#{tracking_branch} #{msg}"
          end

          success, output = repo.execute_git_cmd(cmd, opts)
          if success
            Output.puts_success_message("主仓库操作成功!\n")
          else
            Output.puts_fail_message("主仓库操作失败!\n")
            config_error = output
          end

          # 刷新配置表
          begin
            Workspace.update_config(strict_mode:false) { |missing_repos|
              if missing_repos.length > 0
                success_missing_repos = Workspace.guide_to_checkout_branch(missing_repos, all_repos, 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]
              OperationProgressManager.trap_into_progress(Workspace.root, context)
              show_progress_error("配置表生成失败", "#{e.msg}")
            end
          end

          return config_error
        end
      end
    end

    def remind_config_repo_fail(msg)
      Output.puts_fail_message(msg)
      if Workspace.filter_config.auto_exec || Output.continue_with_user_remind?("是否继续操作其余仓库?")
        return
      else
        Output.puts_cancel_message
        exit
      end
    end

    def do_abort(argv)
      if mgit_try_to_abort?
        Output.puts_start_cmd
        OperationProgressManager.remove_progress(Workspace.root, __progress_type)
        do_repos = all_repos.select { |repo| repo.status_checker.is_in_merge_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('merge', '--abort', do_repos)
          Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
        else
          Output.puts_success_message("没有仓库需要操作!")
        end

        return true
      else
        return false
      end
    end

    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

    def show_progress_error(summary, detail)
      error = "#{summary} 已进入操作中间态。

原因:
  #{detail}

可选:
  - 使用\"mgit pull --continue\"继续拉取。
  - 使用\"mgit pull --abort\"取消拉取。"
      Foundation.help!(error, title:'暂停')
    end

    def enable_repo_selection
      return true
    end

    def enable_auto_execution
      return true
    end

    def include_lock_by_default
      return true
    end

    def enable_continue_operation
      return true
    end

    def enable_abort_operation
      return true
    end

    def self.description
      return "从仓库或本地分支获取数据并合并。"
    end

    def self.usage
      return "mgit pull [<git-pull-option>] [(--mrepo|--el-mrepo) <repo>...] [--auto-exec] [--no-check] [--include-lock] [--help]\nmgit pull --continue\nmgit pull --abort"
    end

  end

end


================================================
FILE: lib/m-git/command/push.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git push
  # 可自动生成 git gerrit 评审分支
  # mgit push --gerrit
  #
  class Push < BaseCommand

    # 默认是否开启gerrit相关功能
    #
    MGIT_PUSH_GERRIT_ENABLED = false

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

    OPT_LIST = {
      :gerrit     =>  '--gerrit',
      :topic_id   =>  '--topic'
    }.freeze

    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

    attr_reader :topic_id
    attr_reader :gerrit_enabled
    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

    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

    private

    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

    #
    # @return [String, String] error_message, code_review_url
    #
    #
    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

    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

    def enable_repo_selection
      return true
    end

    def self.description
      return "更新远程分支和对应的数据对象。"
    end

    def self.usage
      return "mgit push [<git-push-option>|--gerrit] [(--mrepo|--el-mrepo) <repo>...] [--topic] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/rebase.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git rebase
  #
  class Rebase < BaseCommand

    PROGRESS_STAGE = {
      :new_start          => 0,
      :did_pull_config    => 1,
      :did_refresh_config => 2,
      :did_pull_sub       => 3
    }.freeze
    PROGRESS_STAGE_KEY = 'progress_stage'

    PROGRESS_AUTO = 'auto_exec'

    OPT_LIST = {
      :pull      => '--pull'
    }.freeze

    def options
      [
          ARGV::Opt.new(OPT_LIST[:pull], info:'可选参数,指定后在合并仓库前会拉取远程分支更新代码,如:"mgit rabase --pull"。', type: :boolean)
      ].concat(super)
    end

    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

    # 合并主仓库
    #
    # @param cmd [String] 合并指令
    #
    # @param opts [String] 合并参数
    #
    # @param repo [Repo] 配置仓库对象
    #
    # @param exec_repos [Array<Repo>] 本次操作的所有仓库(含配置仓库)
    #
    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

    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

    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

    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

    # 刷新配置表
    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

    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

    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

    def remind_config_repo_fail(msg)
      Output.puts_fail_message(msg)
      return if Output.continue_with_user_remind?("是否继续操作其余仓库?")
      Output.puts_cancel_message
      exit
    end

    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

    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

    def show_progress_error(summary, detail)
      error = "#{summary} 已进入操作中间态。

原因:
  #{detail}

可选:
  - 使用\"mgit rebase --continue\"继续变基。
  - 使用\"mgit rebase --abort\"取消变基。"
      Foundation.help!(error, title:'暂停')
    end

    def enable_repo_selection
      true
    end

    def enable_continue_operation
      true
    end

    def self.description
      "重新将提交应用到其他基点,该命令不执行lock的仓库。"
    end

    def self.usage
      "mgit rebase [<git-rebase-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]\nmgit rebase --continue\nmgit rebase --abort"
    end

    private

    def __progress_type
      OperationProgressManager::PROGRESS_TYPE[:rebase]
    end

  end

end


================================================
FILE: lib/m-git/command/reset.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git reset
  #
  class Reset < BaseCommand

    def execute(argv)
      Workspace.check_branch_consistency

      Output.puts_start_cmd
      _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, all_repos)
      Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
    end

    def enable_repo_selection
      true
    end

    def self.description
      "将当前HEAD指针还原到指定状态。"
    end

    def self.usage
      "mgit reset [<git-reset-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/self.rb
================================================
#coding=utf-8

module MGit

  # 该指令用于不带指令的输入:mgit --help,用于执行mgit的一级参数(如"mgit --help"的"--help")
  #
  class Self < BaseCommand

    HELP_INTRODUCTION = <<INTRO
#{Output.info_title("Description:")}

     mgit是多仓库管理工具,通过将git指令作用到多个仓库,实现批量的版本管理功能

     更多介绍:https://github.com/baidu/mgit

INTRO

    OPT_LIST = {
      :all            =>  '--all',
      :all_s          =>  '-a',
      :list           =>  '--list',
      :list_s         =>  '-l',
      :size           =>  '--size',
      :size_s         =>  '-s',
      :version        =>  '--version',
      :version_s      =>  '-v',
      :workspace      =>  '--workspace',
      :workspace_s    =>  '-w'
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:list], short_key:OPT_LIST[:list_s], info:"显示MGit管理的仓库。", type: :boolean),
          ARGV::Opt.new(OPT_LIST[:size], short_key:OPT_LIST[:size_s], info:"显示MGit管理的仓库的磁盘占用量。", type: :boolean),
          ARGV::Opt.new(OPT_LIST[:all], short_key:OPT_LIST[:all_s], info:"指定操作所有定义在manifest内的仓库,可配合-l合并使用: \"mgit -al\"。", type: :boolean),
          ARGV::Opt.new(OPT_LIST[:version], short_key:OPT_LIST[:version_s], info:"显示当前MGit版本。", type: :boolean),
          ARGV::Opt.new(OPT_LIST[:workspace], short_key:OPT_LIST[:workspace_s], info:"显示当前MGit工程管理根目录(.mgit所在目录)。", type: :boolean)
      ].concat(super)
    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit --help\"查看用法。") if argv.git_opts.length > 0
    end

    def prepare
      Workspace.setup_multi_repo_root
      Workspace.setup_config
    end

    # --- 覆写,不需要预设操作 ---
    def pre_exec
    end

    def post_exec
    end

    def usage(argv)
      show_help(argv)
    end
    # -------------------------

    def execute(argv)
      # 如果存在多余(透传)指令则报错
      extra_opt_str = argv.git_opts
      if extra_opt_str.length > 0
        Output.puts_fail_message("输入无效参数:#{extra_opt_str}\n")
        show_help(argv)
        exit
      end

      argv.enumerate_valid_opts { |opt|
        if opt.key == OPT_LIST[:list] || opt.key == OPT_LIST[:list_s]
          show_all_repos(argv)
          return
        elsif opt.key == OPT_LIST[:size] || opt.key == OPT_LIST[:size_s]
          show_repo_size
          return
        elsif opt.key == OPT_LIST[:version] || opt.key == OPT_LIST[:version_s]
          show_version
          return
        elsif opt.key == OPT_LIST[:workspace] || opt.key == OPT_LIST[:workspace_s]
          show_workspace
          return
        end
      }

      # 无任何参数传入时显示帮助
      show_help(argv)
    end

    def show_help(argv)
      head_space = '    '
      middle_space = '   '

      output =  HELP_INTRODUCTION # "#{Output.info_title("Description:")}\n\n#{head_space}mgit是多仓库管理工具,通过将git指令作用到多个仓库,实现批量的版本管理功能。"
      output += "#{Output.info_title("Usage:")}\n\n#{head_space}$ #{Output.green_message("mgit <mgit_options>")}\n"
      output += "#{head_space}$ #{Output.green_message("mgit <command> [<command_option>...] [<value>...]")}\n\n"

      # mgit options
      output += "#{Output.info_title("MGit Option:")}\n\n"
      divider = ", "
      longest_opt = argv.opt_list.opts.max_by { |e| "#{Output.blue_message("#{e.key}#{divider + e.short_key if !e.short_key.nil?}")}".length }
      max_opt_length = "#{longest_opt.short_key + divider + longest_opt.key}".length
      mgit_option_info = ''
      argv.opt_list.opts.each { |opt|
        key = "#{opt.short_key + divider + opt.key}"
        mgit_option_info += "#{head_space}#{Output.blue_message(key)}#{' ' * (max_opt_length - key.length + middle_space.length)}#{argv.info(opt.key)}\n"
      }
      output += mgit_option_info + "\n"

      # subcommand
      output += "#{Output.info_title("Command:")}\n\n"
      cmd_header = '+ '
      cmd_info = ''

      max_cmd_length = Output.blue_message(cmd_header + CommandManager.commands.keys.max_by { |e| e.length }.to_s).length
      CommandManager.commands.keys.sort.each { |cmd_name|
        next if cmd_name == self.class.cmd
        cls_name = CommandManager.commands[cmd_name]
        cmd_name = Output.green_message(cmd_header + cmd_name.downcase.to_s)
        cmd_info += "#{head_space}#{cmd_name}#{' ' * (max_cmd_length - cmd_name.length + middle_space.length)}#{cls_name.description}\n"
      }
      output += cmd_info + "\n"
      output += "#{Output.info_title("Command Option:")}\n\n#{head_space}请通过[ mgit <command> --help ]查看。\n\n"
      output += "#{Output.info_title("Version:")}\n\n#{head_space}#{MGit::VERSION}\n"

      puts output
    end

    def show_all_repos(argv)
      prepare

      list_all = argv.opt_list.did_set_opt?(OPT_LIST[:all])
      existing_repos, missing_repos = prepare_repos(with_excluded:list_all)

      list = {}
      if existing_repos.length > 0
        existing_repos.sort_by { |e| e.name }.each { |light_repo|
          dir = File.join("<ROOT>", File.dirname(light_repo.path)).bold
          list[dir] = [] if list[dir].nil?
          list[dir].push(light_repo.name)
        }
      end

      if missing_repos.length > 0
        list['本地缺失'.bold] = missing_repos.sort_by { |e| e.name }.map { |e| e.name }
      end

      list_array = []
      list.each { |dir, list|
        list_array.push([dir.bold, list])
      }

      puts Output.generate_table_combination(list_array, separator:'|')
      if list_all
        message = "共统计#{existing_repos.length + missing_repos.length}个仓库。"
      else
        message = "mgit目前共管理#{existing_repos.length + missing_repos.length}个仓库。"
      end
      Output.puts_remind_message(message)
      Output.puts_fail_message("有#{missing_repos.length}个仓库本地缺失!") if missing_repos.length > 0
    end

    def show_repo_size
      prepare
      Workspace.setup_all_repos
      Output.puts_processing_message("开始计算...")
      repo_size = {}
      mutex = Mutex.new
      task_count = 0
      Output.update_progress(all_repos.length, task_count)
      Workspace.concurrent_enumerate(all_repos) { |repo|
        success, output = repo.execute("du -sh #{repo.path}")
        mutex.lock
        if success
          repo_size[repo.name] = output&.strip
        end
        task_count += 1
        Output.update_progress(all_repos.length, task_count)
        mutex.unlock
      }
      Output.puts_success_message("计算完成。")

      display_size = repo_size.sort_by { |k,v| k}.map { |e| e.last }
      Output.puts_remind_block(display_size, "共统计#{repo_size.length}个仓库。")
    end

    def prepare_repos(with_excluded:false)
      existing_repos = []
      missing_repos = []
      Workspace.config.light_repos.each { |light_repo|
        if with_excluded || !light_repo.mgit_excluded
          repo_exist = Repo.is_git_repo?(light_repo.abs_dest(Workspace.root))
          if repo_exist
            existing_repos.push(light_repo)
          else
            missing_repos.push(light_repo)
          end
        end
      }
      return existing_repos, missing_repos
    end

    def show_workspace
      root = Workspace.multi_repo_root_path
      if root.nil?
        Output.puts_fail_message("当前不在任何多仓库目录下!")
      else
        puts root
      end
    end

    def show_version
      puts MGit::VERSION
    end

    def enable_short_basic_option
      return true
    end

  end

end


================================================
FILE: lib/m-git/command/stash.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git stash,但是强制标记名称
  #
  class Stash < BaseCommand

    OPT_LIST = {
      :push     => '--push',
      :pop      => '--pop',
      :apply    => '--apply',
      :list     => '--list',
      :clear    => '--clear',
    }.freeze

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:push], info:'添加储藏:mgit stash --push "stash_name"。', type: :string),
          ARGV::Opt.new(OPT_LIST[:pop], info:'恢复储藏:mgit stash --pop "stash_name"。', type: :string),
          ARGV::Opt.new(OPT_LIST[:apply], info:'恢复储藏:mgit stash --apply "stash_name"。', type: :string),
          ARGV::Opt.new(OPT_LIST[:list], info:'显示储藏列表。', type: :boolean),
          ARGV::Opt.new(OPT_LIST[:clear], info:'清空所有储藏。', type: :boolean)
      ].concat(super)
    end

    def validate(argv)
      missing_msg = "缺失必要参数"  if argv.raw_opts.length == 0
      illegal_msg = "输入非法参数:#{argv.git_opts}" if argv.git_opts.length > 0
      conjunction = ",同时" if !missing_msg.nil? && !illegal_msg.nil?
      Foundation.help!("#{missing_msg}#{conjunction}#{illegal_msg},请通过\"mgit #{argv.cmd} --help\"查看用法。") if !missing_msg.nil? || !illegal_msg.nil?
    end

    # 注意:git stash相关命令不支持指定“working tree”和“git dir”
    # 如:git --git-dir=/path/to/.git --work-tree=/path/to/working-tree stash list,若当前不在working tree目录下,则将无法执行。
    def execute(argv)
      argv.enumerate_valid_opts { |opt|
        if opt.key == OPT_LIST[:push]
          do_stash_push(argv, opt.value)
          break
        elsif opt.key == OPT_LIST[:pop] || opt.key == OPT_LIST[:apply]
          action = opt.key.gsub('--', '')
          do_stash_pop_apply(argv, opt.value, action)
          break
        elsif opt.key == OPT_LIST[:list]
          do_stash_list(argv)
        elsif opt.key == OPT_LIST[:clear]
          do_clear(argv)
        end
      }
    end

    def do_clear(argv)
      if Output.continue_with_user_remind?("该操作会丢失所有的stash,确定要执行吗?")
        Output.puts_start_cmd
        abs_cmd = "git stash clear"
        _, error_repos = Workspace.execute_common_cmd_with_repos(abs_cmd, all_repos)
        Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
      else
        Output.puts_cancel_message
      end
    end

    def do_stash_list(argv)
      Output.puts_start_cmd

      error_repos = {}
      all_repos.each { |repo|
        cmd = "git -C \"#{repo.path}\" stash list"
        success, output = repo.execute(cmd)
        if success
          puts Output.generate_title_block(repo.name) {
            output
          } + "\n" if output.length > 0
        else
          error_repos[repo.name] = output
        end
      }
      if error_repos.length > 0
        Workspace.show_error(error_repos)
      else
        Output.puts_succeed_cmd(argv.absolute_cmd)
      end
    end

    def do_stash_pop_apply(argv, stash_name, action)
      do_repos = []
      mutex = Mutex.new
      Output.puts_processing_message("检查仓库状态...")
      all_repos.each { |repo|
        stash_list = repo_stash_list_msg(repo)
        next if stash_list.nil?

        stash_id = stash_include_name(stash_list, stash_name)
        next if stash_id.nil?
        mutex.lock
        do_repos.push(repo)
        mutex.unlock
      }
      Output.puts_success_message("检查完成!\n")

      Output.puts_start_cmd
      if do_repos.length == 0
        Output.puts_nothing_to_do_cmd
      else
        _, error_repos = Workspace.execute_common_cmd_with_repos('', do_repos) { |repo|
          stash_list = repo_stash_list_msg(repo)
          stash_id = stash_include_name(stash_list, stash_name)
          "git stash #{action} #{stash_id}"
        }
        Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
      end
    end

    def do_stash_push(argv, stash_name)
      do_repos = []
      remind_repos = []
      mutex = Mutex.new
      Output.puts_processing_message("检查仓库状态...")
      all_repos.each { |repo|
        next if repo.status_checker.status == Repo::Status::GIT_REPO_STATUS[:clean]
        next if repo.status_checker.dirty_zone == Repo::Status::GIT_REPO_STATUS_DIRTY_ZONE[:special]

        stash_list = repo_stash_list_msg(repo)
        stash_id = stash_include_name(stash_list, stash_name)
        mutex.lock
        if stash_id.nil?
          do_repos.push(repo)
        else
          remind_repos.push(repo.name)
        end
        mutex.unlock
      }
      Output.puts_success_message("检查完成!\n")

      if remind_repos.length > 0
        Output.puts_remind_block(remind_repos, "以上仓库当前分支已经存在stash名称:#{stash_name},请换一个名称或者使用\"mgit stash --list\"查看详情。")
        Output.puts_fail_cmd(argv.absolute_cmd)
      elsif do_repos.empty?
        Output.puts_remind_message("所有仓库均是clean状态或者文件未跟踪,无需执行")
      else
        Output.puts_start_cmd
        abs_cmd = "git stash save -u #{stash_name}"
        _, error_repos = Workspace.execute_common_cmd_with_repos(abs_cmd, do_repos)
        Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
      end
    end

    # 获取当前的 stash list 字符串,nil,标识当前没有stash
    def repo_stash_list_msg(repo)
      success, output = repo.execute("git -C \"#{repo.path}\" stash list")
      return output if success && output.length > 0
    end

    # 查询stash_list 是否包含某一个保存的stash
    # 不做分支判断,因为在保存的stashlist中,分支只保留了/之后的内容
    #
    def stash_include_name(stash_list, stash_name)
      return if stash_list.nil?

      stash_list_array = stash_list.split("\n")

      find_stash_id = nil
      stash_list_array.each do |line|
        regex = /(stash@{\d+}):.*:\s(.*)$/
        next unless line.match(regex)
        match_stash_name = $2
        next unless match_stash_name == stash_name
        find_stash_id = $1
        break
      end
      find_stash_id
    end

    def enable_repo_selection
      true
    end

    def self.description
      "使用git stash将当前工作区改动暂时存放起来。"
    end

    def self.usage
      "mgit stash [<option> <value>...] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command/status.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git status
  #
  class Status < BaseCommand
    def execute(argv)
      Output.puts_processing_message("正在检查各仓库状态...")

      status_info = {}
      mutex = Mutex.new
      mutex_branch = Mutex.new
      mutex_modification = Mutex.new
      mutex_other = Mutex.new

      branch_notice = []
      modification_notice = []
      other_notice = []
      in_progress_notice = []

      task_count = 0
      repo_combo = all_repos + locked_repos
      Output.update_progress(repo_combo.length, task_count)
      Workspace.concurrent_enumerate(repo_combo) { |repo|
        status_msg = ''
        info = []

        # 非锁定仓库进行常规分支检查
        if !locked_repos.include?(repo) &&
          repo.status_checker.branch_status != Repo::Status::GIT_BRANCH_STATUS[:up_to_date] &&
          repo.status_checker.branch_status != Repo::Status::GIT_BRANCH_STATUS[:no_remote]
            info.push(['分支', [repo.status_checker.branch_message]])

            mutex_branch.lock
            branch_notice.push(repo.name)
            mutex_branch.unlock
        end

        # 检查工作区状态
        if repo.status_checker.status != Repo::Status::GIT_REPO_STATUS[:clean]
          info += repo.status_checker.message
          mutex_modification.lock
          modification_notice.push(repo.name)
          mutex_modification.unlock
        end

        # 检查url是否一致
        if !repo.url_consist?
          info.push(['其他', ['仓库实际url与当前配置不一致']])
          mutex_other.lock
          other_notice.push(repo.name)
          mutex_other.unlock
        end

        # 生成表格
        status_msg = Output.generate_table_combination(info) + "\n\n" if info.length > 0

        # 压缩状态信息
        mutex.lock
        if status_msg.length > 0
          status_info[status_msg] = {'repo_names' => [], 'info' => info} if status_info[status_msg].nil?
          status_info[status_msg]['repo_names'].push(repo.name)
        end
        task_count += 1
        Output.update_progress(repo_combo.length, task_count)
        mutex.unlock
      }

      status_info.each_with_index { |(status_msg, item), index|
        info = item['info']
        repo_names = item['repo_names']
        Output.puts_remind_block(repo_names, "以上仓库状态:")
        MGit::Loger.info(info)
        status_msg += "\n" if index != status_info.length - 1
        puts status_msg
      }

      OperationProgressManager::PROGRESS_TYPE.each { |type, type_str|
        if OperationProgressManager.is_in_progress?(Workspace.root, type_str)
          in_progress_notice.push(type.to_s)
        end
      }

      summary = []
      if branch_notice.length > 0
        summary.push(["分支提醒(#{branch_notice.length})",branch_notice])
      end

      if modification_notice.length > 0
        summary.push(["改动提醒(#{modification_notice.length})",modification_notice])
      end

      if other_notice.length > 0
        summary.push(["其他警告(#{other_notice.length})", other_notice])
      end

      if in_progress_notice.length > 0
        summary.push(["处于中间态的操作",in_progress_notice])
      end

      if summary.length > 0
        puts "\n"
        puts Output.generate_table_combination(summary, title: "状态小结", separator: "|")
        MGit::Loger.info('状态小结')
        MGit::Loger.info(summary)
      end

      Output.puts_success_message("所查询仓库均无改动!") if status_info.keys.length == 0

      Output.puts_succeed_cmd(argv.absolute_cmd)
    end

    def validate(argv)
      Foundation.help!("输入非法参数:#{argv.git_opts}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if argv.git_opts.length > 0
    end

    def enable_repo_selection
      true
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "输出所有仓库的状态。包括:\"分支\",\"暂存区\",\"工作区\",\"特殊(未跟踪和被忽略)\",\"冲突\"。"
    end

    def self.usage
      "mgit status [(-m|-e) <repo>...] [-h]"
    end
  end

end


================================================
FILE: lib/m-git/command/sync.rb
================================================
#coding=utf-8

module MGit

  # @!scope 同步所有管理的仓库到工作区,可能从远端拉取,可能从本地dump,所以sync后的工作区不一定是分支的最新节点
  # 可通过 mgit sync --pull 进行同步后pull到最新节点
  #
  class Sync < BaseCommand

    OPT_LIST = {
      :new_repo    =>  '--new-repo',
      :new_repo_s  =>  '-n',
      :all         =>  '--all',
      :all_s       =>  '-a',
      :clone       =>  '--clone',
      :clone_s     =>  '-c',
      :pull        =>  '--pull',
      :pull_s      =>  '-p',
      :url         =>  '--url',
      :url_s       =>  '-u',
    }.freeze

    # --- 覆写前后hook,不需要预设操作 ---
    def pre_exec
      MGit::DurationRecorder.start
      Workspace.setup_multi_repo_root
      # 配置log
      MGit::Loger.config(Workspace.root)
      MGit::Loger.info("~~~ #{@argv.absolute_cmd} ~~~")
      Workspace.setup_config
    end

    def post_exec
      # 打点结束
      duration = MGit::DurationRecorder.end
      MGit::Loger.info("~~~ #{@argv.absolute_cmd}, 耗时:#{duration} s ~~~")
    end
    # --------------------------------

    def options
      return [
          ARGV::Opt.new(OPT_LIST[:new_repo],
                        short_key:OPT_LIST[:new_repo_s],
                        info:"下载配置表中指定被mgit管理,但本地不存在的仓库,已有仓库不做任何处理,使用:mgit sync -n。",
                        type: :boolean),
          ARGV::Opt.new(OPT_LIST[:all],
                        short_key:OPT_LIST[:all_s],
                        info:'对所有(包含不被mgit管理的)仓库操作:1.如果本地缺失则下载。2.如果本地存在且被锁定则同步到锁定状态。注意,如果需要下载代码,需要配置仓库URL,否则跳过,使用:mgit sync -a。',
                        type: :boolean),
          ARGV::Opt.new(OPT_LIST[:clone], short_key:OPT_LIST[:clone_s], info:'下载一组仓库(包含不被mgit管理的仓库),如: mgit sync -c repo1 repo2。'),
          ARGV::Opt.new(OPT_LIST[:pull],
                        short_key:OPT_LIST[:pull_s],
                        info:'同步本地仓库后执行pull操作更新,配合其他指令使用,如: mgit sync -a -p。',
                        type: :boolean),
          ARGV::Opt.new(OPT_LIST[:url],
                        short_key:OPT_LIST[:url_s],
                        info:'校验并同步URL与配置不一致的仓库,如: mgit sync -u。',
                        type: :boolean)
      ].concat(super)
    end

    def validate(argv)
      opt_all = argv.opt(OPT_LIST[:all])
      opt_clone = argv.opt(OPT_LIST[:clone])
      opt_new = argv.opt(OPT_LIST[:new_repo])
      invalid = [opt_all, opt_clone, opt_new].select { |e| !e.nil? }.length > 1
      Foundation.help!("请勿同时指定[#{OPT_LIST[:all]}|#{OPT_LIST[:all_s]}],[#{OPT_LIST[:clone]}|#{OPT_LIST[:clone_s]}]和[#{OPT_LIST[:new_repo]}|#{OPT_LIST[:new_repo_s]}]。") if invalid
    end

    def execute(argv)
      Output.puts_start_cmd

      if argv.opt_list.did_set_opt?(OPT_LIST[:all])
        setup_all_sync_reops(argv)
      elsif argv.opt_list.did_set_opt?(OPT_LIST[:new_repo])
        setup_new_reops
      elsif argv.opt_list.did_set_opt?(OPT_LIST[:url])
        setup_config_url_repos
      elsif argv.opt_list.did_set_opt?(OPT_LIST[:clone])
        return if !setup_download_reops(argv.opt(OPT_LIST[:clone]).value)
      elsif argv.git_opts.length > 0
        return if !setup_specified_repos(argv)
      else
        setup_normal_reops(argv)
      end

      if (@sync_repos.length + @update_repos.length + @download_repos.length) == 0
        Output.puts_success_message("没有仓库需要同步!")
        return
      end

      error_repos = {}
      if @sync_repos.length > 0
        Workspace.concurrent_enumerate_with_progress_bar(@sync_repos, "正在同步(锁定)以上仓库...") { |light_repo|
          repo = Repo.generate_strictly(Workspace.root, light_repo)
          error_message = Repo::SyncHelper.sync_exist_repo(repo, light_repo)
          if !error_message.nil?
            Lock.mutex_exec { error_repos[light_repo.name] = error_message }
          end
        }
      end

      if @update_repos.length > 0
        Workspace.concurrent_enumerate_with_progress_bar(@update_repos, "正在更新以上仓库...") { |light_repo|
          repo = Repo.generate_strictly(Workspace.root, light_repo)
          success, output = repo.execute_git_cmd('pull', '')
          if !success
            Lock.mutex_exec { error_repos[light_repo.name] = output }
          end
        }
      end

      if @download_repos.length > 0
        Workspace.sync_new_repos(@download_repos)
      end

      if error_repos.length > 0
        Workspace.show_error(error_repos)
      else
        Output.puts_succeed_cmd(argv.absolute_cmd)
      end
    end

    def prepare_repo_category
      @sync_repos = []
      @update_repos = []
      @download_repos = []
    end

    # M/NM: 被/不被mgit管理
    # E/NE: 仓库本地存在/不存在
    # L/NL: 仓库被/不被锁定
    # U/NU: 仓库有/没有远程地址

    # mgit sync repo1 repo2 ... [--pull]
    # 针对一组指定仓库进行:下载仓库:NE|U,同步仓库:E|L,更新仓库: E|U(可选)
    def setup_specified_repos(argv)
      prepare_repo_category
      should_pull = argv.opt_list.did_set_opt?(OPT_LIST[:pull])
      valid_repos, error_repo_names = check_valid_repos(parse_repo_name(argv))
      valid_repos.each { |light_repo|
        repo_exist = Dir.exist?(light_repo.abs_dest(Workspace.root))
        if !repo_exist
          if !light_repo.url.nil?
            @download_repos.push(light_repo)
          else
            error_repo_names.push(light_repo.name)
          end
        else
          if light_repo.lock
            @sync_repos.push(light_repo)
          end

          if should_pull
            if !light_repo.url.nil?
              @update_repos.push(light_repo)
            else
              error_repo_names.push(light_repo.name)
            end
          end
        end
      }

      should_continue = true
      error_repos = []
      error_repos.push(['配置表中未定义(或未指定"remote-path")', error_repo_names]) if error_repo_names.length > 0
      Output.interact_with_multi_selection_combined_repos(error_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 终止']) { |input|
        if input == 'b'
          Output.puts_cancel_message
          should_continue = false
        end
      } if error_repos.length > 0
      return should_continue
    end

    # mgit sync [--pull]
    # 下载仓库:M|NE|U, 同步仓库:M|E|L, 更新仓库:M|E|U(可选)
    def setup_normal_reops(argv)
      prepare_repo_category
      should_pull = argv.opt_list.did_set_opt?(OPT_LIST[:pull])
      Workspace.config.light_repos.each { |light_repo|
        if !light_repo.mgit_excluded
          repo_exist = Dir.exist?(light_repo.abs_dest(Workspace.root))
          if !repo_exist && !light_repo.url.nil?
            @download_repos.push(light_repo)
          elsif repo_exist && light_repo.lock
            @sync_repos.push(light_repo)
          end

          if repo_exist && should_pull && !light_repo.url.nil?
              @update_repos.push(light_repo)
          end
        end
      }
    end

    # mgit sync --new
    # 下载仓库:M|NE|U
    def setup_new_reops
      prepare_repo_category
      Workspace.config.light_repos.each { |light_repo|
        if !light_repo.mgit_excluded
          repo_exist = Dir.exist?(light_repo.abs_dest(Workspace.root))
          @download_repos.push(light_repo) if !repo_exist && !light_repo.url.nil?
        end
      }
    end

    # mgit sync --all [--pull]
    # 下载仓库:NE|U,同步仓库:E|L,更新仓库: E|NL|U(可选)
    def setup_all_sync_reops(argv)
      prepare_repo_category
      Workspace.config.light_repos.each { |light_repo|
        repo_exist = Dir.exist?(light_repo.abs_dest(Workspace.root))
        if !repo_exist && !light_repo.url.nil?
          @download_repos.push(light_repo)
        elsif repo_exist && light_repo.lock
          @sync_repos.push(light_repo)
        elsif repo_exist && !light_repo.url.nil? && argv.opt_list.did_set_opt?(OPT_LIST[:pull])
          @update_repos.push(light_repo)
        end
      }
    end

    # mgit sync --clone repo1 repo2 ...
    # 下载仓库:NE|U(指定)
    def setup_download_reops(repo_names)
      prepare_repo_category
      existing_repos = []
      valid_repos, error_repo_names = check_valid_repos(repo_names)

      valid_repos.each { |light_repo|
        repo_exist = Dir.exist?(light_repo.abs_dest(Workspace.root))
        if !repo_exist
          if !light_repo.url.nil?
            @download_repos.push(light_repo)
          else
            error_repo_names.push(light_repo.name)
          end
        else
          error_repo_names.push(light_repo.name) if light_repo.url.nil?
          existing_repos.push(light_repo.name)
        end
      }

      should_continue = true
      error_repos = []
      error_repos.push(['配置表中未定义(或未指定"remote-path")', error_repo_names]) if error_repo_names.length > 0
      error_repos.push(['本地已经存在', existing_repos]) if existing_repos.length > 0
      Output.interact_with_multi_selection_combined_repos(error_repos, "以上仓库状态异常", ['a: 跳过并继续', 'b: 终止']) { |input|
        if input == 'b'
          Output.puts_cancel_message
          should_continue = false
        end
      } if error_repos.length > 0
      return should_continue
    end

    # mgit sync -u
    # 同步仓库:E | url不一致
    def setup_config_url_repos()
      prepare_repo_category

      warning_repos = []
      missing_repos = []
      Workspace.config.light_repos.each { |light_repo|
        if !light_repo.mgit_excluded
          repo, _ = Repo.generate_softly(Workspace.root, light_repo)
          if !repo.nil?
            original_url = repo.status_checker.default_url
            target_url = light_repo.url
            warning_repos.push(light_repo) if !Utils.url_consist?(original_url, target_url)
          else
            missing_repos.push(light_repo)
          end
        end
      }

      Output.puts_remind_block(missing_repos.map { |repo| repo.name }, "以上仓库本地缺失,无法校验URL,请执行\"mgit sync -n\"重新下载后重试!") if missing_repos.length > 0

      if warning_repos.length > 0
        if Output.continue_with_interact_repos?(warning_repos.map { |repo| repo.name }, "以上仓库的当前URL(origin)和配置表指定URL不一致,建议\n     1. 执行\"mgit delete repo1 repo2...\"删除仓库.\n     2. 执行\"mgit sync -n\"重新下载。\n    继续强制设置可能导致仓库出错,是否继续?")
          @sync_repos = warning_repos
        else
          Output.puts_cancel_message
          exit
        end
      end
    end

    def check_valid_repos(repo_names)
      specified_repos = repo_names.map { |name| name.downcase }
      all_valid_repos = Workspace.config.light_repos.map { |light_repo| light_repo.name.downcase }
      error_repo_names = specified_repos - all_valid_repos
      valid_repo_names = specified_repos - error_repo_names
      valid_repos = Workspace.config.light_repos.select { |light_repo|
        valid_repo_names.include?(light_repo.name.downcase)
      }
      [valid_repos, error_repo_names]
    end


    def parse_repo_name(argv)
      return if argv.git_opts.nil?

      repos = argv.git_opts.split(' ')
      extra_opts = repos.select { |e| argv.is_option?(e) }
      Foundation.help!("输入非法参数:#{extra_opts.join(',')}。请通过\"mgit #{argv.cmd} --help\"查看用法。") if extra_opts.length > 0
      Foundation.help!("未输入查询仓库名!请使用这种形式查询:mgit info repo1 repo2 ...") if repos.length == 0
      repos.map { |e| e.downcase }
    end

    def enable_short_basic_option
      true
    end

    def self.description
      "根据配置表(从远端或本地)同步仓库到工作区,包括被锁定仓库,已经在工作的不作处理(默认不执行pull)。"
    end

    def self.usage
      "mgit sync [-a|-n|-c] [<repo>...] [-p] [-o] [-h]"
    end

  end

end


================================================
FILE: lib/m-git/command/tag.rb
================================================
#coding=utf-8

module MGit

  # @!scope 类似 git tag
  #
  class Tag < BaseCommand

    def execute(argv)
      Workspace.check_branch_consistency

      Output.puts_start_cmd

      exec_repos = all_repos + locked_repos

      if argv.git_opts.empty?
        _, error_repos = Workspace.execute_git_cmd_with_repos(argv.cmd, argv.git_opts, exec_repos)
        Output.puts_succeed_cmd(argv.absolute_cmd) if error_repos.length == 0
        return
      end

      error_repos = {}
      no_tag_repos = []
      exec_repos.each { |repo|
        success, output = repo.execute_git_cmd(argv.cmd, argv.git_opts)
        if success
          tags = output.split("\n")
          if tags.length > 0
            puts Output.generate_title_block(repo.name) {
              Output.generate_table(tags, separator:"")
            }
          else
            no_tag_repos.push(repo.name)
          end
        else
          error_repos[repo.name] = output
        end
      }

      if no_tag_repos.length > 0
        puts "\n"
        Output.puts_remind_block(no_tag_repos, "以上仓库尚未创建tag!")
      end

      if error_repos.length > 0
        Workspace.show_error(error_repos)
      else
        Output.puts_succeed_cmd(argv.absolute_cmd)
      end

    end

    def enable_repo_selection
      true
    end

    def self.description
      "增删查或验证标签。增加标签示例:mgit tag -a 'v0.0.1' -m 'Tag description message'"
    end

    def self.usage
      "mgit tag [<git-tag-option>] [(--mrepo|--el-mrepo) <repo>...] [--help]"
    end

  end

end


================================================
FILE: lib/m-git/command_manager.rb
================================================

module MGit
  class CommandManager
    class << self
      # cmd generate
      #
      def commands
        @commands ||= {}
      end

      def register_command(cmd, cls)
        commands[cmd] = cls
      end

      def [](cmd)
        class_with_command(cmd)
      end

      def class_with_command(cmd)
        commands[cmd]
      end
    end
  end
end


================================================
FILE: lib/m-git/error.rb
================================================
#coding=utf-8

module MGit

  MGIT_ERROR_TYPE = {
    :config_name_error => 'config_name_error',
    :config_generate_error => 'config_generate_error'
  }.freeze

  class Error < StandardError
    attr_reader :msg
    attr_reader :type

    def initialize(msg, type:nil)
      @msg = msg
      @type = type
    end
  end

end


================================================
FILE: lib/m-git/foundation/constants.rb
================================================
#coding=utf-8

module MGit
  module Constants
    PROJECT_DIR = {
        :hooks          => '.mgit/hooks',
        :source_config  => '.mgit/source-config',
        :source_git     => '.mgit/source-git',
        :snapshot       => '.mgit/snapshot',
        :log_dir        => '.mgit/logs'
    }.freeze

    HOOK_NAME = {
        :pre_hook            => 'pre_hook.rb',
        :post_hook           => 'post_hook.rb',
        :manifest_hook       => 'manifest_hook.rb',
        :post_download_hook  => 'post_download_hook.rb',
        :pre_push_hook       => 'pre_push_hook.rb',
        :pre_exec_hook       => 'pre_exec_hook.rb'
    }.freeze

    MGIT_CONFIG_PATH = ".mgit/config.yml"

    CONFIG_FILE_NAME = {
        :manifest       => 'manifest.json',
        :manifest_cache => '.manifest_cache.json',
        :local_manifest => 'local_manifest.json'
    }.freeze

    # 全局配置
    CONFIG_KEY = {
        # [String] 包含仓库的文件夹相对.mgit目录路径,完整路径如:<.mgit所在路径>/dest/<repo_name>,与abs-dest同时指定时无效
        :dest           => 'dest',
        # [Boolean] 是否将所有仓库排除mgit管理
        :mgit_excluded  => 'mgit-excluded',
        # [String] 远程仓库根目录,完整URL:remote/remote_path
        :remote         => 'remote',
        # [String] 远程仓库相对目录,完整URL:remote/remote_path
        :repositories   => 'repositories',
        # [String] mgit版本
        :version        => 'version',
    }.freeze

    # 仓库配置
    REPO_CONFIG_KEY = {
        # [String] 仓库完整路径
        :abs_dest       => 'abs-dest',
        # [Boolean] 是否是配置仓库
        :config_repo    => 'config-repo',
        # [String] 包含仓库的文件夹相对.mgit目录路径,完整路径如:<.mgit所在路径>/dest/<repo_name>,与abs-dest同时指定时无效
        :dest           => 'dest',
        # [Boolean] 是否是占位仓库,占位操作不会让mgit进行常规操作(隐含指定mgit_excluded为true),标记为占位的仓库组装器组装时不需要使用
        :dummy          => 'dummy',
        # [Json] 锁定状态,见REPO_CONFIG_LOCK_KEY
        :lock           => 'lock',
        # [Boolean] 是否排除mgit管理,被排除的仓库不会让mgit进行常规操作,若未标记dummy,则为不被mgit管理,但组装器需要的仓库
        :mgit_excluded  => 'mgit-excluded',
        # [String] 远程仓库根目录,完整URL:remote/remote_path
        :remote         => 'remote',
        # [String] 远程仓库相对目录,完整URL:remote/remote_path
        :remote_path    => 'remote-path'
    }.freeze

    # 必须全局字段
    REQUIRED_CONFIG_KEY = [
        CONFIG_KEY[:remote],
        CONFIG_KEY[:version],
        CONFIG_KEY[:dest],
        CONFIG_KEY[:repositories]
    ].freeze

    # 必须仓库配置字段
    REQUIRED_REPO_CONFIG_KEY = [
        REPO_CONFIG_KEY[:remote_path],
    ].freeze

    REPO_CONFIG_LOCK_KEY = {
        :branch         => 'branch',
        :commit_id      => 'commit-id',
        :tag            => 'tag'
    }.freeze

    SNAPSHOT_KEY = {
        :time_stamp     => 'time-stamp',
        :message        => 'message',
        :snapshot       => 'snapshot'
    }.freeze

    CONFIG_CACHE_KEY = {
        :hash           => 'hash',
        :cache          => 'cache'
    }.freeze

    # 定义字段用于调用git(shell)指令前export,便于仓库git hook区分来进行其余操作
    MGIT_EXPORT_INFO = {
        # 是否是MGit操作的git指令
        :MGIT_TRIGGERRED  =>  1,
        # 本次MGit操作的唯一标示
        :MGIT_OPT_ID      =>  SecureRandom.uuid
    }.freeze

    # 临时中央仓库名
    CENTRAL_REPO = "Central".freeze

    INIT_CACHE_DIR_NAME = "pmet_tini_tigm".freeze
  end
end


================================================
FILE: lib/m-git/foundation/dir.rb
================================================

class Dir

  # 检查传入路径是不是git仓库
  #
  # @param path [String] 仓库路径
  #
  def self.is_git_repo?(path)
    return false unless File.directory?(path)
    git_dir = File.join(path, '.git')
    File.directory?(git_dir)
  end

  def self.is_in_git_repo?(path)
    check_path = path
    result = is_git_repo?(check_path)
    while !result
      check_path = File.dirname(check_path)
      break if check_path == '/'
      result = is_git_repo?(check_path)
    end
    result
  end

end

================================================
FILE: lib/m-git/foundation/duration_recorder.rb
================================================
#coding=utf-8

module MGit

  # 计时器
  #
  class DurationRecorder

    DEFAULT_DURATION_KEY = 'default_duration_key'

    module Status
      # 停止态
      IDLE = 0
      # 暂停态
      PAUSE = 1
      # 计时中
      RUNNING = 2
    end

    include Status

    @@status = {}
    @@duration = {}
    @@time_stamp = {}
    @@lock = Mutex.new

    def self.start(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      mutex_exec(use_lock) {
        status = IDLE if @@status[duration_key].nil?
        if status == IDLE
          @@duration[duration_key] = 0.0
          @@time_stamp[duration_key] = Time.new.to_f
          @@status[duration_key] = RUNNING
        else
          puts '需要停止计时后重新开始计时'
        end
      }
    end

    def self.pause(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      mutex_exec(use_lock) {
        if @@status[duration_key] == RUNNING
          current = Time.new.to_f
          @@duration[duration_key] += (current - @@time_stamp[duration_key])
          @@time_stamp.delete(duration_key)
          @@status[duration_key] = PAUSE
        end
      }
    end

    def self.resume(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      mutex_exec(use_lock) {
        if @@status[duration_key] == PAUSE
          @@time_stamp[duration_key] = Time.new.to_f
          @@status[duration_key] = RUNNING
        end
      }
    end

    def self.end(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      mutex_exec(use_lock) {
        if @@status[duration_key] != IDLE
          current = Time.new.to_f
          @@duration[duration_key] += (current - @@time_stamp[duration_key])
          @@time_stamp.delete(duration_key)
          @@status[duration_key] = IDLE
          return @@duration[duration_key]
        else
          puts '没有需要停止的计时'
        end
      }
    end

    private

    # 多线程执行保护
    #
    # @param use_lock [Boolean] 执行是否加锁
    #
    def self.mutex_exec(use_lock)
      if use_lock
        @@lock.lock
        yield if block_given?
        @@lock.unlock
      else
        yield if block_given?
      end
    end

  end

end


================================================
FILE: lib/m-git/foundation/git_message_parser.rb
================================================

module MGit
  # Git interact message parse with git-server
  #
  class GitMessageParser

    def initialize(url)
      @url = url
    end

    # @return [String] error message
    # @return [nil] none error
    #
    def parse_fetch_msg(input)
      parse_pull_msg(input)
    end

    # @return [String] error message
    # @return [nil] none error
    #
    def parse_pull_msg(input)
      __default_parse_msg(input)
    end

    # @return [String] error message
    # @return [nil] none error
    #
    def parse_push_msg(input)
      __default_parse_msg(input)
    end

    # @return [String] codereview的url地址
    # 默认无解析
    def parse_code_review_url(input)
      nil
    end

    def __default_parse_msg(msg)
      return if msg.nil? || msg.empty?

      key_word = 'error:'
      error_line = msg.split("\n").find {|line|
        # 解析 "error: failed to push some refs..."
        line.include?(key_word)
      }
      msg if error_line
    end
  end

end


================================================
FILE: lib/m-git/foundation/lock.rb
================================================

module MGit
  class Lock
    class << self

      # @!scope 互斥执行锁
      # @example
      # mutex_exec do
      #   exec..
      # end
      def mutex_exec
        @mutex = Mutex.new if @mutex.nil?
        @mutex.lock
        yield if block_given?
        @mutex.unlock
      end

      # @!scope 互斥显示锁
      # @example
      # mutex_puts do
      #   exec..
      # end
      def mutex_puts
        @mutex_puts = Mutex.new if @mutex_puts.nil?
        @mutex_puts.lock
        yield if block_given?
        @mutex_puts.unlock
      end

    end
  end
end


================================================
FILE: lib/m-git/foundation/loger.rb
================================================
#coding=utf-8

require 'logger'
require 'fileutils'
module MGit

  # 日志模块配置
  class Loger

    MGIT_LOG_FILE_NAME = 'mgit.log'

    #
    # 配置Log模块
    #
    # @param root [String] 工程根目录
    #
    def self.config(root)
      # 是否开启log
      begin
        log_enable = MGit::MGitConfig.query_with_key(root, :logenable)
      rescue MGitClass::Error => e
        log_enable = TRUE
      end
      MGit::Loger.set_log_enable(log_enable)

      # 配置log路径
      log_dir = File.join(root, MGit::Constants::PROJECT_DIR[:log_dir])
      FileUtils.mkdir_p(log_dir) if !Dir.exist?(log_dir)
      file_path = File.join(log_dir, MGIT_LOG_FILE_NAME)
      MGit::Loger.set_log_file(file_path)

      # 配置log的level
      begin
        log_level = MGit::MGitConfig.query_with_key(root, :loglevel)
      rescue MGitClass::Error => e
        log_level = 1
      end
      MGit::Loger.set_log_level(log_level)

    end

  end


  # 日志模块
  class Loger
    DEFAULT_SHIFT_SIZE = 1048576  #每个文件1M
    DEFAULT_SHIFT_AGE = 10 # 保留10个文件
    DEFAULT_LOG_FILE = "./mgit.log"

    #
    # 设置mgit log是否开启 默认开启
    #
    # @param log_enable [Boolean] 是否开启log日志
    #
    def self.set_log_enable(log_enable)
      @log_enable = log_enable
    end

    #
    # 设置mgit log的打印等级
    #
    # @param level [Enumerable]: Logger::DEBUG | Logger::INFO | Logger::ERROR | Logger::FATAL
    #
    def self.set_log_level(level)
      self.logger.level = level
    end

    #
    # 设置mgit log的文件名, 默认路径在工作区的 .mgit/logs/mgit.log
    #
    def self.set_log_file(file_path)
      @log_file = file_path
    end

    #
    # 打印 DEBUG类型的log
    #
    def self.debug(message)
      self.logger.debug(message) if @log_enable
    end

    #
    # 打印 DEBUG类型的log
    #
    def self.info(message)
      self.logger.info(message) if @log_enable
    end

    #
    # 打印 WARN类型的log
    #
    def self.warn(message)
      self.logger.warn(message) if @log_enable
    end

    #
    # 打印 ERROR类型的log
    #
    def self.error(message)
      self.logger.error(message) if @log_enable
    end

    #
    # 打印 FATAL类型的log
    #
    def self.fatal(message)
      self.logger.fatal(message) if @log_enable
    end

    private
    def self.logger
      unless @logger
        @log_enable ||= TRUE
        @log_level ||= Logger::INFO
        @log_file ||= DEFAULT_LOG_FILE
        @logger = Logger.new(@log_file, shift_age = DEFAULT_SHIFT_AGE, shift_size = DEFAULT_SHIFT_SIZE)
        @logger.level = @log_level
        @logger.datetime_format = '%Y-%m-%d %H:%M:%S'
        @logger.formatter = proc do | severity, datetime, progname, msg|
          "#{datetime} - #{severity} - : #{msg}\n"
        end
      end
      @logger
    end

  end

end


================================================
FILE: lib/m-git/foundation/mgit_config.rb
================================================
#coding=utf-8

require_relative 'utils'

module MGit
  class MGitConfig

    CONFIG_KEY = {
        :managegit  => {
            :info     => '是否将.git实体托管给MGit,托管后仓库内的.git目录是软链 (true/false)',
            :type     => 'Boolean',
            :default  => true
        },
        :maxconcurrentcount => {
            :info     => 'MGit操作的最大并发数? (Integer)',
            :type     => 'Integer',
            :default  => MGit::Utils.logical_cpu_num
        },
        :syncworkspace => {
            :info     => '是否按照配置表同步工作区? (true/false)',
            :type     => 'Boolean',
            :default  => true
        },
        :savecache => {
            :info     => '同步工作区时回收的仓库是否保留到缓存中? (true/false)',
            :type     => 'Boolean',
            :default  => false
        },
        :logenable => {
            :info     => '是否开启日志打印,操作日志会收集到.mgit/log目录下? (true/false)',
            :type     => 'Boolean',
            :default  => true
        },
        :loglevel => {
            :info     => '日志的打印级别, DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, FATAL = 4',
            :type     => 'Integer',
            :default  => 1
        }
    }

    class << self

      # 查询配置
      #
      # @param root [String] 工程根目录
      #
      # @raise [MGit::Error] 异常错误
      #
      def query(root)
        config, error = __load_file(root)
        if !error.nil?
          raise Error.new(error)
          return
        elsif block_given?
          # 如果文件存在但无内容,此时读取到的数据类型是FalseClass,此处统一规范化
          config = {} if !config.is_a?(Hash)
          yield(config)
        end
      end

      # Description of #query_with_key
      #
      # @param root [String] 工程根目录
      #
      # @param key_symbol [Symbol] 符号key值
      #
      # @return [Object] 配置值
      #
      # @raise [MGit::Error] 异常错误
      def query_with_key(root, key_symbol)
        query(root) { |config|
          if !config[key_symbol.to_s].nil?
            return config[key_symbol.to_s]
          elsif !CONFIG_KEY[key_symbol].nil?
            return CONFIG_KEY[key_symbol][:default]
          else
            return nil
          end
        }
      end

      # 更新配置
      #
      # @param root [String] 工程根目录
      #
      # @raise [MGit::Error] 异常错误
      #
      def update(root)
        # 加载配置
        config, error = __load_file(root)
        if !error.nil?
          raise Error.new(error)
          return
        end

        # 如果文件存在但无内容,此时读取到的数据类型是FalseClass,此处统一规范化
        config = {} if !config.is_a?(Hash)

        # 更新
        yield(config) if block_given?

        # 更新完后校验格式
        if !config.is_a?(Hash)
          raise Error.new("工具配置更新数据格式错误,更新失败!")
          return
        end

        # 写回配置
        error = write_to_file(root, config)
        if !error.nil?
          raise Error.new(error)
          return
        end
      end

      # 列出所有配置
      #
      # @param root [String] 工程根目录
      #
      def dump_config(root)
        query(root) { |config|
          CONFIG_KEY.each_with_index { |(key, value_dict), index|
            line = "#{Output.blue_message("[#{value_dict[:info]}]")}\n#{key.to_s}: "
            set_value = config[key.to_s]
            if !set_value.nil?
              line += "#{set_value}\n\n"
            else
              line += "#{value_dict[:default]}\n\n"
            end
            puts line
          }
        }
      end

      # 验证一个value的值是否符合key的类型
      #
      # @param root [String] 工程根目录
      #
      # @param key [String] 字符串key值
      #
      # @param value [String] 字符串格式的值
      #
      # @return [Object] key值对应的合法类型value值
      #
      def to_suitable_value_for_key(root, key, value)
        return unless CONFIG_KEY.keys.include?(key.to_sym)
        return nil if !value.is_a?(String)

        key_dict = CONFIG_KEY[key.to_sym]
        set_value = nil

        # 是否是数字
        if key_dict[:type] == 'Integer' && value.to_i.to_s == value
          set_value = value.to_i

          # 是否是true
        elsif key_dict[:type] == 'Boolean' && value.to_s.downcase == 'true'
          set_value = true

          # 是否是false
        elsif key_dict[:type] == 'Boolean' && value.to_s.downcase == 'false'
          set_value = false

          # 是否是其他字符串
        elsif key_dict[:type] == 'String' && value.is_a?(String)
          set_value = value
        end

        set_value
      end

      private

      # 加载mgit配置文件
      #
      # @param root [String] 工程根目录
      #
      # @return [(Hash, Boolean)] (配置内容,错误信息)
      #
      def __load_file(root)
        config_path = File.join(root, Constants::MGIT_CONFIG_PATH)
        if File.exists?(config_path)
          begin
            return YAML.load_file(config_path), nil
          rescue => e
            return nil, "工具配置文件\"#{File.basename(config_path)}\"读取失败,原因:\n#{e.message}"
          end
        end

        [nil, nil]
      end

      # 将配置写回文件
      #
      # @param root [String] 工程根目录
      #
      # @param content [Hash] 新配置
      #
      # @return [String] 错误信息
      #
      def write_to_file(root, content)
        config_path = File.join(root, Constants::MGIT_CONFIG_PATH)
        begin
          FileUtils.rm_f(config_path) if File.exist?(config_path)

          dir_name = File.dirname(config_path)
          FileUtils.mkdir_p(dir_name) if !Dir.exist?(dir_name)

          file = File.new(config_path, 'w')
          if !file.nil?
            file.write(content.to_yaml)
            file.close
          end
          return nil
        rescue => e
          return "工具配置文件\"#{File.basename(config_path)}\"写入失败,原因:\n#{e.message}"
        end
      end
    end

  end
end


================================================
FILE: lib/m-git/foundation/operation_progress_manager.rb
================================================
#coding=utf-8

module MGit
  # 本类用于缓存/加载操作中间态
  class OperationProgressContext

    # 现场信息关键字段
    CONTEXT_CMD = 'cmd'
    CONTEXT_OPTS = 'opts'
    CONTEXT_BRANCH = 'branch'
    CONTEXT_REPOS = 'repos'
    CONTEXT_OTHER = 'other'

    attr_accessor :type     # String: 本次进入中间态的操作类型,可自定义,如'merge_in_progress','rebase_in_progress'等。该字段用于索引缓存的中间态信息,需要唯一。
    attr_accessor :cmd      # String: 本次执行指令,如'merge','checkout'等
    attr_accessor :opts     # String: 本次执行参数,如'--no-ff','--ff'等
    attr_accessor :branch   # String: 仓库当前分支
    attr_accessor :repos    # [String]: 本次执行哪些仓库的名称数组
    attr_accessor :other    # Object: 其他信息,可自定义

    def initialize(type)
      self.type = type
      self.other = {}
    end

    # 将中间态对象序列化为Hash
    def serialize
      return {
          CONTEXT_CMD         => self.cmd,
          CONTEXT_OPTS        => self.opts,
          CONTEXT_BRANCH      => self.branch,
          CONTEXT_REPOS       => self.repos,
          CONTEXT_OTHER       => self.other
      }
    end

    # 反序列化
    #
    # @param dict [Hash] 中间态Hash
    #
    def deserialize(dict)
      self.cmd = dict[CONTEXT_CMD]
      self.opts = dict[CONTEXT_OPTS]
      self.branch = dict[CONTEXT_BRANCH]
      self.repos = dict[CONTEXT_REPOS]
      self.other = dict[CONTEXT_OTHER]
    end

    # 校验中间态是否合法,仓库可缺省,若缺省则表示所有仓库
    #
    # @return [Boolean] 是否合法
    #
    def validate?
      return !self.cmd.nil? && !self.opts.nil? && !self.branch.nil?
    end
  end

  class OperationProgressManager

    PROGRESS_TYPE = {
        :merge  =>  'merge_in_progress',
        :rebase =>  'rebase_in_progress',
        :pull   =>  'pull_in_progress'
    }.freeze

    class << self

      # 进入中间态
      #
      # @param root [String] 多仓库根目录
      #
      # @param context [OperationProgressContext] 中间态对象
      #
      def trap_into_progress(root, context)
        begin
          MGitConfig.update(root) { |config|
            config[context.type] = context.serialize
          }
        rescue Error => e
          Output.puts_fail_message(e.msg)
        end
      end

      # 删除中间态
      #
      # @param root [String] 多仓库根目录
      #
      # @param type [String] 自定义Key值,用于索引中间态信息
      #
      def remove_progress(root, type)
        begin
          MGitConfig.update(root) { |config|
            config.delete(type)
          }
        rescue Error => e
          Output.puts_fail_message(e.msg)
        end
      end

      # 是否处于中间态中
      def is_in_progress?(root, type)
        is_in_progress = false
        begin
          MGitConfig.query(root) { |config|
            is_in_progress = !config[type].nil?
 
Download .txt
gitextract_p54qz427/

├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── docs/
│   ├── commands/
│   │   └── commands.md
│   ├── config/
│   │   ├── config-env.md
│   │   └── manifest.md
│   ├── design/
│   │   ├── design.md
│   │   ├── principle.md
│   │   └── workspace.md
│   ├── extension/
│   │   ├── cmd_ext.md
│   │   ├── hooks.md
│   │   ├── plugins.md
│   │   └── submodule.md
│   ├── faq/
│   │   └── faq.md
│   ├── references.md
│   └── use/
│       ├── common-commands.md
│       └── how-to-start.md
├── lib/
│   ├── m-git/
│   │   ├── argv/
│   │   │   ├── opt.rb
│   │   │   ├── opt_list.rb
│   │   │   └── parser.rb
│   │   ├── argv.rb
│   │   ├── base_command.rb
│   │   ├── command/
│   │   │   ├── add.rb
│   │   │   ├── branch.rb
│   │   │   ├── checkout.rb
│   │   │   ├── clean.rb
│   │   │   ├── commit.rb
│   │   │   ├── config.rb
│   │   │   ├── delete.rb
│   │   │   ├── fetch.rb
│   │   │   ├── forall.rb
│   │   │   ├── info.rb
│   │   │   ├── init.rb
│   │   │   ├── log.rb
│   │   │   ├── merge.rb
│   │   │   ├── pull.rb
│   │   │   ├── push.rb
│   │   │   ├── rebase.rb
│   │   │   ├── reset.rb
│   │   │   ├── self.rb
│   │   │   ├── stash.rb
│   │   │   ├── status.rb
│   │   │   ├── sync.rb
│   │   │   └── tag.rb
│   │   ├── command_manager.rb
│   │   ├── error.rb
│   │   ├── foundation/
│   │   │   ├── constants.rb
│   │   │   ├── dir.rb
│   │   │   ├── duration_recorder.rb
│   │   │   ├── git_message_parser.rb
│   │   │   ├── lock.rb
│   │   │   ├── loger.rb
│   │   │   ├── mgit_config.rb
│   │   │   ├── operation_progress_manager.rb
│   │   │   ├── timer.rb
│   │   │   └── utils.rb
│   │   ├── foundation.rb
│   │   ├── hooks_manager.rb
│   │   ├── manifest/
│   │   │   ├── cache_manager.rb
│   │   │   ├── internal.rb
│   │   │   ├── light_repo.rb
│   │   │   ├── light_repo_generator.rb
│   │   │   ├── linter.rb
│   │   │   └── manifest.sample
│   │   ├── manifest.rb
│   │   ├── open_api/
│   │   │   └── script_download_info.rb
│   │   ├── open_api.rb
│   │   ├── output/
│   │   │   └── output.rb
│   │   ├── plugin/
│   │   │   ├── completion.bash
│   │   │   ├── completion.zsh
│   │   │   └── init.sh
│   │   ├── plugin_manager.rb
│   │   ├── repo/
│   │   │   ├── status.rb
│   │   │   └── sync_helper.rb
│   │   ├── repo.rb
│   │   ├── template/
│   │   │   ├── local_manifest.rb
│   │   │   ├── manifest_hook.rb
│   │   │   ├── post_download_hook.rb
│   │   │   ├── post_hook.rb
│   │   │   ├── pre_exec_hook.rb
│   │   │   ├── pre_hook.rb
│   │   │   └── pre_push_hook.rb
│   │   ├── template.rb
│   │   ├── version.rb
│   │   ├── workspace/
│   │   │   ├── path_helper.rb
│   │   │   └── workspace_helper.rb
│   │   └── workspace.rb
│   └── m-git.rb
├── m-git.gemspec
├── manifest.json
├── mgit
└── test/
    ├── argv/
    │   ├── test_opt.rb
    │   ├── test_opt_list.rb
    │   └── test_parser.rb
    ├── foundation/
    │   ├── test_dir.rb
    │   ├── test_mgit_config.rb
    │   └── test_utils.rb
    ├── manifest/
    │   └── test_light_repo.rb
    ├── test_argv.rb
    ├── test_foundation.rb
    ├── test_helper.rb
    ├── test_manifest.rb
    ├── test_open_api.rb
    ├── test_plugin_manager.rb
    └── test_workspace.rb
Download .txt
SYMBOL INDEX (674 symbols across 68 files)

FILE: lib/m-git.rb
  type MGit (line 19) | module MGit
    function run (line 47) | def run(raw_argv)
  type MGit (line 41) | module MGit
    function run (line 47) | def run(raw_argv)

FILE: lib/m-git/argv.rb
  type MGit (line 3) | module MGit
    class ARGV (line 6) | class ARGV
      method initialize (line 30) | def initialize(cmd, pure_opts, absolute_cmd, raw_opts)
      method register_opts (line 38) | def register_opts(opts)
      method resolve! (line 44) | def resolve!
      method update_opt (line 59) | def update_opt(key, value, priority:nil, info:nil)
      method opt (line 76) | def opt(key)
      method info (line 81) | def info(key)
      method git_opts (line 92) | def git_opts(raw: true)
      method enumerate_valid_opts (line 102) | def enumerate_valid_opts
      method is_option? (line 110) | def is_option?(opt_str)
      method show_detail (line 115) | def show_detail
      method show_info (line 126) | def show_info
      method __resolve_git_opts (line 137) | def __resolve_git_opts
      method __resolve_raw_opts (line 150) | def __resolve_raw_opts

FILE: lib/m-git/argv/opt.rb
  type MGit (line 3) | module MGit
    class ARGV (line 5) | class ARGV
      class Opt (line 10) | class Opt
        method initialize (line 21) | def initialize(key, default:nil, short_key:nil, priority:-1, info:...
        method empty? (line 27) | def empty?
        method validate? (line 31) | def validate?

FILE: lib/m-git/argv/opt_list.rb
  type MGit (line 3) | module MGit
    class ARGV (line 4) | class ARGV
      class OptList (line 6) | class OptList
        method initialize (line 13) | def initialize(opts)
        method valid_opts (line 17) | def valid_opts
        method opt (line 27) | def opt(key)
        method did_set_opt? (line 36) | def did_set_opt?(key)
        method registered_opt (line 48) | def registered_opt(key)
        method did_register_opt? (line 56) | def did_register_opt?(key)
        method opts_ordered_by_priority (line 64) | def opts_ordered_by_priority

FILE: lib/m-git/argv/parser.rb
  type MGit (line 2) | module MGit
    class ARGV (line 3) | class ARGV
      type Parser (line 4) | module Parser
        function parse (line 15) | def self.parse(argv)

FILE: lib/m-git/base_command.rb
  type MGit (line 6) | module MGit
    class BaseCommand (line 7) | class BaseCommand
      method inherited (line 9) | def self.inherited(sub_klass)
      method cmd (line 14) | def self.cmd
      method initialize (line 45) | def initialize(argv)
      method run (line 55) | def run
      method all_repos (line 67) | def all_repos(except_config:false)
      method locked_repos (line 71) | def locked_repos
      method exec_light_repos (line 75) | def exec_light_repos
      method generate_config_repo (line 79) | def generate_config_repo
      method __config_repo_filter (line 84) | def __config_repo_filter
      method setup_argv (line 93) | def setup_argv(argv)
      method process_highest_priority_option (line 104) | def process_highest_priority_option(argv)
      method pre_exec (line 115) | def pre_exec
      method post_exec (line 136) | def post_exec
      method enable_auto_execution (line 145) | def enable_auto_execution
      method enable_repo_selection (line 150) | def enable_repo_selection
      method enable_short_basic_option (line 155) | def enable_short_basic_option
      method enable_lock_operation (line 160) | def enable_lock_operation
      method include_lock_by_default (line 165) | def include_lock_by_default
      method enable_continue_operation (line 170) | def enable_continue_operation
      method enable_abort_operation (line 175) | def enable_abort_operation
      method did_interrupt (line 180) | def did_interrupt
      method description (line 184) | def self.description
      method usage (line 188) | def self.usage
      method execute (line 194) | def execute(argv)
      method options (line 202) | def options
      method revise_option_value (line 246) | def revise_option_value(opt)
      method validate (line 251) | def validate(argv)
      method mgit_try_to_continue? (line 255) | def mgit_try_to_continue?
      method mgit_try_to_abort? (line 259) | def mgit_try_to_abort?
      method usage (line 264) | def usage(argv)

FILE: lib/m-git/command/add.rb
  type MGit (line 3) | module MGit
    class Add (line 9) | class Add < BaseCommand
      method execute (line 13) | def execute(argv)
      method enable_repo_selection (line 23) | def enable_repo_selection
      method description (line 29) | def self.description
      method usage (line 35) | def self.usage

FILE: lib/m-git/command/branch.rb
  type MGit (line 3) | module MGit
    class Branch (line 9) | class Branch < BaseCommand
      method options (line 15) | def options
      method execute (line 21) | def execute(argv)
      method extcute_as_common (line 34) | def extcute_as_common(argv)
      method show_compact_branches (line 54) | def show_compact_branches(argv)
      method show_branches_for_repos (line 61) | def show_branches_for_repos(repos, locked)
      method enable_repo_selection (line 77) | def enable_repo_selection
      method description (line 81) | def self.description
      method usage (line 85) | def self.usage

FILE: lib/m-git/command/checkout.rb
  type MGit (line 3) | module MGit
    class Checkout (line 9) | class Checkout < BaseCommand
      method execute (line 11) | def execute(argv)
      method checkout_config_repo (line 60) | def checkout_config_repo(cmd, opts, repo)
      method remind_config_repo_fail (line 85) | def remind_config_repo_fail(msg)
      method enable_repo_selection (line 92) | def enable_repo_selection
      method description (line 96) | def self.description
      method usage (line 100) | def self.usage

FILE: lib/m-git/command/clean.rb
  type MGit (line 3) | module MGit
    class Clean (line 9) | class Clean < BaseCommand
      method execute (line 11) | def execute(argv)
      method validate (line 42) | def validate(argv)
      method enable_repo_selection (line 46) | def enable_repo_selection
      method enable_short_basic_option (line 50) | def enable_short_basic_option
      method description (line 54) | def self.description
      method usage (line 58) | def self.usage

FILE: lib/m-git/command/commit.rb
  type MGit (line 3) | module MGit
    class Commit (line 9) | class Commit < BaseCommand
      method validate (line 11) | def validate(argv)
      method execute (line 21) | def execute(argv)
      method enable_repo_selection (line 70) | def enable_repo_selection
      method description (line 74) | def self.description
      method usage (line 78) | def self.usage

FILE: lib/m-git/command/config.rb
  type MGit (line 3) | module MGit
    class Config (line 9) | class Config < BaseCommand
      method options (line 24) | def options
      method validate (line 48) | def validate(argv)
      method pre_exec (line 57) | def pre_exec
      method post_exec (line 66) | def post_exec
      method execute (line 73) | def execute(argv)
      method update_mgit_config (line 102) | def update_mgit_config(config_path)
      method update_local_config (line 124) | def update_local_config(config_path)
      method create_local_config (line 138) | def create_local_config(dir)
      method dump_config (line 157) | def dump_config
      method set_config (line 166) | def set_config(key_value_arr)
      method enable_short_basic_option (line 190) | def enable_short_basic_option
      method description (line 194) | def self.description
      method usage (line 198) | def self.usage

FILE: lib/m-git/command/delete.rb
  type MGit (line 3) | module MGit
    class Delete (line 9) | class Delete < BaseCommand
      method pre_exec (line 12) | def pre_exec
      method post_exec (line 21) | def post_exec
      method execute (line 28) | def execute(argv)
      method parse_repo_name (line 75) | def parse_repo_name(argv)
      method enable_short_basic_option (line 85) | def enable_short_basic_option
      method description (line 89) | def self.description
      method usage (line 93) | def self.usage

FILE: lib/m-git/command/fetch.rb
  type MGit (line 3) | module MGit
    class Fetch (line 9) | class Fetch < BaseCommand
      method execute (line 10) | def execute(argv)
      method enable_repo_selection (line 19) | def enable_repo_selection
      method description (line 23) | def self.description
      method usage (line 27) | def self.usage

FILE: lib/m-git/command/forall.rb
  type MGit (line 3) | module MGit
    class Forall (line 9) | class Forall < BaseCommand
      method options (line 18) | def options
      method validate (line 31) | def validate(argv)
      method execute (line 36) | def execute(argv)
      method enable_repo_selection (line 63) | def enable_repo_selection
      method enable_short_basic_option (line 67) | def enable_short_basic_option
      method description (line 71) | def self.description
      method usage (line 75) | def self.usage

FILE: lib/m-git/command/info.rb
  type MGit (line 3) | module MGit
    class Info (line 7) | class Info < BaseCommand
      method execute (line 9) | def execute(argv)
      method parse_repo_name (line 39) | def parse_repo_name(argv)
      method calculate_size (line 48) | def calculate_size(repo)
      method check_stash (line 54) | def check_stash(repo)
      method enable_short_basic_option (line 60) | def enable_short_basic_option
      method description (line 64) | def self.description
      method usage (line 68) | def self.usage

FILE: lib/m-git/command/init.rb
  type MGit (line 3) | module MGit
    class Init (line 7) | class Init < BaseCommand
      method options (line 22) | def options
      method validate (line 47) | def validate(argv)
      method pre_exec (line 60) | def pre_exec
      method post_exec (line 72) | def post_exec
      method execute (line 81) | def execute(argv)
      method init_dir (line 101) | def init_dir
      method write_content (line 114) | def write_content(path, content)
      method setup_hooks (line 122) | def setup_hooks(pre_hook_path, post_hook_path, manifest_hook_path, p...
      method initial_multi_repo_root (line 129) | def initial_multi_repo_root
      method setup_local_config (line 141) | def setup_local_config(path, config_repo, use_local)
      method clone_with_git_url (line 152) | def clone_with_git_url(git_url, branch, use_local, clone_all)
      method clone_with_local_config (line 203) | def clone_with_local_config(config_path, clone_all)
      method clone_sub_repos (line 222) | def clone_sub_repos(repo_list, default_branch)
      method link_config (line 278) | def link_config(config_path, config_cache_path, root)
      method terminate! (line 289) | def terminate!(msg)
      method move_project_to_root (line 297) | def move_project_to_root
      method finish_init (line 306) | def finish_init(config)
      method enable_short_basic_option (line 312) | def enable_short_basic_option
      method description (line 316) | def self.description
      method usage (line 320) | def self.usage

FILE: lib/m-git/command/log.rb
  type MGit (line 3) | module MGit
    class Log (line 7) | class Log < BaseCommand
      method options (line 14) | def options
      method revise_option_value (line 20) | def revise_option_value(opt)
      method execute (line 24) | def execute(argv)
      method parse_repo_name (line 46) | def parse_repo_name(argv)
      method is_integer? (line 56) | def is_integer?(string)
      method enable_short_basic_option (line 60) | def enable_short_basic_option
      method description (line 64) | def self.description
      method usage (line 68) | def self.usage

FILE: lib/m-git/command/merge.rb
  type MGit (line 3) | module MGit
    class Merge (line 7) | class Merge < BaseCommand
      method options (line 23) | def options
      method __progress_type (line 29) | def __progress_type
      method execute (line 33) | def execute(argv)
      method merge_config_repo (line 175) | def merge_config_repo(cmd, opts, repo, auto_update)
      method continue_execute (line 190) | def continue_execute(cmd, opts, repo, check_point, auto_update)
      method update_config_repo (line 222) | def update_config_repo(repo, context, auto)
      method exec_config_repo (line 239) | def exec_config_repo(repo, cmd, opts)
      method refresh_config (line 253) | def refresh_config(repo, context, auto)
      method update_subrepos (line 280) | def update_subrepos(subrepos, context, auto)
      method refresh_context (line 297) | def refresh_context(context)
      method remind_config_repo_fail (line 303) | def remind_config_repo_fail(msg)
      method do_abort (line 313) | def do_abort(argv)
      method show_progress_error (line 334) | def show_progress_error(summary, detail)
      method enable_repo_selection (line 346) | def enable_repo_selection
      method enable_continue_operation (line 350) | def enable_continue_operation
      method description (line 354) | def self.description
      method usage (line 358) | def self.usage

FILE: lib/m-git/command/pull.rb
  type MGit (line 3) | module MGit
    class Pull (line 7) | class Pull < BaseCommand
      method options (line 13) | def options
      method __progress_type (line 19) | def __progress_type
      method execute (line 23) | def execute(argv)
      method verbose_pull (line 32) | def verbose_pull(argv)
      method simple_pull (line 202) | def simple_pull(argv)
      method pull_config_repo (line 222) | def pull_config_repo(cmd, opts, repo)
      method remind_config_repo_fail (line 285) | def remind_config_repo_fail(msg)
      method do_abort (line 295) | def do_abort(argv)
      method refresh_context (line 316) | def refresh_context(context)
      method show_progress_error (line 322) | def show_progress_error(summary, detail)
      method enable_repo_selection (line 334) | def enable_repo_selection
      method enable_auto_execution (line 338) | def enable_auto_execution
      method include_lock_by_default (line 342) | def include_lock_by_default
      method enable_continue_operation (line 346) | def enable_continue_operation
      method enable_abort_operation (line 350) | def enable_abort_operation
      method description (line 354) | def self.description
      method usage (line 358) | def self.usage

FILE: lib/m-git/command/push.rb
  type MGit (line 3) | module MGit
    class Push (line 9) | class Push < BaseCommand
      method options (line 23) | def options
      method __setup_option_value (line 36) | def __setup_option_value(argv)
      method execute (line 47) | def execute(argv)
      method __execute_push (line 233) | def __execute_push(argv, do_repos)
      method __process_push_result (line 262) | def __process_push_result(repo, stdout, stderr, status)
      method __parse_cmd_and_opt (line 283) | def __parse_cmd_and_opt(repo)
      method enable_repo_selection (line 297) | def enable_repo_selection
      method description (line 301) | def self.description
      method usage (line 305) | def self.usage

FILE: lib/m-git/command/rebase.rb
  type MGit (line 3) | module MGit
    class Rebase (line 7) | class Rebase < BaseCommand
      method options (line 23) | def options
      method execute (line 29) | def execute(argv)
      method rebase_config_repo (line 152) | def rebase_config_repo(cmd, opts, repo, auto_update)
      method continue_execute (line 166) | def continue_execute(cmd, opts, repo, check_point, auto_update)
      method update_config_repo (line 198) | def update_config_repo(repo, context, auto)
      method exec_config_repo (line 215) | def exec_config_repo(repo, cmd, opts)
      method refresh_config (line 229) | def refresh_config(repo, context, auto)
      method update_subrepos (line 257) | def update_subrepos(subrepos, context, auto)
      method refresh_context (line 274) | def refresh_context(context)
      method remind_config_repo_fail (line 280) | def remind_config_repo_fail(msg)
      method check_master_rebase (line 287) | def check_master_rebase(argv)
      method do_abort (line 294) | def do_abort(argv)
      method show_progress_error (line 312) | def show_progress_error(summary, detail)
      method enable_repo_selection (line 324) | def enable_repo_selection
      method enable_continue_operation (line 328) | def enable_continue_operation
      method description (line 332) | def self.description
      method usage (line 336) | def self.usage
      method __progress_type (line 342) | def __progress_type

FILE: lib/m-git/command/reset.rb
  type MGit (line 3) | module MGit
    class Reset (line 7) | class Reset < BaseCommand
      method execute (line 9) | def execute(argv)
      method enable_repo_selection (line 17) | def enable_repo_selection
      method description (line 21) | def self.description
      method usage (line 25) | def self.usage

FILE: lib/m-git/command/self.rb
  type MGit (line 3) | module MGit
    class Self (line 7) | class Self < BaseCommand
      method options (line 31) | def options
      method validate (line 41) | def validate(argv)
      method prepare (line 45) | def prepare
      method pre_exec (line 51) | def pre_exec
      method post_exec (line 54) | def post_exec
      method usage (line 57) | def usage(argv)
      method execute (line 62) | def execute(argv)
      method show_help (line 91) | def show_help(argv)
      method show_all_repos (line 130) | def show_all_repos(argv)
      method show_repo_size (line 164) | def show_repo_size
      method prepare_repos (line 188) | def prepare_repos(with_excluded:false)
      method show_workspace (line 204) | def show_workspace
      method show_version (line 213) | def show_version
      method enable_short_basic_option (line 217) | def enable_short_basic_option

FILE: lib/m-git/command/stash.rb
  type MGit (line 3) | module MGit
    class Stash (line 7) | class Stash < BaseCommand
      method options (line 17) | def options
      method validate (line 27) | def validate(argv)
      method execute (line 36) | def execute(argv)
      method do_clear (line 53) | def do_clear(argv)
      method do_stash_list (line 64) | def do_stash_list(argv)
      method do_stash_pop_apply (line 86) | def do_stash_pop_apply(argv, stash_name, action)
      method do_stash_push (line 115) | def do_stash_push(argv, stash_name)
      method repo_stash_list_msg (line 150) | def repo_stash_list_msg(repo)
      method stash_include_name (line 158) | def stash_include_name(stash_list, stash_name)
      method enable_repo_selection (line 175) | def enable_repo_selection
      method description (line 179) | def self.description
      method usage (line 183) | def self.usage

FILE: lib/m-git/command/status.rb
  type MGit (line 3) | module MGit
    class Status (line 7) | class Status < BaseCommand
      method execute (line 8) | def execute(argv)
      method validate (line 114) | def validate(argv)
      method enable_repo_selection (line 118) | def enable_repo_selection
      method enable_short_basic_option (line 122) | def enable_short_basic_option
      method description (line 126) | def self.description
      method usage (line 130) | def self.usage

FILE: lib/m-git/command/sync.rb
  type MGit (line 3) | module MGit
    class Sync (line 8) | class Sync < BaseCommand
      method pre_exec (line 24) | def pre_exec
      method post_exec (line 33) | def post_exec
      method options (line 40) | def options
      method validate (line 62) | def validate(argv)
      method execute (line 70) | def execute(argv)
      method prepare_repo_category (line 124) | def prepare_repo_category
      method setup_specified_repos (line 137) | def setup_specified_repos(argv)
      method setup_normal_reops (line 178) | def setup_normal_reops(argv)
      method setup_new_reops (line 199) | def setup_new_reops
      method setup_all_sync_reops (line 211) | def setup_all_sync_reops(argv)
      method setup_download_reops (line 227) | def setup_download_reops(repo_names)
      method setup_config_url_repos (line 261) | def setup_config_url_repos()
      method check_valid_repos (line 291) | def check_valid_repos(repo_names)
      method parse_repo_name (line 303) | def parse_repo_name(argv)
      method enable_short_basic_option (line 313) | def enable_short_basic_option
      method description (line 317) | def self.description
      method usage (line 321) | def self.usage

FILE: lib/m-git/command/tag.rb
  type MGit (line 3) | module MGit
    class Tag (line 7) | class Tag < BaseCommand
      method execute (line 9) | def execute(argv)
      method enable_repo_selection (line 53) | def enable_repo_selection
      method description (line 57) | def self.description
      method usage (line 61) | def self.usage

FILE: lib/m-git/command_manager.rb
  type MGit (line 2) | module MGit
    class CommandManager (line 3) | class CommandManager
      method commands (line 7) | def commands
      method register_command (line 11) | def register_command(cmd, cls)
      method [] (line 15) | def [](cmd)
      method class_with_command (line 19) | def class_with_command(cmd)

FILE: lib/m-git/error.rb
  type MGit (line 3) | module MGit
    class Error (line 10) | class Error < StandardError
      method initialize (line 14) | def initialize(msg, type:nil)

FILE: lib/m-git/foundation.rb
  type MGit (line 14) | module MGit
    type Foundation (line 15) | module Foundation
      function help! (line 19) | def help!(msg, title:nil)

FILE: lib/m-git/foundation/constants.rb
  type MGit (line 3) | module MGit
    type Constants (line 4) | module Constants

FILE: lib/m-git/foundation/dir.rb
  class Dir (line 2) | class Dir
    method is_git_repo? (line 8) | def self.is_git_repo?(path)
    method is_in_git_repo? (line 14) | def self.is_in_git_repo?(path)

FILE: lib/m-git/foundation/duration_recorder.rb
  type MGit (line 3) | module MGit
    class DurationRecorder (line 7) | class DurationRecorder
      type Status (line 11) | module Status
      method start (line 27) | def self.start(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      method pause (line 40) | def self.pause(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      method resume (line 51) | def self.resume(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      method end (line 60) | def self.end(duration_key:DEFAULT_DURATION_KEY, use_lock:false)
      method mutex_exec (line 80) | def self.mutex_exec(use_lock)

FILE: lib/m-git/foundation/git_message_parser.rb
  type MGit (line 2) | module MGit
    class GitMessageParser (line 5) | class GitMessageParser
      method initialize (line 7) | def initialize(url)
      method parse_fetch_msg (line 14) | def parse_fetch_msg(input)
      method parse_pull_msg (line 21) | def parse_pull_msg(input)
      method parse_push_msg (line 28) | def parse_push_msg(input)
      method parse_code_review_url (line 34) | def parse_code_review_url(input)
      method __default_parse_msg (line 38) | def __default_parse_msg(msg)

FILE: lib/m-git/foundation/lock.rb
  type MGit (line 2) | module MGit
    class Lock (line 3) | class Lock
      method mutex_exec (line 11) | def mutex_exec
      method mutex_puts (line 23) | def mutex_puts

FILE: lib/m-git/foundation/loger.rb
  type MGit (line 5) | module MGit
    class Loger (line 8) | class Loger
      method config (line 17) | def self.config(root)
      method set_log_enable (line 56) | def self.set_log_enable(log_enable)
      method set_log_level (line 65) | def self.set_log_level(level)
      method set_log_file (line 72) | def self.set_log_file(file_path)
      method debug (line 79) | def self.debug(message)
      method info (line 86) | def self.info(message)
      method warn (line 93) | def self.warn(message)
      method error (line 100) | def self.error(message)
      method fatal (line 107) | def self.fatal(message)
      method logger (line 112) | def self.logger
    class Loger (line 46) | class Loger
      method config (line 17) | def self.config(root)
      method set_log_enable (line 56) | def self.set_log_enable(log_enable)
      method set_log_level (line 65) | def self.set_log_level(level)
      method set_log_file (line 72) | def self.set_log_file(file_path)
      method debug (line 79) | def self.debug(message)
      method info (line 86) | def self.info(message)
      method warn (line 93) | def self.warn(message)
      method error (line 100) | def self.error(message)
      method fatal (line 107) | def self.fatal(message)
      method logger (line 112) | def self.logger

FILE: lib/m-git/foundation/mgit_config.rb
  type MGit (line 5) | module MGit
    class MGitConfig (line 6) | class MGitConfig
      method query (line 49) | def query(root)
      method query_with_key (line 70) | def query_with_key(root, key_symbol)
      method update (line 88) | def update(root)
      method dump_config (line 120) | def dump_config(root)
      method to_suitable_value_for_key (line 145) | def to_suitable_value_for_key(root, key, value)
      method __load_file (line 180) | def __load_file(root)
      method write_to_file (line 201) | def write_to_file(root, content)

FILE: lib/m-git/foundation/operation_progress_manager.rb
  type MGit (line 3) | module MGit
    class OperationProgressContext (line 5) | class OperationProgressContext
      method initialize (line 21) | def initialize(type)
      method serialize (line 27) | def serialize
      method deserialize (line 41) | def deserialize(dict)
      method validate? (line 53) | def validate?
    class OperationProgressManager (line 58) | class OperationProgressManager
      method trap_into_progress (line 74) | def trap_into_progress(root, context)
      method remove_progress (line 90) | def remove_progress(root, type)
      method is_in_progress? (line 101) | def is_in_progress?(root, type)
      method load_context (line 121) | def load_context(root, type)

FILE: lib/m-git/foundation/timer.rb
  type MGit (line 3) | module MGit
    class Timer (line 5) | class Timer
      method start (line 19) | def start(repo_name, use_lock:false)
      method stop (line 33) | def stop(repo_name, use_lock:false)
      method show_time_consuming_repos (line 47) | def show_time_consuming_repos(threshold:5)
      method mutex_exec (line 61) | def mutex_exec(use_lock)

FILE: lib/m-git/foundation/utils.rb
  type MGit (line 3) | module MGit
    type Utils (line 4) | module Utils
      function logical_cpu_num (line 7) | def logical_cpu_num
      function execute_shell_cmd (line 23) | def execute_shell_cmd(cmd)
      function change_dir (line 37) | def change_dir(dir)
      function execute_under_dir (line 47) | def execute_under_dir(dir)
      function relative_dir (line 62) | def relative_dir(dir_a, dir_b, realpath: true)
      function expand_path (line 78) | def expand_path(path, base:nil)
      function generate_init_cache_path (line 92) | def generate_init_cache_path(root)
      function link (line 103) | def link(target_path, link_path)
      function show_clone_info (line 112) | def show_clone_info(root, missing_light_repos)
      function branch_exist_on_remote? (line 137) | def branch_exist_on_remote?(branch, git_url)
      function has_permission_of_remote? (line 149) | def has_permission_of_remote?(git_url)
      function link_git (line 168) | def link_git(source_dir, source_git_dir)
      function generate_git_store (line 191) | def generate_git_store(root, url)
      function sync_workspace (line 218) | def sync_workspace(root, config, recover_cache_if_cancelled:true)
      function pop_git_entity (line 285) | def pop_git_entity(root, config)
      function push_git_entity (line 292) | def push_git_entity(root, config)
      function url_consist? (line 307) | def url_consist?(url_a, url_b)
      function normalize_url (line 331) | def normalize_url(url)
      function safe_join (line 350) | def safe_join(path_a, path_b)

FILE: lib/m-git/hooks_manager.rb
  type MGit (line 2) | module MGit
    type HooksManager (line 3) | module HooksManager
      function execute_manifest_hook (line 14) | def execute_manifest_hook(strict_mode:true)
      function execute_mgit_pre_hook (line 29) | def execute_mgit_pre_hook(cmd, pure_opts)
      function execute_mgit_post_hook (line 36) | def execute_mgit_post_hook(cmd, pure_opts, light_repos)
      function execute_mgit_pre_exec_hook (line 45) | def execute_mgit_pre_exec_hook(cmd, pure_opts, light_repos)
      function execute_mgit_pre_push_hook (line 52) | def execute_mgit_pre_push_hook(cmd, pure_opts, light_repos)
      function execute_post_download_hook (line 70) | def execute_post_download_hook(repo_name, repo_path)
      function __execute_hook_file (line 84) | def __execute_hook_file(file_name, hook_class)

FILE: lib/m-git/manifest.rb
  type MGit (line 7) | module MGit
    class Manifest (line 11) | class Manifest
      method generate_light_repos (line 54) | def self.generate_light_repos(root, mgit_managed_only: false, only_e...
      method parse (line 89) | def self.parse(config_path, strict_mode:true)
      method simple_parse (line 103) | def self.simple_parse(config_content, strict_mode:true)
      method repo_list (line 120) | def repo_list(selection: nil, exclusion: nil, all:false)
      method global_config (line 141) | def global_config(key)
      method update_cache_with_content (line 154) | def update_cache_with_content(root, config_content)
      method update_previous_extra_light_repos (line 165) | def update_previous_extra_light_repos(root)
      method terminate! (line 170) | def terminate!(msg, type:nil)

FILE: lib/m-git/manifest/cache_manager.rb
  type MGit (line 2) | module MGit
    class Manifest (line 3) | class Manifest
      class CacheManager (line 4) | class CacheManager
        method load_path (line 10) | def load_path(cache_path)
        method save_to_cache (line 31) | def self.save_to_cache(cache_path, hash_sha1, hash_data)

FILE: lib/m-git/manifest/internal.rb
  type MGit (line 4) | module MGit
    class Manifest (line 5) | class Manifest
      type Internal (line 6) | module Internal
        function __setup (line 15) | def __setup(config_path, strict_mode)
        function __simple_setup (line 37) | def __simple_setup(config_content, strict_mode)
        function cache_manager (line 57) | def cache_manager
        function __load_config (line 64) | def __load_config(config_path, config_content: nil, local_config_p...
        function __load_cache (line 102) | def __load_cache(cache_path)
        function __parse_manifest_json (line 112) | def __parse_manifest_json(raw_string)
        function __parse_manifest (line 122) | def __parse_manifest(path)
        function __generate_light_repos (line 131) | def __generate_light_repos(config_hash)
        function __generate_extra_light_repos (line 140) | def __generate_extra_light_repos(current_hash, previous_hash)
        function __generate_hash_sha1 (line 154) | def __generate_hash_sha1(json_string)
        function __merge_manifest_hash (line 162) | def __merge_manifest_hash(base_hash, attach_hash)

FILE: lib/m-git/manifest/light_repo.rb
  type MGit (line 5) | module MGit
    class Manifest (line 6) | class Manifest
      method clone_url (line 51) | def clone_url(root, local_url:nil, clone_branch:nil)
      method abs_dest (line 71) | def abs_dest(root)
      method git_store_dir (line 81) | def git_store_dir(root)
      method cache_store_dir (line 103) | def cache_store_dir(root)

FILE: lib/m-git/manifest/light_repo_generator.rb
  type MGit (line 2) | module MGit
    class Manifest (line 3) | class Manifest
      class LightRepoGenerator (line 6) | class LightRepoGenerator
        method simple_init (line 18) | def self.simple_init(name, path, url)
        method light_repo_with (line 22) | def self.light_repo_with(name, config_json, parent_json)
        method __parse_path (line 56) | def __parse_path(repo_name, config_json, parent_json)
        method __parse_url (line 72) | def __parse_url(config_json, parent_json)

FILE: lib/m-git/manifest/linter.rb
  type MGit (line 2) | module MGit
    class Manifest (line 3) | class Manifest
      type Linter (line 4) | module Linter
        function lint_manifest_path (line 12) | def lint_manifest_path(path)
        function lint_local_manifest_path (line 32) | def lint_local_manifest_path(path)
        function lint_light_repos! (line 40) | def lint_light_repos!
        function lint_raw_json! (line 62) | def lint_raw_json!(dict)

FILE: lib/m-git/open_api.rb
  type MGit (line 5) | module MGit
    class OpenApi (line 8) | class OpenApi
      method sync_repos (line 28) | def sync_repos(config_content, download_root)
      method download_repos (line 72) | def download_repos(config_content, download_root, manage_git:true)
      method checkout (line 119) | def checkout(repo_name, repo_path, create_branch:false, branch:nil, ...
      method fetch (line 222) | def fetch(repo_name, repo_path)
      method pull (line 244) | def pull(repo_name, repo_path)
      method check_extra_repos (line 267) | def check_extra_repos(path_dict)
      method concurrent_enumerate (line 298) | def concurrent_enumerate(array, max_concurrent_count:5)
      method check_permission_batch (line 320) | def check_permission_batch(root, url_list)
      method check_permission (line 345) | def check_permission(url, root:nil)
      method validate_argv (line 365) | def validate_argv(method_name, args)
      method download (line 386) | def download(config, download_root, manage_git:true, sync_exist: false)

FILE: lib/m-git/open_api/script_download_info.rb
  type MGit (line 2) | module MGit
    class OpenApi (line 3) | class OpenApi
      type DownloadResult (line 5) | module DownloadResult
      class ScriptDownloadInfo (line 11) | class ScriptDownloadInfo
        method initialize (line 28) | def initialize(repo_name, repo_path, result, output, progress)

FILE: lib/m-git/output/output.rb
  type MGit (line 5) | module MGit
    type Output (line 6) | module Output
      function puts_cancel_message (line 11) | def puts_cancel_message
      function puts_succeed_cmd (line 15) | def puts_succeed_cmd(cmd)
      function puts_start_cmd (line 19) | def puts_start_cmd
      function puts_fail_cmd (line 23) | def puts_fail_cmd(cmd)
      function puts_nothing_to_do_cmd (line 27) | def puts_nothing_to_do_cmd
      function puts_success_message (line 31) | def puts_success_message(str)
      function puts_remind_message (line 36) | def puts_remind_message(str)
      function puts_terminate_message (line 41) | def puts_terminate_message(str, title:nil)
      function puts_fail_message (line 46) | def puts_fail_message(str)
      function puts_processing_message (line 51) | def puts_processing_message(str)
      function puts_fail_block (line 56) | def puts_fail_block(list, bottom_summary)
      function puts_remind_block (line 60) | def puts_remind_block(list, bottom_summary)
      function puts_processing_block (line 64) | def puts_processing_block(list, bottom_summary)
      function puts_fail_combined_block (line 68) | def puts_fail_combined_block(list_array, bottom_summary, title:nil)
      function puts_in_pager (line 72) | def puts_in_pager(output)
      function continue_with_user_remind? (line 82) | def continue_with_user_remind?(msg)
      function continue_with_interact_repos? (line 97) | def continue_with_interact_repos?(repos, msg)
      function continue_with_combined_interact_repos? (line 114) | def continue_with_combined_interact_repos?(repos_array, msg, title:nil)
      function interact_with_multi_selection_combined_repos (line 139) | def interact_with_multi_selection_combined_repos(list_array, msg, se...
      function processing_message (line 150) | def processing_message(str)
      function remind_message (line 154) | def remind_message(str)
      function success_message (line 158) | def success_message(str)
      function fail_message (line 162) | def fail_message(str)
      function terminate_message (line 166) | def terminate_message(str, title:nil)
      function green_message (line 175) | def green_message(str)
      function blue_message (line 180) | def blue_message(str)
      function red_message (line 185) | def red_message(str)
      function yellow_message (line 190) | def yellow_message(str)
      function info_title (line 194) | def info_title(str)
      function generate_fail_combined_block (line 200) | def generate_fail_combined_block(list_array, bottom_summary, title:nil)
      function generate_remind_block (line 207) | def generate_remind_block(list, bottom_summary)
      function generate_fail_block (line 213) | def generate_fail_block(list, bottom_summary)
      function generate_processing_block (line 219) | def generate_processing_block(list, bottom_summary)
      function generate_block (line 225) | def generate_block(list, bottom_summary)
      function generate_title_block (line 233) | def generate_title_block(title, has_separator:true)
      function generate_table_combination (line 252) | def generate_table_combination(list_array, title:nil, separator:'')
      function generate_table (line 300) | def generate_table(list, title:nil, separator:'', fixed_width:-1, hi...
      function calculate_table_info (line 368) | def calculate_table_info(list, head_separator, middle_separator, tai...
      function string_length_by_ascii (line 429) | def string_length_by_ascii(str)
      function max_meta_display_length_by_char (line 436) | def max_meta_display_length_by_char(str, max_meta_display_length_by_...
      function secure (line 442) | def secure(num)
      function update_progress (line 448) | def update_progress(totaltasks, finishtasks)

FILE: lib/m-git/plugin_manager.rb
  type MGit (line 2) | module MGit
    class PluginManager (line 3) | class PluginManager
      method setup (line 10) | def self.setup
      method loaded_plugins (line 41) | def self.loaded_plugins
      method load_local_plugin_dir (line 47) | def self.load_local_plugin_dir(plugin_prefix, plugins_dir)
      method load_local_plugins (line 58) | def self.load_local_plugins(plugin_prefix, plugin_root, with_name = ...
      method load_gem_plugins (line 70) | def self.load_gem_plugins(plugin_prefix)
      method safe_activate_gem (line 84) | def self.safe_activate_gem(spec, paths)
      method safe_activate_plugin_files (line 98) | def self.safe_activate_plugin_files(plugin_name, paths)

FILE: lib/m-git/repo.rb
  type MGit (line 6) | module MGit
    class Repo (line 8) | class Repo
      method initialize (line 22) | def initialize(name, path, config:nil)
      method generate_softly (line 31) | def self.generate_softly(root, config)
      method generate_strictly (line 42) | def self.generate_strictly(root, config)
      method check_git_dest (line 54) | def self.check_git_dest(root, config)
      method is_git_repo? (line 69) | def self.is_git_repo?(path)
      method execute (line 79) | def execute(abs_cmd)
      method execute_git_cmd (line 97) | def execute_git_cmd(cmd, opts)
      method git_cmd (line 102) | def git_cmd(cmd, opts)
      method url_consist? (line 123) | def url_consist?

FILE: lib/m-git/repo/status.rb
  type MGit (line 3) | module MGit
    class Repo (line 4) | class Repo
      class Status (line 5) | class Status
        method initialize (line 119) | def initialize(path)
        method status (line 125) | def status
        method message (line 130) | def message
        method dirty_zone (line 135) | def dirty_zone
        method branch_status (line 140) | def branch_status
        method branch_message (line 145) | def branch_message
        method is_in_merge_progress? (line 151) | def is_in_merge_progress?
        method is_in_rebase_progress? (line 159) | def is_in_rebase_progress?
        method is_ancestor_of_branch? (line 167) | def is_ancestor_of_branch?(branch)
        method tracking_branch (line 203) | def tracking_branch(branch, use_cache:false)
        method current_branch (line 224) | def current_branch(strict_mode:true, use_cache:false)
        method current_head (line 246) | def current_head(strict_mode:true)
        method local_branch_exist? (line 260) | def local_branch_exist?(branch)
        method remote_branch_exist? (line 265) | def remote_branch_exist?(branch)
        method commit_exist? (line 270) | def commit_exist?(commit)
        method default_url (line 278) | def default_url
        method refresh (line 290) | def refresh
        method git_dir (line 299) | def git_dir
        method work_tree (line 304) | def work_tree
        method has_branch? (line 314) | def has_branch?(branch, is_remote)
        method check_repo_status (line 328) | def check_repo_status
        method check_branch_status (line 345) | def check_branch_status
        method parse_change (line 406) | def parse_change(list)
        method convert_file_status (line 469) | def convert_file_status(type, status)

FILE: lib/m-git/repo/sync_helper.rb
  type MGit (line 3) | module MGit
    class Repo (line 5) | class Repo
      class SyncHelper (line 6) | class SyncHelper
        method sync_new_repo (line 20) | def sync_new_repo(light_repo, root, link_git:true)
        method sync_exist_repo (line 102) | def sync_exist_repo(repo, light_repo)
        method sync_lock_point (line 126) | def sync_lock_point(repo, light_repo)
        method sync_tag (line 147) | def sync_tag(repo, light_repo)
        method sync_commit_id (line 163) | def sync_commit_id(repo, light_repo)
        method sync_branch (line 180) | def sync_branch(repo, light_repo)
        method sync_remote_url (line 221) | def sync_remote_url(repo, light_repo)
        method local_bare_git_url (line 234) | def local_bare_git_url(path)
        method delete_legacy_repo (line 246) | def delete_legacy_repo(repo_abs_path)

FILE: lib/m-git/template/local_manifest.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template
      function default_template (line 6) | def default_template
      function local_config_template (line 15) | def local_config_template(config_repo_name)

FILE: lib/m-git/template/manifest_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/template/post_download_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/template/post_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/template/pre_exec_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/template/pre_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/template/pre_push_hook.rb
  type MGit (line 3) | module MGit
    type Template (line 4) | module Template

FILE: lib/m-git/version.rb
  type MGit (line 2) | module MGit

FILE: lib/m-git/workspace.rb
  type MGit (line 5) | module MGit
    class Workspace (line 6) | class Workspace
      method filter_config (line 20) | def filter_config
      method setup_multi_repo_root (line 25) | def setup_multi_repo_root(initial_root=nil)
      method setup_config (line 40) | def setup_config(strict_mode:true)
      method update_config (line 93) | def update_config(strict_mode:true)
      method setup_all_repos (line 147) | def setup_all_repos(strict_mode: true)
      method all_repos (line 192) | def all_repos(except_config:false)
      method locked_repos (line 202) | def locked_repos
      method update_all_repos (line 208) | def update_all_repos(update_repos_names)
      method exec_light_repos (line 221) | def exec_light_repos
      method multi_repo_root_path (line 246) | def multi_repo_root_path
      method generate_config_repo (line 262) | def generate_config_repo
      method concurrent_enumerate_with_progress_bar (line 273) | def concurrent_enumerate_with_progress_bar(light_repos, message, &ex...
      method concurrent_enumerate_with_progress_bar_pure (line 278) | def concurrent_enumerate_with_progress_bar_pure(light_repos, &exec_h...
      method sync_new_repos (line 290) | def sync_new_repos(repos)
      method sync_locked_repos (line 343) | def sync_locked_repos(repos)
      method check_branch_consistency (line 363) | def check_branch_consistency
      method has_diff_branch? (line 378) | def has_diff_branch?(repos)
      method guide_to_checkout_branch (line 402) | def guide_to_checkout_branch(new_repos, exist_repos, append_message:...
      method concurrent_enumerate (line 470) | def concurrent_enumerate(array)
      method serial_enumerate_with_progress (line 483) | def serial_enumerate_with_progress(array)
      method execute_git_cmd_with_repos (line 494) | def execute_git_cmd_with_repos(cmd, git_opts, repos)
      method execute_common_cmd_with_repos (line 520) | def execute_common_cmd_with_repos(abs_cmd, repos)
      method execute_common_cmd_with_repos_concurrent (line 543) | def execute_common_cmd_with_repos_concurrent(abs_cmd, repos)
      method show_error (line 570) | def show_error(error_repos, action:nil)
      method pre_fetch (line 588) | def pre_fetch
      method is_all_exec_sub_repos? (line 626) | def is_all_exec_sub_repos?(subrepos)
      method is_all_exec_sub_repos_by_name? (line 639) | def is_all_exec_sub_repos_by_name?(subrepo_names)

FILE: lib/m-git/workspace/path_helper.rb
  type MGit (line 2) | module MGit
    class Workspace (line 3) | class Workspace
      type PathHelper (line 7) | module PathHelper
        function config_file (line 11) | def config_file
        function hooks_dir (line 17) | def hooks_dir
        function snapshot_dir (line 23) | def snapshot_dir
        function source_config_dir (line 29) | def source_config_dir
        function source_git_dir (line 34) | def source_git_dir
        function manifest_path (line 39) | def manifest_path
        function local_manifest_path (line 44) | def local_manifest_path
        function cache_manifest_path (line 49) | def cache_manifest_path

FILE: lib/m-git/workspace/workspace_helper.rb
  type MGit (line 3) | module MGit
    class Workspace (line 4) | class Workspace
      type WorkspaceHelper (line 5) | module WorkspaceHelper
        function pop_git_entity (line 12) | def pop_git_entity(root, light_repo)
        function push_git_entity (line 40) | def push_git_entity(root, light_repo)
        function pop (line 64) | def pop(root, light_repo)
        function push (line 89) | def push(root, light_repo)
        function replace (line 117) | def replace(root, light_repo_a, light_repo_b)
        function sync_workspace (line 128) | def sync_workspace(root, light_repo)
        function invalid_move? (line 153) | def invalid_move?(from_path, to_path)

FILE: test/foundation/test_mgit_config.rb
  function __stub_config_env (line 14) | def __stub_config_env
  function __stub_write_config (line 20) | def __stub_write_config

FILE: test/test_helper.rb
  type MGitTest (line 14) | module MGitTest
    type FileProvider (line 15) | module FileProvider
Condensed preview — 109 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (420K chars).
[
  {
    "path": ".gitignore",
    "chars": 203,
    "preview": "/.bundle/\n/.yardoc\n/_yardoc/\n/coverage/\n/doc/\n/pkg/\n/spec/reports/\n/tmp/\n\n.bundle\nvendor/\n*.gem\n\n## IDE\n*.xccheckout\n.id"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 149,
    "preview": "## Release Note:\n\n### 2.5.5\n\n-【bug修复】修复 box config -m 命令 不可用问题,修改参数类型为String\n\n### 2.5.4\n\n- mgit增加inject注入文件功能\n- 增加manife"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3213,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "Gemfile",
    "chars": 168,
    "preview": "source \"https://gems.ruby-china.com\"\n\n# git_source(:github) {|repo_name| \"https://github.com/#{repo_name}\" }\n\n# Specify "
  },
  {
    "path": "LICENSE",
    "chars": 1099,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2020 Baidu, Inc. All Rights Reserved.\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "README.md",
    "chars": 2092,
    "preview": "# MGit\n\nMGit 是一款 Ruby 封装的基于 Git 的多仓库管理工具,可以高效的、同时的对多个 Git 仓库执行 Git 命令。\n适合于在多个仓库中进行关联开发的项目,提高 Git 操作的效率,避免逐个执行 Git 命令带来的误"
  },
  {
    "path": "Rakefile",
    "chars": 187,
    "preview": "\nrequire \"bundler/gem_tasks\"\ntask :default => :test\n\n\nrequire 'rake/testtask'\n\nRake::TestTask.new(:test) do |t|\n  t.libs"
  },
  {
    "path": "docs/commands/commands.md",
    "chars": 1022,
    "preview": "\n## Command-line Reference\n参考:https://guides.cocoapods.org/terminal/commands.html#pod_spec_lint\n\n\n# Brief Description Of"
  },
  {
    "path": "docs/config/config-env.md",
    "chars": 2179,
    "preview": "### config.yml 介绍\n\n config.yml 是 MGit 的配置文件;\n\n MGit的配置内容保存在多仓库目录下的`.mgit/config.yml`文件中,配置仅针对当前的多仓库生效;\n\n\n\n- 通过 `$ mgit c"
  },
  {
    "path": "docs/config/manifest.md",
    "chars": 6859,
    "preview": "### 1. manifest 配置介绍\n\nmanifest.json 配置表定义了多仓库管理所需的仓库信息,采用json格式,示例如下:\n\n```ruby\n{\n  \"remote\": \"https://github.com/baidu\","
  },
  {
    "path": "docs/design/design.md",
    "chars": 18,
    "preview": "## 设计篇(略)\n### 基本架构"
  },
  {
    "path": "docs/design/principle.md",
    "chars": 7,
    "preview": "## 基本原则"
  },
  {
    "path": "docs/design/workspace.md",
    "chars": 8,
    "preview": "### 工作区\n"
  },
  {
    "path": "docs/extension/cmd_ext.md",
    "chars": 934,
    "preview": "### 一、扩展指令\n\n\nMGit 加载插件有**2种方式**:\n\n- **ruby gem 包加载**\n当运行 MGit 命令时,从所有的`ruby-gems`包中查找并加载 MGit 的插件,查找判定条件 gem 包中存在如下文件:\n`"
  },
  {
    "path": "docs/extension/hooks.md",
    "chars": 1324,
    "preview": "### hook\n\n#### 一、什么是hook\n\nhook是在MGit 仓库中特定事件触发后被调用的脚本。hook最常见的使用场景包括根据仓库状态改变项目环境、接入持续集成工作流等。 由于脚本是可以完全定制,所以你可以用hook来自动化或"
  },
  {
    "path": "docs/extension/plugins.md",
    "chars": 39,
    "preview": "### 插件\n- 插件介绍,可以做什么\n- 如何扩展插件\n- 如何引用其他插件"
  },
  {
    "path": "docs/extension/submodule.md",
    "chars": 30,
    "preview": "### 用做子工具\n- 介绍其他工具如何调用mgit作为模块"
  },
  {
    "path": "docs/faq/faq.md",
    "chars": 8,
    "preview": "## F.A.Q"
  },
  {
    "path": "docs/references.md",
    "chars": 406,
    "preview": "<center style=\"font-family:verdana\">MGit相关文档</center>\n\n* [介绍](../README.md)\n\n* 使用篇\n    * [快速开始](use/how-to-start.md)\n   "
  },
  {
    "path": "docs/use/common-commands.md",
    "chars": 17379,
    "preview": "## 常用命令\n\n> 指令描述符说明: \n>\n> < > : 占位参数\n>\n> [  ]  : 可选组合\n>\n> (  )  :必选组合 \n>\n>  |     :  互斥参数 \n>\n> ···   :可重复指定前一个参数\n\n\n### mg"
  },
  {
    "path": "docs/use/how-to-start.md",
    "chars": 2432,
    "preview": "## 快速开始\n\n\n### 1、安装 MGit 工具\n\n环境要求:\n\n- 系统:支持 macOS、Ubuntu,暂时不支持 window-\n- Ruby版本: >= 2.3.7\n\n```ruby\n$ gem install m-git\n``"
  },
  {
    "path": "lib/m-git/argv/opt.rb",
    "chars": 1022,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  class ARGV\n    # 单个选项, 如‘--k1=v1 --k2 v2 v3 --flag’\n    # '--k1=v1'     key: '--k1', value"
  },
  {
    "path": "lib/m-git/argv/opt_list.rb",
    "chars": 1324,
    "preview": "#coding=utf-8\n\nmodule MGit\n  class ARGV\n    # 参数对象列表\n    class OptList\n\n      # [Array<ARGV::Opt>] 参数对象数组\n      attr_acc"
  },
  {
    "path": "lib/m-git/argv/parser.rb",
    "chars": 2068,
    "preview": "\nmodule MGit\n  class ARGV\n    module Parser\n\n      # @param [Array] argv\n      #\n      # @return [String, Array, Array]\n"
  },
  {
    "path": "lib/m-git/argv.rb",
    "chars": 4323,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # 参数处理类\n  class ARGV\n\n    require 'm-git/argv/opt'\n    require 'm-git/argv/opt_list'\n    r"
  },
  {
    "path": "lib/m-git/base_command.rb",
    "chars": 6948,
    "preview": "#coding=utf-8\n#\n\nrequire 'm-git/command_manager'\n\nmodule MGit\n  class BaseCommand\n\n    def self.inherited(sub_klass)\n   "
  },
  {
    "path": "lib/m-git/command/add.rb",
    "chars": 728,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] add\n  # follow git add\n  # eg: mgit add .\n  #\n  class Add < BaseComman"
  },
  {
    "path": "lib/m-git/command/branch.rb",
    "chars": 2111,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] branch\n  # follow git branch\n  # eg: mgit branch --compact\n  #\n  class"
  },
  {
    "path": "lib/m-git/command/checkout.rb",
    "chars": 3027,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] checkout\n  # follow git checkout\n  # eg: mgit checkout master\n  #\n  cl"
  },
  {
    "path": "lib/m-git/command/clean.rb",
    "chars": 1743,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] clean 清除所有仓库中工作区的变更\n  # follow git combinatorial command\n  # eg: git a"
  },
  {
    "path": "lib/m-git/command/commit.rb",
    "chars": 2456,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] commit\n  # follow git commit\n  # eg: mgit commit -m 'Just for fun'\n  #"
  },
  {
    "path": "lib/m-git/command/config.rb",
    "chars": 7053,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] config 配置 .mgit/config.yml 文件信息\n  #\n  # eg: mgit config -s key 'value'"
  },
  {
    "path": "lib/m-git/command/delete.rb",
    "chars": 2991,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] delete 删除某个仓库的`所有`文件,包括工作区、暂存区和版本库\n  #\n  # eg: mgit delete subA\n  #\n  "
  },
  {
    "path": "lib/m-git/command/fetch.rb",
    "chars": 647,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] fetch\n  # follow git fetch\n  # eg: mgit fetch\n  #\n  class Fetch < Base"
  },
  {
    "path": "lib/m-git/command/forall.rb",
    "chars": 2250,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope [command] forall 对管理的仓库依次执行shell命令\n  #\n  # eg: mgit forall -c 'git status'\n  #\n "
  },
  {
    "path": "lib/m-git/command/info.rb",
    "chars": 2314,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 指定仓库的信息\n  #\n  class Info < BaseCommand\n\n    def execute(argv)\n      Output.puts_"
  },
  {
    "path": "lib/m-git/command/init.rb",
    "chars": 11804,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 初始化多仓库命令\n  #\n  class Init < BaseCommand\n\n    OPT_LIST = {\n      :git_source     "
  },
  {
    "path": "lib/m-git/command/log.rb",
    "chars": 1964,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 查询多仓库的日志\n  #\n  class Log < BaseCommand\n\n    OPT_LIST = {\n      :number    =>  '-"
  },
  {
    "path": "lib/m-git/command/merge.rb",
    "chars": 12924,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git merge\n  #\n  class Merge < BaseCommand\n\n    PROGRESS_STAGE = {\n      :new_"
  },
  {
    "path": "lib/m-git/command/pull.rb",
    "chars": 13281,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git pull\n  #\n  class Pull < BaseCommand\n\n    OPT_LIST = {\n      :no_check    "
  },
  {
    "path": "lib/m-git/command/push.rb",
    "chars": 10750,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git push\n  # 可自动生成 git gerrit 评审分支\n  # mgit push --gerrit\n  #\n  class Push < "
  },
  {
    "path": "lib/m-git/command/rebase.rb",
    "chars": 11859,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git rebase\n  #\n  class Rebase < BaseCommand\n\n    PROGRESS_STAGE = {\n      :ne"
  },
  {
    "path": "lib/m-git/command/reset.rb",
    "chars": 590,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git reset\n  #\n  class Reset < BaseCommand\n\n    def execute(argv)\n      Worksp"
  },
  {
    "path": "lib/m-git/command/self.rb",
    "chars": 7202,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # 该指令用于不带指令的输入:mgit --help,用于执行mgit的一级参数(如\"mgit --help\"的\"--help\")\n  #\n  class Self < BaseC"
  },
  {
    "path": "lib/m-git/command/stash.rb",
    "chars": 5963,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git stash,但是强制标记名称\n  #\n  class Stash < BaseCommand\n\n    OPT_LIST = {\n      :p"
  },
  {
    "path": "lib/m-git/command/status.rb",
    "chars": 3832,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git status\n  #\n  class Status < BaseCommand\n    def execute(argv)\n      Outpu"
  },
  {
    "path": "lib/m-git/command/sync.rb",
    "chars": 11102,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 同步所有管理的仓库到工作区,可能从远端拉取,可能从本地dump,所以sync后的工作区不一定是分支的最新节点\n  # 可通过 mgit sync --pull "
  },
  {
    "path": "lib/m-git/command/tag.rb",
    "chars": 1517,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # @!scope 类似 git tag\n  #\n  class Tag < BaseCommand\n\n    def execute(argv)\n      Workspace."
  },
  {
    "path": "lib/m-git/command_manager.rb",
    "chars": 359,
    "preview": "\nmodule MGit\n  class CommandManager\n    class << self\n      # cmd generate\n      #\n      def commands\n        @commands "
  },
  {
    "path": "lib/m-git/error.rb",
    "chars": 326,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  MGIT_ERROR_TYPE = {\n    :config_name_error => 'config_name_error',\n    :config_generate_er"
  },
  {
    "path": "lib/m-git/foundation/constants.rb",
    "chars": 3219,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Constants\n    PROJECT_DIR = {\n        :hooks          => '.mgit/hooks',\n        :sou"
  },
  {
    "path": "lib/m-git/foundation/dir.rb",
    "chars": 476,
    "preview": "\nclass Dir\n\n  # 检查传入路径是不是git仓库\n  #\n  # @param path [String] 仓库路径\n  #\n  def self.is_git_repo?(path)\n    return false unle"
  },
  {
    "path": "lib/m-git/foundation/duration_recorder.rb",
    "chars": 2076,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  # 计时器\n  #\n  class DurationRecorder\n\n    DEFAULT_DURATION_KEY = 'default_duration_key'\n\n   "
  },
  {
    "path": "lib/m-git/foundation/git_message_parser.rb",
    "chars": 962,
    "preview": "\nmodule MGit\n  # Git interact message parse with git-server\n  #\n  class GitMessageParser\n\n    def initialize(url)\n      "
  },
  {
    "path": "lib/m-git/foundation/lock.rb",
    "chars": 555,
    "preview": "\nmodule MGit\n  class Lock\n    class << self\n\n      # @!scope 互斥执行锁\n      # @example\n      # mutex_exec do\n      #   exec"
  },
  {
    "path": "lib/m-git/foundation/loger.rb",
    "chars": 2678,
    "preview": "#coding=utf-8\n\nrequire 'logger'\nrequire 'fileutils'\nmodule MGit\n\n  # 日志模块配置\n  class Loger\n\n    MGIT_LOG_FILE_NAME = 'mgi"
  },
  {
    "path": "lib/m-git/foundation/mgit_config.rb",
    "chars": 5579,
    "preview": "#coding=utf-8\n\nrequire_relative 'utils'\n\nmodule MGit\n  class MGitConfig\n\n    CONFIG_KEY = {\n        :managegit  => {\n   "
  },
  {
    "path": "lib/m-git/foundation/operation_progress_manager.rb",
    "chars": 3368,
    "preview": "#coding=utf-8\n\nmodule MGit\n  # 本类用于缓存/加载操作中间态\n  class OperationProgressContext\n\n    # 现场信息关键字段\n    CONTEXT_CMD = 'cmd'\n "
  },
  {
    "path": "lib/m-git/foundation/timer.rb",
    "chars": 1832,
    "preview": "#coding=utf-8\n\nmodule MGit\n  # 计时器,用于统计指令执行耗时\n  class Timer\n\n    @@time_stamp = {}\n    @@duration = {}\n    @@lock = Mute"
  },
  {
    "path": "lib/m-git/foundation/utils.rb",
    "chars": 10428,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Utils\n\n    class << self\n      def logical_cpu_num\n        if @logical_cpu_num.nil?\n"
  },
  {
    "path": "lib/m-git/foundation.rb",
    "chars": 591,
    "preview": "#coding=utf-8\n\nrequire 'm-git/foundation/constants'\nrequire 'm-git/foundation/dir'\nrequire 'm-git/foundation/git_message"
  },
  {
    "path": "lib/m-git/hooks_manager.rb",
    "chars": 3005,
    "preview": "\nmodule MGit\n  module HooksManager\n\n# --- 执行hook ---\n\n    class << self\n      # 获取配置表前执行的hook\n      #\n      # @param str"
  },
  {
    "path": "lib/m-git/manifest/cache_manager.rb",
    "chars": 1120,
    "preview": "\nmodule MGit\n  class Manifest\n    class CacheManager\n\n      attr_reader :path\n      attr_reader :hash_sha1\n      attr_re"
  },
  {
    "path": "lib/m-git/manifest/internal.rb",
    "chars": 5609,
    "preview": "\nrequire 'm-git/manifest/cache_manager'\n\nmodule MGit\n  class Manifest\n    module Internal\n\n\n      # 配置对象\n      #\n      #"
  },
  {
    "path": "lib/m-git/manifest/light_repo.rb",
    "chars": 2519,
    "preview": "#coding=utf-8\n\nrequire_relative 'light_repo_generator'\n\nmodule MGit\n  class Manifest\n    # @!scope manifest.json 配置的repo"
  },
  {
    "path": "lib/m-git/manifest/light_repo_generator.rb",
    "chars": 2931,
    "preview": "\nmodule MGit\n  class Manifest\n    # @!scope lightrepo生成器\n    #\n    class LightRepoGenerator\n\n      # 简单初始化,有写字段缺失,仅包含名字,"
  },
  {
    "path": "lib/m-git/manifest/linter.rb",
    "chars": 6006,
    "preview": "\nmodule MGit\n  class Manifest\n    module Linter\n\n      # 校验配置文件路径\n      #\n      # @param path [Stirng] 配置文件路径或包含配置文件的目录\n"
  },
  {
    "path": "lib/m-git/manifest/manifest.sample",
    "chars": 1832,
    "preview": "--- manifest.json ---\n{\n  \"remote\": \"https://github.com/baidu\",//远程仓库地址\n  \"version\":1,//配置文件版本\n  \"branch\": \"master\",//配置"
  },
  {
    "path": "lib/m-git/manifest.rb",
    "chars": 5057,
    "preview": "#coding=utf-8\n\nrequire 'm-git/manifest/light_repo'\nrequire 'm-git/manifest/linter'\nrequire 'm-git/manifest/internal'\n\nmo"
  },
  {
    "path": "lib/m-git/open_api/script_download_info.rb",
    "chars": 745,
    "preview": "#coding=utf-8\nmodule MGit\n  class OpenApi\n\n    module DownloadResult\n      SUCCESS = 0\n      FAIL = 1\n      EXIST = 2\n  "
  },
  {
    "path": "lib/m-git/open_api.rb",
    "chars": 14301,
    "preview": "#coding=utf-8\n\nrequire 'm-git/open_api/script_download_info'\n\nmodule MGit\n\n  # --- Ruby环境调用 ---\n  class OpenApi\n    clas"
  },
  {
    "path": "lib/m-git/output/output.rb",
    "chars": 14908,
    "preview": "#coding=utf-8\n\nrequire 'm-git/foundation'\n\nmodule MGit\n  module Output\n    class << self\n\n# --- 简单输出 ---\n\n      def puts"
  },
  {
    "path": "lib/m-git/plugin/completion.bash",
    "chars": 442,
    "preview": "_mgit_completion() {\n  COMPREPLY=()\n\n  local completions word argc\n  word=\"${COMP_WORDS[COMP_CWORD]}\"\n  argc=${#COMP_WOR"
  },
  {
    "path": "lib/m-git/plugin/completion.zsh",
    "chars": 450,
    "preview": "_mgit_completion() {\n\n  if [[ ${#words[@]} == 2 ]]; then\n    # Complete command\n    completions=\"$(mgit script --command"
  },
  {
    "path": "lib/m-git/plugin/init.sh",
    "chars": 227,
    "preview": "if [ -n \"$BASH_VERSION\" ]; then\n  plugin_root=\"$(dirname \"${BASH_SOURCE[0]}\")\"\n  source \"$plugin_root/completion.bash\"\n\n"
  },
  {
    "path": "lib/m-git/plugin_manager.rb",
    "chars": 3719,
    "preview": "\nmodule MGit\n  class PluginManager\n\n    # @!scope 加载插件\n    # 1. 先加载本地源码插件\n    # 2. 搜索加载gem插件\n    # 3. 处理加载注入的插件\n    #\n  "
  },
  {
    "path": "lib/m-git/repo/status.rb",
    "chars": 16058,
    "preview": "#coding=utf-8\n\nmodule MGit\n  class Repo\n    class Status\n\n      # https://git-scm.com/docs/git-status\n      # status格式:X"
  },
  {
    "path": "lib/m-git/repo/sync_helper.rb",
    "chars": 8353,
    "preview": "#coding=utf-8\n\nmodule MGit\n\n  class Repo\n    class SyncHelper\n\n      class << self\n\n        # 同步新仓库\n        #\n        # "
  },
  {
    "path": "lib/m-git/repo.rb",
    "chars": 3336,
    "preview": "#coding=utf-8\n\nrequire 'm-git/repo/status'\nrequire 'm-git/repo/sync_helper'\n\nmodule MGit\n\n  class Repo\n\n    # 仓库名\n    at"
  },
  {
    "path": "lib/m-git/template/local_manifest.rb",
    "chars": 466,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    module_function\n    def default_template\n      return \"{\n  \\\"#{Constant"
  },
  {
    "path": "lib/m-git/template/manifest_hook.rb",
    "chars": 427,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    MANIFEST_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTemplate\n\n  class "
  },
  {
    "path": "lib/m-git/template/post_download_hook.rb",
    "chars": 363,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    POST_DOWNLOAD_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTemplate\n\n  c"
  },
  {
    "path": "lib/m-git/template/post_hook.rb",
    "chars": 462,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    POST_CUSTOMIZED_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTemplate\n\n "
  },
  {
    "path": "lib/m-git/template/pre_exec_hook.rb",
    "chars": 469,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    PRE_CUSTOMIZED_EXEC_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTemplat"
  },
  {
    "path": "lib/m-git/template/pre_hook.rb",
    "chars": 370,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n    PRE_CUSTOMIZED_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTemplate\n\n  "
  },
  {
    "path": "lib/m-git/template/pre_push_hook.rb",
    "chars": 472,
    "preview": "#coding=utf-8\n\nmodule MGit\n  module Template\n\n    PRE_CUSTOMIZED_PUSH_HOOK_TEMPLATE = '\n#coding=utf-8\n\nmodule MGitTempla"
  },
  {
    "path": "lib/m-git/template.rb",
    "chars": 284,
    "preview": "#coding=utf-8\n\nrequire 'm-git/template/local_manifest'\nrequire 'm-git/template/manifest_hook'\nrequire 'm-git/template/po"
  },
  {
    "path": "lib/m-git/version.rb",
    "chars": 46,
    "preview": "\nmodule MGit\n  VERSION = \"2.5.5\".freeze\nend\n\n\n"
  },
  {
    "path": "lib/m-git/workspace/path_helper.rb",
    "chars": 1255,
    "preview": "\nmodule MGit\n  class Workspace\n\n    # .mgit 目录下的文件路径\n    #\n    module PathHelper\n\n      # .mgit/config.yml\n      #\n     "
  },
  {
    "path": "lib/m-git/workspace/workspace_helper.rb",
    "chars": 4727,
    "preview": "#coding=utf-8\n\nmodule MGit\n  class Workspace\n    module WorkspaceHelper\n      # 弹出托管的.git实体\n      #\n      # @param root "
  },
  {
    "path": "lib/m-git/workspace.rb",
    "chars": 20197,
    "preview": "\nrequire 'm-git/workspace/workspace_helper'\nrequire 'm-git/workspace/path_helper'\n\nmodule MGit\n  class Workspace\n    # @"
  },
  {
    "path": "lib/m-git.rb",
    "chars": 1409,
    "preview": "\n$:.unshift __dir__\n\nrequire 'open3'\nrequire 'fileutils'\nrequire 'json'\nrequire 'digest'\nrequire 'pathname'\nrequire 'uri"
  },
  {
    "path": "m-git.gemspec",
    "chars": 2090,
    "preview": "\nlib = File.expand_path(\"../lib\", __FILE__)\n$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)\nrequire \"m-git/versi"
  },
  {
    "path": "manifest.json",
    "chars": 338,
    "preview": "{\n  \"remote\": \"https://gitee.com/baidu\",\n  \"version\":1,\n  \"mgit-excluded\": false,\n  \"dest\": \"Sources\",\n  \"repositories\":"
  },
  {
    "path": "mgit",
    "chars": 275,
    "preview": "#!/usr/bin/env ruby -W0\n#coding=utf-8\n\n#\n# if $PROGRAM_NAME == __FILE__\n#   ENV['BUNDLE_GEMFILE'] = File.expand_path('.."
  },
  {
    "path": "test/argv/test_opt.rb",
    "chars": 623,
    "preview": "\n\nrequire_relative '../test_helper'\n\n\ndescribe MGit::ARGV::Opt do\n\n  it \"opt key cant be nil\" do\n    _{MGit::ARGV::Opt.n"
  },
  {
    "path": "test/argv/test_opt_list.rb",
    "chars": 1291,
    "preview": "\n\n\nrequire_relative '../test_helper'\n\n\ndescribe MGit::ARGV::OptList do\n\n  let(:list) {\n    arr = []\n    opt = MGit::ARGV"
  },
  {
    "path": "test/argv/test_parser.rb",
    "chars": 1651,
    "preview": "\n\nrequire_relative '../test_helper'\n\n\ndescribe MGit::ARGV::Parser do\n\n  let(:input_argv) {\n    cmd = \"checkout -b branch"
  },
  {
    "path": "test/foundation/test_dir.rb",
    "chars": 832,
    "preview": "\n\nrequire_relative '../test_helper'\n\n\ndescribe Dir do\n\n  # before do\n  #   @git_dir  = MiniTest::Mock.new\n  # end\n\n  it "
  },
  {
    "path": "test/foundation/test_mgit_config.rb",
    "chars": 1932,
    "preview": "\n\nrequire_relative '../test_helper'\n\n\ndescribe MGit::MGitConfig do\n\n  STUB_CONFIG = {\n      'managegit' => true,\n      '"
  },
  {
    "path": "test/foundation/test_utils.rb",
    "chars": 2398,
    "preview": "require_relative '../test_helper'\n\ndescribe MGit::Utils do\n\n  it \"#change_dir(dir)\" do\n    check_dir = File.expand_path("
  },
  {
    "path": "test/manifest/test_light_repo.rb",
    "chars": 80,
    "preview": "\n\nrequire_relative '../test_helper'\n\ndescribe MGit::Manifest::LightRepo do\n\n\nend"
  },
  {
    "path": "test/test_argv.rb",
    "chars": 2742,
    "preview": "require_relative 'test_helper'\n\ndescribe MGit::ARGV do\n\n  let(:input_argv) {\n    cmd = \"checkout -b branch_name --mrepo "
  },
  {
    "path": "test/test_foundation.rb",
    "chars": 67,
    "preview": "\nrequire_relative 'test_helper'\n\ndescribe MGit::Foundation do\n\nend\n"
  },
  {
    "path": "test/test_helper.rb",
    "chars": 686,
    "preview": "\nrequire 'minitest/autorun'\nrequire 'minitest/reporters'\nMiniTest::Reporters.use! Minitest::Reporters::SpecReporter.new\n"
  },
  {
    "path": "test/test_manifest.rb",
    "chars": 1,
    "preview": "\n"
  },
  {
    "path": "test/test_open_api.rb",
    "chars": 68,
    "preview": "\n\n\nrequire_relative './test_helper'\n\n\ndescribe MGit::OpenApi do\n\nend"
  },
  {
    "path": "test/test_plugin_manager.rb",
    "chars": 76,
    "preview": "\n\n\nrequire_relative './test_helper'\n\n\ndescribe MGit::PluginManager do\n\n\n\nend"
  },
  {
    "path": "test/test_workspace.rb",
    "chars": 70,
    "preview": "\n\n\nrequire_relative './test_helper'\n\n\ndescribe MGit::Workspace do\n\nend"
  }
]

About this extraction

This page contains the full source code of the baidu/m-git GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 109 files (334.7 KB), approximately 103.2k tokens, and a symbol index with 674 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!