git的学习汇总--原创

tools/git/git-banner

这篇博文是自己在学习git过程中的思考总结。本文仅仅代表个人的看法,如有不妥地方还请本文文末留言。 😊

原文链接git的学习汇总--原创

GIT是什么

GIT是一个免费并且开源的分布式版本控制系统,能够高速有效的处理或小或大的项目。(以上的话是自己翻译github官网)

至今,自己用过了window系统的TortoiseSVN, mac系统的CornerStone,最近的大半年也在用GIT(主要管理自己的github项目)。比较下来,还是GIT优势比较明显。

GIT跨平台

GIT可以在不同的操作系统中使用。也许你注意到了,我在window上和mac系统上工作的时候是使用两个不同的svn。如果我在linux上工作会不会又是一个呢。

GIT是分布式版本控制系统,而svn是集中式版本控制系统

集中式版本控制系统是集中放在中央服务器上面的,而团队的人需要从中央服务器上面拉取最新的代码,然后进行开发,最后推送到中央服务器上面,就像串联的电路。而分布式版本控制系统没有中央服务器,团队的每个人的电脑就是一个完整的版本库,就好像并联的电路(自我理解)。

集中式版本控制系统必须联网才能工作,如果是在局域网内还好,带宽足够大,速度足够快,但是遇到网速慢的话,那心里就一万个羊驼🐑在蹦腾了。

集中式版本控制系统安全性比较低,如果中央系统崩溃了,那就有点悲催了。当然你不嫌麻烦,可以定期备份的啦。而分布式中央系统就比较安全,团队的每个成员的电脑就是一个完整的版本库。如果其中一个坏掉了,你可以从团队另外一个的人员电脑那里拷贝一份就行了。对了,GIT也会有一台中央的机子,主要是为了方便团队的交流,它是可以不存在的。

GIT安装

GIT支持不同的系统,看者可以在链接https://git-scm.com/downloads中,找到和自己电脑系统匹配的GIT版本,下载安装包后根据提示进行安装。当然,GIT还提供图形界面管理工具,看者也可以在链接中下载GUI Clients,如下图所示--

tools/git/git_install

根据提示安装完成后,要验证是否安装成功。看者可打开命令行工具,输入git --version命令,如果安装成功,控制台输出安装的版本号(当然,安装前就应该输入git --version查看是否安装了git),我这里安装的GIT版本是2.10.0

GIT配置

GIT在使用前,需要进行相关的配置。每台计算机上面只需要配置一次,程序升级的时候会保留配置信息。当然,看者可以在任何时候再次通过运行命令行来修改它们。

用户信息

设置GIT的用户名称和邮件地址,这个很重要,因为每个GIT的提交都会使用这些信息,并且它会写入到每一次的提交中。你可以在自己的仓库中使用git log,控制台上面显示的每次的提交都有Author字段,它的值就是用户名称 <邮件地址>。方便查看某次的提交的负责人是谁。

$ git config --global user.name "你的用户名"
$ git config --global user.email 你的邮箱地址 

⚠️ GIT一般和github配合使用,看者应该设置用户名称为你的github用户名。当然,还有和gitlab等配合使用...

⚠️ 如果配置中使用了--global选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情,GIT都会使用这些信息。但是,当你想针对特定项目使用不同的用户名称与邮件地址的时候,可以在那个仓库目录下运行不使用global选项的命令来配置。

检查配置信息

通过git config --list命令可以列出所有GIT能找到的配置。如下:(我的git版本为2.10.0)

...
user.name=reng99
user.email=1837895991@qq.com
color.ui=true
core.repositoryformatversion=0
core.filemode=true
core.bare=false
...

当然,你可以通过git config <key>来检查GIT的某一项配置。比如$ git config user.name

帮助中心

在使用GIT的时候,遇到问题寻求帮助的时候,可以运行git helpgit --helpgit命令来查看。在控制台上会展示相关的帮助啦。

usage:
    ...
start a working area (see also: git help tutorial)
    ...
work on the current change (see also: git help everyday)
    ...
examine the history and state (see alse: git help revisions)
    ...
grow,mark and tweak your common history
    ...
collaborate (see also: git help workflows)
    ...

更加详细的内容,请点击传送门

创建版本库

版本库又名仓库(repository),可以理解成一个目录,这个目录里面所有文件都可以被GIT管理起来,每个文件的修改、删除,GIT都能跟踪,以便任何时刻都能可以追踪历史,或者在将来某个时刻可以还原。

创建一个版本库,首先得选择一个存放目录的地方,我这里选择了桌面,并且创建一个空的目录。

$ cd desktop
$ mkdir -p learngit
$ cd learngit
$ pwd
/Users/reng/desktop/learngit

mkdir -p dirnanme是创建一个子目录,这里的-p确保目录的名称存在,如果目录不存在的就新建一个,如果你确定目录不存在,直接使用mkdir dirname就可以了。pwd(Print Working Directory)是显示当前目录的整个路径名。

然后,通过命令行git init,将创建的目录变成GIT可以管理的仓库:

$ git init 
Initialized empty Git repository in /Users/reng/Desktop/learngit/.git/

初始化好仓库后就可以愉快的玩耍了,但是,得先来了解下GIT整个工作流程先。

GIT工作流程

为了更好的学习,自己用Axure RP 8粗略的画了下流程图,如下--

tools/git/git_brief_process

本地仓库(repo)包含工作区和版本库,那么什么是工作区和版本库呢?基本的流程又是什么呢?

工作区和版本库

我们新建一个仓库,就像我们新建的learngit仓库,现在在里面添加一个文件README.md,用sublime打开learngit目录。此时会出现如下图的情况(当然你设置了其他东西例外)--

tools/git/working_version_area

如上图,出现的内容就是工作区( 电脑上能看到的此目录下的内容),这里工作区只有README.md一个文件。工作区有一个隐藏的目录.git,这个不算工作区,而是GIT的版本库。版本库又包括暂存区和GIT仓库。暂存区是一个文件,保存了下次将提交的文件列表信息,而GIT仓库目录是GIT用来保存项目的元数据和对象数据库的地方。这是GIT中最重要的部分,从其他计算机克隆仓库的时候,拷贝的就是这里的数据。当执行git add .或者git add path/to/filename的时候,文件从工作区转到暂存区;执行git commit -m"here is the message described the file you add"的时候,文件从缓存区添加到GIT仓库。

基本的工作流

基本的GIT工作流可以简单总结如下--

  1. 在工作区目录中修改文件
  2. 暂存区中暂存文件,将文件的快照放入暂存区域
  3. 提交更新,找到暂存区域的文件,将快照永久性存储到GIT仓库目录

时光机穿梭

到目前为止,在自己创建的本地仓库--learngit中已经初具形态了。进入learngit,执行ls,可看到目前仓库中已有的文件README.md。

$ cd desktop/learngit
$ ls
README.md
$ cat README.md
## content

上面展示了本地learngit内的相关的内容。运行下git status查看现在的状态。

$ git status
On branch master
nothing to commit, working tree clean

这时候会提示没有内容可以提交,工作区是干净的。因为我之前已经提交(git commit)过了。上面还提示了目前是位于主分支上面,GIT在初始化(git init)的时候会自动创建一个HEAD指针指向默认master分支,也只有一个分支,看者可以通过git branch查看。

现在,在README.md上添加一些内容。

## content

### first change

此刻再通过git status查看当前状态。

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

这时候显示出一堆的东西,告诉我们现在是位于主分支上面,然后告诉我们修改的文件啊,可以使用的命令进行下一步的操纵。那么我们来进行下一步的操作了,git add . 或者 git add README.md将修改的文件添加到暂存区域。

$ git add .
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README.md

对了,有时候需要在添加的之前(执行git add . 或者 git add path/to/filename)的时候,需要看下修改了哪些内容可以执行下git diff。那么,现在先回退到修改的前一个版本。

$ git reset HEAD README.md
Unstaged changes after reset:
M   README.md
$ git checkout -- README.md
$ ls
README.md
$ cat README.md
## content

回退正确,现在像上次那样添加内容### first change,然后执行命令git diff来查看更改的内容。

$ git diff
diff --git a/README.md b/README.md
index 75759ec..0bc52b9 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
-## content
\ No newline at end of file
+## content
+
+### first change
\ No newline at end of file

现在就显示了修改前的内容---前为修改前的内容,和修改后的内容--+前修改后的内容。查看完之后,觉得没有问题了,就可以进行添加(git add),提交(git commit)。当然,一般不常用git diff的,因为自己修改的东西自己心里总有点数吧,可能合作中团队的其他人需要查看文件前后的不同点就需要用到git diff啦。

版本回退

为了方便讲解下版本回退,我先将上面添加的### first change提交以下--git add . && git commit -m "add first change"。下面通过git log就可以查看自己提交的记录了。

$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date:   Sun Dec 17 17:11:53 2017 +0800

    init README.md
    
$ git add . && git commit -m "add first change"
[master 0ac49ba] add first change
 1 file changed, 3 insertions(+), 1 deletion(-)
 
$ git log
commit 0ac49bae6ab55df9c05d0770de347665a2568f31
Author: reng99 <1837895991@qq.com>
Date:   Mon Dec 18 15:26:06 2017 +0800

    add first change

commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date:   Sun Dec 17 17:11:53 2017 +0800

    init README.md

在上面中,自己先执行了git log来显示提交的日志,显示只有一条,然后执行了add和commit的命令,打印的内容是现实主分支、commit的id、commit的信息、多少个文件的更改、多少个插入以及多少个删除。之后再次执行git log打印日志,显示了两次提交。⚠️ 注意:当提交(commit)的次数较多之后,控制台会显示不下(最多现实4条)那么多的条数,可以通过按键盘的向上或向下键查看日志的内容,需要退出查看日志命令的话,在英文输入法的状态按下q,意思就是quit(退出)。

版本的回退就是改变HEAD指针的指向。通过git reset --hard HEAD^返回上一个版本,通过git reset --hard HEAD^^返回上上个版本...由此推论,往上100个版本的话就是100个^,当然,这样你数到明天也未必数得正确,所以写成git reset --hard HEAD~100。另外一种是,你知道提交的id,例如commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67的前7位就是commit的id(5c2639e),执行git reset --hard 5c2639e就回到此版本啦。

$ reng$ git reset --hard HEAD^
HEAD is now at 5c2639e init README.md
$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date:   Sun Dec 17 17:11:53 2017 +0800

    init README.md
$ ls
README.md
$ cat README.md
## content

现在你已经回到了最初的版本,这里演示的是通过HEAD,你也可以通过commit id来实现的。执行上面的代码后,README.md文件里面只有一### content文字内容,但是过了段时间后,你想恢复到原先的版本,通过git log命令行,控制台显示的以前的信息,通过它找不到回退前的commit id,怎么办?GIT提供一个git reflog显示提交的历史记录,在那里可以查看提交的id、HEAD的指针历史和操作的信息记录。下面演示回退到最新的版本(也就是commit -m "add first change")--

$ git log
commit 5c2639ee54bd7c8ef2cbf186f5dc4798e72a4a67
Author: reng99 <1837895991@qq.com>
Date:   Sun Dec 17 17:11:53 2017 +0800

    init README.md
$ git reflog
5c2639e HEAD@{0}: reset: moving to HEAD^
0ac49ba HEAD@{1}: commit: add first change
5c2639e HEAD@{2}: commit (initial): init README.md
$ git reset --hard 0ac49ba
HEAD is now at 0ac49ba add first change
$ ls
README.md
$ cat README.md
## content

### first 

现在又回到了最新的版本,又能够愉快的玩耍了。😊

管理修改

GIT比其他版本控制系统设计优秀,其中一点是--GIT跟踪并管理的是修改,而非文件。

下面在README.md内添加信息### second change。之后看下变化后的文件的状态和差异等。

$ ls
README.md
$ cat README.md
## content

### first change

#### second change
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git add README.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README.md

此时,对README.md进行第三次的修改,添加内容### third change

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

$ cat README.md
## content

### first change

#### second change

### third change
$ git commit -m "test file modify"
[master 18f86ba] test file modify
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

上面的演示流程是这样的第一次修改(#### second change) -> git add -> 第二次修改(### third change) -> git commit。但是最后查看状态的时候(git status),第二次的修改并没有被提交上去。因为GIT管理的是修改,当使用git add命令的时候,在工作区的第一次修改被放入暂存区,准备提交,但是在工作区的第二次修改并没有放入到暂存区,而git commit是将暂存区的修改提交到GIT仓库,所以第二次修改的内容是不会被提交的。这也是说明为什么可以多次添加(git add),一次提交(git commit)的原因了。

撤销修改

文件的撤销修改分成三种情况,一种是修改在工作区的内容,一种是修改在暂存区的内容,另一种是修改在GIT仓库的内容。也许会有看者说,不能修改在远程库中的内容吗?有啊,就是git add->git commit->git push将远程仓库的内容覆盖被,不过团队人在克隆远程库下来的时候,还是可以查看到你提交的错误内容的。我们现在只针对本地仓库的三种情况谈下自己的看法--

情况一:撤销工作区的内容

在管理修改中,自己的工作区还是没有提交,此时想放弃当前工作区的编辑内容执行git checkout -- file。接着上面的内容,我这里的工作区内有的内容是### third change,现在我要放弃第三次修改,只要执行git checkout -- README.md就可以了。

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
$ ls
README.md
$ cat README.md
## content

### first change

#### second change

### third change
$ git checkout -- README.md
$ cat README.md
## content

### first change

#### second change
$ git status
On branch master
nothing to commit, working tree clean

情况二:撤销暂存区的内容

当你不但改乱了工作区的某个文件的内容,还添加(git add)到了暂存区时,想丢弃修改,那么得分两步来撤销文件。先是通过git reset HEAD file,将暂存区的文件退回到工作区,然后通过git checkout -- file放弃修改改文件的内容。为了方便演示,我这里的暂存区没什么内容,所以添加内容### tentative content并将它添加到缓存区。之后,演示将缓存区的内容撤回--

$ cat README.md
## content

### first change

#### second change

### tentative content
$ git add .
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README.md

$ git reset HEAD README.md
Unstaged changes after reset:
M   README.md
$ git checkout -- README.md
$ cat README.md
## content

### first change

#### second change
$ git status
On branch master
nothing to commit, working tree clean

情况三:撤销GIT仓库的内容

如果你不仅添加(git add)了内容到暂存区并且提交(git commit)了内容到GIT仓库中了。你需要撤销上一次的内容,也就是要回退到上一个版本,执行git reset --hard HEAD^就可以啦,详细的内容查看版本回退。如下--

$ git status
On branch master
nothing to commit, working tree clean
$ cat README.md
## content

### first change

#### second change
$ git reset --hard HEAD^
HEAD is now at 0ac49ba add first change
$ cat READMEmd
## content

### first change

远程仓库

远程仓库的使用能够提高你和团队的工作效率,无论何时何地,团队的人员都可以在联网的情况下将代码进行拉取,修改和更新。因为我是使用github来管理项目的,所以我的远程仓库是放在github里面。这里默认看者已经安装了github,当然也可以用码云、gitlab等。

本地库添加到远程库

这点很容易,登录自己注册的github,如果打不开,请开下VPN。进入自己的首页(https://github.com/username),点击+号创建(new repository)一个名为learngit的仓库(注意哦⚠️ 名称是本地仓库已经初始化过的,我这里本地有个同名初始化的learngit仓库),其他的字段自选来填写。点击Create repository创建此远程仓库。紧接着就是进行本地仓库和远程仓库的关联啦,github很友好的提示了你怎么进行一个远程仓库的关联。

tools/git/related_github_step1

tools/git/related_github_step2

tools/git/related_github_step3

现在按照上图来关联下远程仓库。

$ git remote add origin https://github.com/reng99/learngit.git
$ git push -u origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 456 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

注意⚠️ 第一次向远程仓库(关联)push的时候是$ git push -u origin master,不能忽略-u,以后的push不用带-u。至此,打开你的github的相关的仓库就可以看到添加了README.md文件,我这里地址是https://github.com/reng99/learngit,因为我是使用markdown语法写的,控制台显示的内容和仓库的显示内容有所区别啦。<del>(⚠️ 后期我将learngit仓库删除啦,所以你访问链接是找不到这个仓库的,毕竟不想放一个没什么内容的仓库在我的github上)</del>。

远程库克隆到本地

从远程仓库克隆东西到本地同样很简单,只需要进入你想克隆的仓库,将仓库的url复制下来(当然你也可以复制window.location.href的内容),运行git clone address。现在我将本地桌面的learngit的仓库删除,然后从远程将learngit克隆到本地。

tools/git/git_clone_repository
$ cd desktop
$ rm -rf learngit
$ find learngit
find: learngit: No such file or directory
$ git clone https://github.com/reng99/learngit
Cloning into 'learngit'...
remote: Counting objects: 6, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 6 (delta 0), reused 6 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), done.

成功将gitlearn从远程克隆下来,接下来又可以愉快的玩耍啦。

分支管理

分支管理允许创建另一条线/方向上开发,能够让你在不影响他人工作的情况下,正常的工作。当在自己创建的分支中完成自己的功过后,合并到主分支就行了(git init初始化的时候已经默认创建了master主分支)。一般团队的合作是不在主分支上进行的,个人项目除外(个人理解)。

创建分支

当前learngit仓库上只有一个分支,那就是master分支,看者可以通过git branch命令来查看当前的分支,git branch branchName命令来创建一个新的分支,我这里创建的是dev分支。

$ cd desktop/learngit
$ git branch
* master
$ git branch dev
$ git branch
  dev
* master

现在已经创建了dev分支,有两个分支了,分支前面带有一个星号的分支说明是当前的正在工作的分区。执行上面的分支后,可以简单的画下现在的情况了,有个HEAD指针指向主分支的最新点,刚才新创建的dev分支我这里默认是一个dev的指针指向了dev分支的最新点。

.
.       HEAD指针
.       │
├────────*master
└────────dev
        │   
        dev指针   

切换分支

我们一般是很少在主分支进行工作的,所以在创建出新的分支之后,我们就切换到新的分支进行相关的工作。可以通过git checkout branchName切换到已经存在的分支工作,通过分支前面的*可查看目前位于哪个分支内。现在我切换到创建的dev分支。

$ git branch
  dev
* master
$ git checkout dev
Switched to branch 'dev'
$ git branch
* dev
  master

合并分支

在创建好分支后,我们在新的分支上工作完成后,就需要往主分支上进行合并啦。我修改了分支dev上的README.md的内容,就是添加文字### new branch content。合并分支可以分成两个合并的方式,一种是本地合并到materz主分支之后,推送(push)到远程库,一种是直接将分支推送到远程库,在远程库进行合并。

本地合并推送

在合并分支前,需要切换到要合并到哪个分支(一般是master主分支),通过git merge branchName将需要的合并的分支合并到当前分支,我是将dev分支合并到master分支。

$ git branch
* dev
  master
$ git checkout master
M   README.md
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge dev
$ Already up-to-date.
$ git add .
$ git commit -m "merge dev branch"
[master d705e73] merge dev branch
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin master
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 282 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit
   0ac49ba..d705e73  master -> master

合并之后,此时,HEAD指针就指向了dev指针,也就是两者同时指向了master主分支的最新处。具体的内容参考传送门

.
.           
.           
├────────*master
└────────dev
        │   
        dev指针    ── HEAD指针

远程库推送合并

远程库内合并的话,要先将dev的分支推送到远程库,然后在远程库进行合并。我这里在dev分支上添加了### add new branch content into again然后demo演示推送(git push origin dev)以及合并。

$ git branch
  dev
* master
$ git checkout dev
Switched to branch 'dev'
$ git add .
$ git commit -m "add dev branch commit again"
[dev dc817c4] add dev branch commit again
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 300 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit
 * [new branch]      dev -> dev

接下来就是进入我的远程learngit仓库进行合并,你会看到下面图示的提示。点击Compare && pull request,然后写点相关的comment(选填),点击Create pull request。之后在绿色勾的提示下Merge pull request,紧接着点击Confirm merge按钮确定合并此分支,这时候返回主分支就可以看到dev内合并的内容了(后期我改动了dev的内容)。看者如果看得不明白,自己上手尝试一下呗!

tools/git/merge_branch

完成后,你会看到learngit仓库的Pull requests量为1,branches量为2。你可以点击进入分支,在ALL branches里面查看分支的具体内容。

删除分支

在创建了分支,然后将分支的内容合并到主分支后,分支的使命就完成了,你就可以将分支删除了,这里的删除个人认为可以是两种,一种是本地仓库的分支删除,一种是远程仓库的分支的删除。当然啦,留着分支也没啥,可以留着呗<del>,自己认为有点碍眼</del>。

本地分支的删除

在本地的learngit的目录下,执行命令行git branch -D branchName就可以删除了。我这里删除的是dev分支。注意⚠️ ,删除的分支不应该是当前工作的分支,需要切换到其他分支,我这里切换的是master分支,毕竟我只有两个分支呢。

$ git branch
* dev
  master
$ git branch -D dev
error: Cannot delete branch 'dev' checked out at '/Users/reng/desktop/learngit'
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git branch
  dev
* master
$ git branch -D dev
Deleted branch dev (was dc817c4).
$ git branch
* master

远程库分支的删除

删除远程库的分支,只要执行git push origin :branchName命令就行了。现在我要删除我远程库中的dev分支,执行git push origin :dev

$ git push origin :dev
To https://github.com/reng99/learngit
 - [deleted]         dev

此时,打开我的远程库learngit,发现之前的Pull requests量为0,branch量为1。

重命名分支

通过git branch -m oldBranchName newBranchName来重命名分支。我这里没有分支了,现在创建一个reng分支,然后将它重命名为dev分支。

$ git branch
* master
$ git branch reng
$ git branch
* master
  reng
$ git branch -m reng dev
$ git branch
  dev
* master

解决冲突

在我们开发的时候,不知道分支和分支之间的进度情况是什么,难免会产生冲突。当产生冲突的时候,就得将冲突的内容更正,然后提交。为了方便演示,我将本地的learngit删除,重新拉取远程的gitlearn仓库(因为我不知道我之前在本地仓库做的修改是啥,对了,我将远程的分支删除了,只剩下master主分支)。克隆下来后,如果还存在本地分支,也将它删除,之后我将在masterdev分支中重新填充里面的README.md的内容。

$ cd desktop
$ git clone https://github.com/reng99/learngit.git
Cloning into 'learngit'...
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 43 (delta 4), reused 38 (delta 1), pack-reused 0
Unpacking objects: 100% (43/43), done.
$ cd learngit
$ git branch
* master
$ ls
README.md
$ cat README.md
## master branch content
$ git add .
$ git commit -m "add master branch content"
[master 1cfa0aa] add master branch content
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 271 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
   d2f936f..1cfa0aa  master -> master
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content

### dev branch content
$ git add .
$ git commit -m "add dev branch content"
[dev 80faf6d] add dev branch content
 1 file changed, 2 insertions(+)
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ cat README.md
## master content

### new master branch content
$ git add .
$ git commit -m "change master content"
[master ec18715] change master content
 1 file changed, 3 insertions(+), 1 deletion(-)
$ git merge dev
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

README.md文件中冲突内容--

<<<<<<< HEAD (当前更改)
## master content

### new master branch content
=======
## master branch content

### dev branch content
>>>>>>> dev (传入更改)

手动修改了README.md文件中冲突的内容--

## master branch content
### new master branch content
### dev branch content

然后命令行执行--

$ git add .
$ git commit -m "fix confict content"
[master dd848b4] fix confict content
$ git log --graph
*   commit 980788b7690d8bcf14610072fc072460bee7e9f1
|\  Merge: c49d09e 2929dca
| | Author: reng99 <1837895991@qq.com>
| | Date:   Thu Dec 21 11:14:10 2017 +0800
| | 
| |     fix confict content
| |   
| * commit 2929dca91ef8f493adba7744cdad19656538334f
| | Author: reng99 <1837895991@qq.com>
| | Date:   Thu Dec 21 11:11:49 2017 +0800
| | 
| |     add dev branch content
| |   
* | commit c49d09e33e7098d67b59c845d18e9c6f8a8f4fea
|/  Author: reng99 <1837895991@qq.com>
|   Date:   Thu Dec 21 11:12:50 2017 +0800
|   
|       change master content
|  
* commit b07f0be8280e4e437cccf2a3f8fac6beef03ff41
| Author: reng99 <1837895991@qq.com>
| Date:   Thu Dec 21 11:10:51 2017 +0800
| 
:

上面操作过程是,我先从远程库中克隆learngit仓库到本地,目前的本地learngit的分支只有master分支,然后我在master分支的README.md中添加相关的文字(见代码),接着把它推送到远程库。然后创建并切换dev分支,在README.md文件中添加新内容(见代码),接着将它提交到GIT仓库。又切换到master分支,修改README.md到内容(见代码),提交到GIT仓库后开始执行merge命令合并dev分支的内容。此时,产生了冲突,这就需要手动将冲突的内容解决,重新commitGIT仓库,最后你就可以提交到远程库了(这步我没有演示,也就是git push origin master一行命令行的事情)。最后我还使用git log ----graph打印出整个分支合并图(从下往上看),方便查看。⚠️ 此时退出git log --graph是书写英文状态按键盘的q键。

说这么多,目的只有一个 --> 产生冲突后,需要手动调整😊

分支管理策略

先放上一张分支管理策略图,然后再慢慢讲解相关的内容...

tools/git/manager_branch_tactics

在分支管理中,我们不断的新建分支,开发,合并分支,删除分支的操作。这里需要注意合并分子的操作,之前我们进行分支的时候是直接将dev开发的分支使用git merge dev进行合并,这样有个缺点:我们看不出分支信息。因为在默认情况下,合并分支的时候,GIT是使用了Fast Foward的模式,在这种模式下,删除分支后,会丢掉分支的信息。下面我重新克隆下我远程learngit仓库,然后创建并更改dev分支的信息,使用默认的模式进行合并。

$ git branch
* master
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
$ git add .
$ git commit -m "add new dev contentt"
[dev 750e1f1] add new dev content
 1 file changed, 1 insertion(+)
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge dev
Updating 980788b..750e1f1
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
$ git log --graph
* commit 750e1f17854872eed4d6cff8315e404079ecb18f
| Author: reng99 <1837895991@qq.com>
| Date:   Fri Dec 22 10:05:36 2017 +0800
| 
|     add new dev content
|    
*   commit 980788b7690d8bcf14610072fc072460bee7e9f1
...

上面的合并就是将master分支上面的HEAD指向dev指针,如下:

# 记录是从上往下
- before merge
    master
    * (begin)
    |
    |
    *
    \
     \
      *
      |
      |
      *  (end)
     dev
     
- after merge
    master
    * (begin)
    |
    |
    *
    |
    |
    * 
    |
    |
    * (end)

为了保留分支的情况,保证版本演进的清晰,我们就得使用普通模式合并,也就是在Fast Foward的模式基础上加上--no-ff参数,即git merge --no-ff branchName,不过我们一般加上你合并的相关信息,即git merge --no-ff -m "your msg here" banchName。现在更改dev分支的内容,再进行合并。

$ git checkout dev
Switched to branch 'dev'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
$ git add .
$ git commit -m "add no-ff mode content"
[dev 80b628c] add no-ff mode content
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
$ git merge dev --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --graph
*   commit 98746d93a9b64ea02b8ff1c7f0fa5e915405c0e6
|\  Merge: 750e1f1 80b628c
| | Author: reng99 <1837895991@qq.com>
| | Date:   Fri Dec 22 14:39:32 2017 +0800
| | 
| |     merge with no-ff
| |   
| * commit 80b628c334618711b77da81fa805ffc246a2cf7d
|/  Author: reng99 <1837895991@qq.com>
|   Date:   Fri Dec 22 14:38:17 2017 +0800
|   
|       add no-ff mode content
|  
* commit 750e1f17854872eed4d6cff8315e404079ecb18f
...

使用--no-ff参数的普通模式合并,会执行正常合并,在master主分支上面会生成一个新的节点,如下(我上面的分支管理策略图里面的合并就是使用了普通的模式):

# 记录是从上往下
- --no-ff合并
    master
    * (before)
    |
    |
    *
    |\ 
    | \
    |  *dev
    |  |
    |  |
    |  *
    | /
    |/
    * (after) 

我们在开发中,分支管理可以分成master主分支、dev开发分支、feature功能分支、release预发布分支、hotfixes修补bug分支。其中功能分支、预发布分支和修补bug分支可以归为临时分支临时分支在进行分支的合并之后就可以被删除了。下面就一一讲解自己眼中的各种分支。

主分支master

主分支是在你初始化仓库的时候(git init),自动生成的一个master分支,删除不了的哦(演示待会给)。主分支是有且仅有一个,也是发布上线的分支,团队合作的最终代码都会在master主分支上面体现出来。也许你也注意到了分支管理策略图里面的主分支会被打上TAG的标签,这是为了方便到某个时间段对版本的查找,标签tag的学习总结后面给出。

# 记录是从上往下
    master
    |
    |
    *(tag 1.0)
    |
    |
    *(tag 1.1)
    |
    |
    *(tag 1.2)

下面代码演示下不能放删除master的情况:

$ cd learngit
$ git branch
  dev
* master
$ git branch -D master
error: Cannot delete branch 'master' checked out at '/Users/reng/desktop/learngit'

开发分支develop

在开发的过程中,项目合作者应该保持自己本地有一个开发环境的分支,在进行分支开发之前,需要进行git pull拉取master主分支的最新内容,或者通过其他的方法。在获取到最新的内容之后才可以进行本地的新功能的开发。在开发完成后将内容merge到主分支之后,不用将dev分支删除,因为你开发的就是在这里进行,何必删除后再新建一个开发环境的分支呢。

接着上面的情况,我目前已经拥有了dev开发分支:

$ cd learngit
$ git branch
  dev
* master

功能(特性)分支feature

一个软件就是一个个功能叠加起来的,在软件的开发中,我们总不能在主分支开发,将主分支搞乱吧。当然,你可以在dev分支中开发,一般新建功能分支来开发,然后功能开发完再合并到dev分支,之后删除功能分支。需要的时候就可以将dev开发分支合并到master主分支,这样就随时保证dev分支功能的完整性。

下面演示功能分支user开发(随便写点内容)的合并(这里也演示了合并到master主分支,跳过了release分支的测试),删除。

$ git checkout dev
Switched to branch 'dev'
$ git branch user
$ git branch
* dev
  master
  user
$ git checkout user
Switched to branch 'user'
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
### function user
$ git add .
$ git commit -m "function user was acheive"
[user 26beda3] function user was acheive
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout dev
Switched to branch 'dev'
$ git merge --no-ff -m "merge user feature" user
Merge made by the 'recursive' strategy.
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
  (use "git push" to publish your local commits)
$ git merge --no-ff -m "merge dev branch" dev
Merge made by the 'recursive' strategy.
 README.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --graph
*   commit f15a1e9012635fc21e944ab76c4cd4bbd539f82f
|\  Merge: 98746d9 0ca83c6
| | Author: reng99 <1837895991@qq.com>
| | Date:   Fri Dec 22 16:35:43 2017 +0800
| | 
| |     merge dev branch
| |     
| *   commit 0ca83c654df64724743a966f5f0989477e504cbc
| |\  Merge: 80b628c 26beda3
| | | Author: reng99 <1837895991@qq.com>
| | | Date:   Fri Dec 22 16:33:27 2017 +0800
| | | 
| | |     merge user feature
| | |    
| | * commit 26beda3b8246e047f10ac0461ca11d1a6f132819
| |/  Author: reng99 <1837895991@qq.com>
| |   Date:   Fri Dec 22 16:31:41 2017 +0800
| |   
| |       function user was acheive
| |     
* |   commit 98746d93a9b64ea02b8ff1c7f0fa5e915405c0e6
|\ \  Merge: 750e1f1 80b628c
| |/  Author: reng99 <1837895991@qq.com>
:
$ git branch -D user
Deleted branch user (was 26beda3).
$ git branch
  dev
* master

预发布分支release

在进行一系列的功能的开发和合并后,在满足迭代目标的时候,就可以打包送测了。这里就需要一个预发布分支release。预发布分支是指在发布正式版本之前( 即合并到master分支之前,可查看上面分支管理策略图),需要一个有预发布的版本(可以理解为灰度环境)进行测试。

预发布环境是从dev分支上面分出来的,预发布结束之后,必须合并到devmaster分支上面。这里我就不演示了,跟功能分支差不多,就是合并的时候要合并到devmaster上,这时候dev分支和master的同步的代码,就不需要将dev分支合并到master了。最后将预发布分支删除掉。

修复bug分支 bug/hotfixes

在写代码的过程中,由于种种原因 -> 比如功能考虑不周全,版本上线时间有限,产品突然改需求等,我们写的代码就出现一些或大或小的bug或者需要紧急修复。那么我们就可以使用bug分支(其实就是新建一个分支处理bug而已啦,命名随意起的),然后在这个分支上处理编码出现的问题。我在分支管理策略图上面已经展示了一种出现bug的情况 -> 就是在测试发布版本看似没问题的情况下,将release版本整合到masterdev中,这时候火眼精金发现了遗留的一个bug,然后新建一个bug分支处理,再合并到masterdev中,之后将bug分支移除啦。

在开发的过程中,无论咋样都是这样 : 新建bug分支 -> 把分支合并 -> 删除分支,这里的demo就不演示了,可以参考上面的功能(特性)分支feature

这里需要注意⚠️的一点,当在开发的过程中,开发到一定的程度,需要停下来需改紧急的bug,那么需要停下手头的工作需改bug啦。这时候需要将工作现场储藏(stash功能)起来,等以后回复现场了后接着工作。现在我在原先的gitlearn仓库中README.md文件文末添加### modify content内容来进行演示。

$ cd desktop
$ cd learngit
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
$ git status
On branch dev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git stash
Saved working directory and index state WIP on dev: 80b628c add no-ff mode content
HEAD is now at 80b628c add no-ff mode content
$ git status
On branch dev
nothing to commit, working tree clean

然后过段时间(这里省略修改的演示),代码已经修改好合并后,需要回到最新的内容区域进行工作,这就需要还原最新的内容了,demo如下:

$ cd learngit
$ git stash list
stash@{0}: WIP on dev: 80b628c add no-ff mode content
$ git stash pop
On branch dev
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (9e85bcc8435ae38c17db59ddc3cd8401af404827)
$ git stash list

⚠️ git stash不仅可以隐藏工作区的内容,也可以隐藏暂存区的内容。git stash list是查看隐藏的列表。git stash pop是将隐藏的内容恢复并删除,git stash pop相当于git stash apply && git stash drop,这里的git stash apply是恢复隐藏内容,git stash drop是删除隐藏内容。

多人协作

简单谈下自己git协作的过程吧。在负责人将搭建好的仓库上传到远程的仓库后(一般是包含了master默认的分支和dev分支),自己将远程仓库克隆到本地,然后在本地的仓库上新建一个dev分支,将远程的dev分支重新拉取下git pull origin dev,开发完成后就可以提交自己的代码到远程的dev分支了,如果提交之前或者之后需要修改bug或者添加新的需求的话,需要新建一个相关的分支并完成开发,将他们合并到本地dev分支后上传到远程dev分支。如果新建的远程仓库中只有master分支,我是这样处理的:依然要在本地新建一个dev分支,然后在完成特定版本的开发后,将分支合并到本地master分支然后再推送到远程master分支,本地的dev分支保留哦。我自己比较偏向于第一种情况。

标签管理

发布一个版本前,为了唯一确定时刻的版本,我们通常在版本库中打一个标签(tag),方便在发布版本以后,可以在某个时刻将某个历史的版本提取出来(因为标签tag也是版本库的一个快照)。

创建标签

创建标签是默认在你切换的分支最新提交处创建的。我这里在本地桌面的learngit仓库master分支上打一个v1.0标签

$ cd desktop/learngit
$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git tag
$ git tag v1.0
$ git tag
v1.0

当然,你可以在非新commit的地方进行标签。这就需要你查找到需要打标签处的commit的id,然后执行git tag tagName commitId。这里我随意找master分支中的commit id进行标签v0.9的标签创建。

$ git log --pretty=oneline --abbrev-commit
f15a1e9 merge dev branch
0ca83c6 merge user feature
26beda3 function user was acheive
98746d9 merge with no-ff
...

现在在commit id为 98746d9处打标签。

$ git tag v0.9 98746d9
$ git tag
v0.9
v1.0

操作标签

在上面创建标签,我们已经有了标签v0.9 v1.0。有时候我们标签打错了,需要进行删除,那么就得更改啦,运用git tag -d tagName

$ git tag -d v0.9
Deleted tag 'v0.9' (was 98746d9)
$ git tag
v1.0
$ git tag v0.8 80b628c -m "version 0.8"
$ git tag
v0.8
v1.0
$ git show v0.8
$ git show v0.8
tag v0.8
Tagger: reng99 <1837895991@qq.com>
tag v0.8
Tagger: reng99 <1837895991@qq.com>
Date:   Wed Dec 27 16:07:46 2017 +0800

version 0.8

在上面的演示中,我删除了v0.9,然后在创建v0.8的时候追加了打标签的信息,之后使用git show tagName查看签名信息。

我们还可以进行分支切换标签,类似于分支的切换,我这里打的两个标签的内容是不同的,我可以通过观察内容的改表来得知时候成功切换标签了。

$ git tag
v0.8
v1.0
$ git checkout v1.0
HEAD is now at f15a1e9... merge dev branch
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff
### function user
$ git checkout v0.8
Previous HEAD position was f15a1e9... merge dev branch
HEAD is now at 80b628c... add no-ff mode content
$ cat README.md
## master branch content
### new master branch content
### dev branch content
### new dev branch content
### merge with no-ff

在确认好标签后,就可以像远程推送标签了,我这里推送v1.0

$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/reng99/learngit.git
 * [new tag]         v1.0 -> v1.0

上面是使用git push origin tagName推送特定的tag到远程库,但是我们能不能推送全部的tag呢?答案是肯定的,看者可以通过git push origin --tags进行推送。有时候,我们推送了tag标签到远程库中了,现在想删除掉怎么办?这个就略微麻烦点,我们不能像上面提到的删除本地库的标签那样,通过git tag -d tagName那样,而是通过git push origin :refs/tags/tagName,这里不演示,如果看者感兴趣可以自己来把弄一下哦。

参考内容

廖雪峰官方网站--Git教程

易百教程--Git教程

git官网

分支管理模型图

Git分支管理策略 - 阮一峰的网络日志

原文链接

git的学习汇总--原创

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1. 安装 Github 查看是否安装git: $ git config --global user.name "...
    Albert_Sun阅读 13,626评论 9 163
  • 1.git的安装 1.1 在Windows上安装Git msysgit是Windows版的Git,从https:/...
    落魂灬阅读 12,645评论 4 54
  • Git是目前最流行的版本管理系统,也是最先进的分布式版本控制系统(distributed version cont...
    pro648阅读 5,672评论 1 17
  • 选择遗忘,我只是为了更好的活下去。 那种疼,你不懂,你也不在乎。 就像我们是两个世界的人,仅仅只是过客。 你的不在...
    千层云林阅读 532评论 4 17
  • 前言 永澄老师在StepAs模型学习课上布置了第一个作业 选定一个任务,然后产生成果,并书写下成果产生方式。 担心...
    想飞的小粉猪阅读 245评论 0 0