本文共 24996 字,大约阅读时间需要 83 分钟。
文章目录
这里将介绍Git相关的基本知识与操作方法,介绍一些必不可少的Git操作。
1. 基本操作
① git init
初始化仓库
要用Git进行版本管理,必须先初始化Git仓库。我们需要实际建立一个新文件夹,之后用 git init
命令初始化以创建新的Git仓库:
$ mkdir git-tutorial$ cd git-tutorial$ git initInitialized empty Git repository in D:/MyDownloads/git-tutorial/.git/
初始化成功的标识是执行了 git init
命令的目录下会生成 .git
目录。其中存储着管理当前目录内容所需的仓库数据。在Git中,这个目录的内容被称作:附属于该仓库的工作树。文件的编辑等操作在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。如果要恢复文件到之前的状态,可以从仓库中调取之前的快照,在工作树中打开。
我们的本地仓库由Git维护的三棵树组成:
- 第一个是我们的 工作目录 ,它持有实际文件;
- 第二个是 暂存区(Index) ,它像一个缓存区域,临时保存个人的改动;
- 第三个是 HEAD ,它指向我们最后一次提交的结果。
② git status
查看仓库中的状态
git status
命令用于显示Git仓库的状态。工作树和仓库的状态,会在操作过程中不断变化。在Git操作中就常常用 git status
查看当前状态。下面实际运行一下:
$ git statusOn branch masterNo commits yetnothing to commit (create/copy files and use "git add" to track)
结果显示,我们当前正处于 master
分支下,还显示:没有可提交的内容。所谓提交 Commit
,指的是:记录工作树中所有文件的当前状态。没有可提交的内容说明,当前我们建立的仓库中没有记录任何文件的任何状态。
我们建立 README.md
文件作为管理对象,对第一次提交做准备:
$ touch README.md$ git statusOn branch masterNo commits yetUntracked files: (use "git add..." to include in what will be committed) README.mdnothing added to commit but untracked files present (use "git add" to track)
可以看到,Untracked files
中显示了 README.md
文件。可见,只要对Git的工作树或者仓库进行操作,git status
命令的显示结果就会发生变化。
③ git add
向暂存区中添加文件
如果仅使用Git仓库的工作树创建文件,那么该文件不会被记入Git仓库的版本管理对象中,用 git status
能发现它们被显示在 Untracked files
之中。要想让文件变成Git仓库的管理对象,需要用 git add <filename>
或者 git add *
命令提出更改,将它们添加到暂存区 (Stage/Index
,提交之前的一个临时区域) 当中:
$ git add README.md$ git statusOn branch masterNo commits yetChanges to be committed: (use "git rm --cached..." to unstage) new file: README.md
将 README.md
加入暂存区后,git status
命令的显示结果发生变化,README.md
文件显示在 Changes to be committed
当中。这是Git基本工作流程的第一步。
④ git commit
保存仓库的历史记录
git commit -m "代码提交信息"
命令实际提交改动,将当前暂存区中的文件实际保存到仓库的历史记录中,通过这些记录可以在工作树中回滚和恢复文件。另外,使用这一命令,可以记述提交信息,-m
参数后的字符串就是提交信息,用来对这个提交进行概述:
$ git commit -m "First commit"[master (root-commit) d938084] First commit 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.md
现在,我们的改动已经提交到了 HEAD ,但是还没有到我们的远端仓库。如果要记录得更加详细,就不加 -m
,直接执行 git commit
命令。执行后编辑器自动启动,显示如下:
# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit# On branch master# # Initial commit## Changes to be committed:# (use "git rm --cached..." to unstage)# # new file: README.md#
在 #
标为注释的 Changes to be committed
要提交的更改一栏中,可以看到本次提交中包含的文件。需要注意的是:如果在编辑器启动后,将提交信息留空并直接关闭编辑器,提交就会被中止。
在编辑器中记录提交信息的格式如下:
- 第一行:用一行文字简述提交的更改内容;
- 第二行:空行
- 第三行以后:记录更改的原因和详细内容
将提交信息按照格式记录完毕后,保存并关闭编辑器,以 #
为注释的行不用删除,之后,这些记录的提交信息就会被提交。只要按照这样的格式输入和提交,以后就可以通过确认日志的命令看到这些记录。
查看提交后的状态,发现当前工作树处于刚刚完成提交的最新状态,没有内容进行提交:
$ git statusOn branch masternothing to commit, working tree clean
⑤ git log
查看提交日志
使用 git log
可以查看仓库以往的提交日志:
$ git logcommit d938084c76d2a56a672b75cd91797fcdb3a8a078 (HEAD -> master)Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 22:27:59 2020 +0800 First commit
可以发现,这是刚刚的提交操作,commit
一栏后跟着的是指向这个提交的哈希值。在Git的其他命令中,指向提交时将用到这个哈希值。Author
栏则显示给Git设置的用户名和邮箱地址。Date
栏中显示提交执行的日期和时间。接着就是这个提交的提交信息。
在 git log
命令后加上 --pretty=short
,就可以让程序显示第一行简述信息,从而方便查看多个提交的日志:
$ git log --pretty=shortcommit d938084c76d2a56a672b75cd91797fcdb3a8a078 (HEAD -> master)Author: memcpy0 <2183927003@qq.com> First commit
如果在 git log
之后加上目录名,就只会显示该目录下的日志;如果添加文件名,就只会显示和该文件相关的日志:
$ git log README.mdcommit d938084c76d2a56a672b75cd91797fcdb3a8a078 (HEAD -> master)Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 22:27:59 2020 +0800 First commit
更进一步,如果想查看提交带来的改动,可以加上 -p
参数,文件的前后差别就会显示在提交信息之后。执行如下命令,就可以只查看 README.md
文件的提交日志、提交前后的差别:
$ git log -p README.mdcommit d938084c76d2a56a672b75cd91797fcdb3a8a078 (HEAD -> master)Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 22:27:59 2020 +0800 First commitdiff --git a/README.md b/README.mdnew file mode 100644index 0000000..e69de29
从这里可以看出,git log
可以组合多种参数,帮助开发者查看以往提交的内容。我们也不必一次记下所有的参数,需要时查看就可以了。
⑥ git diff
查看更改前后的差别
git diff
可以查看工作树、暂存区、最新提交之间的差别。为了理解,以之前提交的 README.md
为例,写一点内容进去:
# Git学习笔记
接着执行 git diff
命令,查看当前工作树和暂存区的区别——由于我们还没有 git add
向暂存区中添加文件,所以程序只会显示工作树和最新提交状态之间的差别:"+"
号显示的是新添加的行,被删除的行用 "-"
号标出,这里只添加了一行:
$ git diffdiff --git a/README.md b/README.mdindex e69de29..70c68ac 100644--- a/README.md+++ b/README.md@@ -0,0 +1 @@+# Git学习笔记\ No newline at end of file
用 git add README.md
命令将 README.md
加入暂存区。如果之后立刻执行 git diff
,由于此时工作树和暂存区的状态没有区别,结果什么都不会显示。要查看与最新提交的差别,执行以下命令:
$ git diff HEADdiff --git a/README.md b/README.mdindex e69de29..70c68ac 100644--- a/README.md+++ b/README.md@@ -0,0 +1 @@+# Git学习笔记\ No newline at end of file
在执行 git commit
命令之前,先执行 git diff HEAD
命令查看本次提交和上次提交之间有什么区别,确认后再提交,是一个好习惯。注意:这里的 HEAD
是指向当前分支中最新一次提交的指针。由于确认了两个提交之间的差别,直接运行 git commit
即可:
$ git commit README.md[master a23d4ca] Add index 1 file changed, 1 insertion(+)
之后查看提交日志,确认提交是否成功。发现能够查到第二个提交:
$ git logcommit a23d4ca4adf7c50ded2011debe3965e9d6343dc7 (HEAD -> master)Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 23:37:59 2020 +0800 Add indexcommit d938084c76d2a56a672b75cd91797fcdb3a8a078Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 22:27:59 2020 +0800 First commit
2. 分支操作
在多人并行开发的过程中,往往同时存在多个最新的代码状态。master
分支是Git默认创建的分支,所有开发都是以这个分支为中心进行的;每个分支中都拥有自己的最新代码。
不同分支之间,可以同时进行完全不同的任务。某个分支的任务完成后,再将它合并到 master
分支上。通过灵活运用分支和合并,能够进行大规模的并行开发。
① git branch
显示分支表和当前分支、创建、删除和移动分支
git branch
命令可以将分支名列表显示,同时确认当前所在的分支:
$ git branch* master
结果中没有显示其他分支名,表示本地仓库只有 master
一个分支;master
分支左侧标有 "*"
,表示这是当前分支,我们正在 master
分支下进行开发。
② git checkout -b
创建、切换分支
要以当前的 master
分支为基础创建新的分支,需要用到 git checkout -b
命令。执行下述命令,会创建名为 feature-A
的分支,并切换过去:
$ git checkout -b feature-ASwitched to a new branch 'feature-A'
注意,这条命令等价于下面的连续两条命令:
$ git branch feature-A //创建feature-A分支$ git checkout feature-A //切换当前分支到feature-A分支
注意,除非我们把分支推送到远端仓库,不然该分支就是不为他人所见的:git push origin <branch>
。之后查看分支列表,显示处于 feature-A
分支下,因为 feature-A
分支左侧标有 "*"
:
$ git branch* feature-A master
在这一状态下,可以正常开发、修改代码、执行 git add
命令并进行提交,不过代码会被提交到 feature-A
分支。不断对一个分支进行提交的操作,被称为培育分支。
此时如果在 README.md
中实际添加一行:
# Git学习笔记- this is feature-A
添加到暂存区,进行提交,之后这一行就添加到 feature-A
分支中:
$ git add README.md$ git diff HEADdiff --git a/README.md b/README.mdindex 70c68ac..f9a2349 100644--- a/README.md+++ b/README.md@@ -1 +1,2 @@-# Git学习笔记\ No newline at end of file+# Git学习笔记+- this is feature-A\ No newline at end of file$ git commit -m "Add feature-A"[feature-A 8bdbe0d] Add feature-A 1 file changed, 2 insertions(+), 1 deletion(-)
现在回头看一下 master
分支有没有受到影响。切换到 master
分支:
$ git checkout masterSwitched to branch 'master'
然后查看 README.md
文件,发现它仍然保持之前的状态,没有被添加文字:
$ cat README.md# Git学习笔记
可见,feature-A
分支的更改,不会影响到 master
分支,这正是在开发中创建分支的优点。只要创建多个分支,就可以在彼此隔离、互不影响的情况下,同时进行多个功能的开发。
如果想要把新建的分支删除掉,使用如下命令,这里不实际执行:
$ git branch -d feature-A //删除feature-A分支
现在切换回 feature-A
分支:
$ git checkout -Switched to branch 'feature-A'
这样用 "-"
连字符代表分支名,就可以切换到上一个分支。或者将 "-"
替换成 feature-A
也可以。
③ 特性分支与主干分支
特性分支,是集中实现单一特性或者主题 Topic
,除此之外不进行任何作业的分支。日常开发中,往往创建多个特性分支,并保留一个随时可以发布软件的稳定分支,稳定分支通常由 master
分支来担当。
- 之前创建的
feature-A
主要实现feature-A
,除此之外不进行任何作业。即使开发中发现了BUG,也需要创建新的分支,在新分支中进行修正。 - 基于特定主题的作业,在特定分支中进行,完成后再与
master
分支合并。只要基于这一开发流程,就能确保masterr
分支随时可以供人查看,其他开发者也可以放心地从master
分支创建新的特性分支。
主干分支是特性分支的原点,也是合并的终点。通常 master
分支是主干分支,主干分支中没有开发中途的代码,可随时供他人查看。有时候需要管理多个版本发布,则需要有多个主干分支。
④ git merge
合并分支
假设 feature-A
已经实现完成,将它合并到主干分支 master
中。首先切换到 master
分支使其成为当前分支:
$ git checkout masterSwitched to branch 'master'
接着进行合并,为了在历史记录中明确记录下本次分支合并,需要创建合并提交。需要在合并时加上 --no-ff
参数:
$ git merge --no-ff feature-AMerge made by the 'recursive' strategy. README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
用 git log
查看,发现 feature-A
分支的内容已经被合并到 master
分支中了:
$ git logcommit f5521b1559418e0f784e067a340356bd2d21e640 (HEAD -> master)Merge: a23d4ca 8bdbe0dAuthor: memcpy0 <2183927003@qq.com>Date: Mon Sep 7 10:11:32 2020 +0800 Merge branch 'feature-A' into mastercommit 8bdbe0de8eb9b2f2d40cc0fcbb04e630dbd1d254 (feature-A)Author: memcpy0 <2183927003@qq.com>Date: Mon Sep 7 01:32:55 2020 +0800 Add feature-Acommit a23d4ca4adf7c50ded2011debe3965e9d6343dc7Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 23:37:59 2020 +0800 Add indexcommit d938084c76d2a56a672b75cd91797fcdb3a8a078Author: memcpy0 <2183927003@qq.com>Date: Sun Sep 6 22:27:59 2020 +0800 First commit
⑤ git log --graph
图表形式查看分支
如果用 git log --graph
查看,就能够用图表形式输出提交日志,非常直观。可以看到特性分支 feature-A
提交的内容已被合并:
$ git log --graph* commit f5521b1559418e0f784e067a340356bd2d21e640 (HEAD -> master)|\ Merge: a23d4ca 8bdbe0d| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 10:11:32 2020 +0800| || | Merge branch 'feature-A' into master| || * commit 8bdbe0de8eb9b2f2d40cc0fcbb04e630dbd1d254 (feature-A)|/ Author: memcpy0 <2183927003@qq.com>| Date: Mon Sep 7 01:32:55 2020 +0800|| Add feature-A|* commit a23d4ca4adf7c50ded2011debe3965e9d6343dc7| Author: memcpy0 <2183927003@qq.com>| Date: Sun Sep 6 23:37:59 2020 +0800|| Add index|* commit d938084c76d2a56a672b75cd91797fcdb3a8a078 Author: memcpy0 <2183927003@qq.com> Date: Sun Sep 6 22:27:59 2020 +0800 First commit
3. 更改提交的操作
① git reset
回溯历史版本
前面学会了如何在实现功能后进行合并提交、累积提交日志作为历史记录。但是这仅仅是Git功能的冰山一角。Git的真正强大之处,在于可以灵活操作历史版本——分散型仓库的优势可以让Git在不影响其他仓库的前提下,对历史版本进行操作。
为此,我们先回溯历史版本,创建一个名为 fix-B
的特性分支:
master / | / | / | / |feature-A | fix-B \ | / \ | / \ | / \ | / node | | | node
a. 回溯到创建 feature-A
分支前
我们回溯到上一次 feature-A
分支创建之前,创建一个名为 fix-B
的特性分支。要让仓库的 HEAD
、暂存区、当前工作树,回到指定状态,需要用 git reset --hard
命令。只要提供目标时间点的哈希值,就可以完全恢复到该时间点的状态。 执行下面的命令:
$ git reset --hard a23d4ca4adf7c50ded2011debe3965e9d6343dc7HEAD is now at a23d4ca Add index
b. 创建 fix-B
分支
我们成功回到了特性分支 feature-A
创建之前的状态。因为所有文件都回溯到了指定哈希值对应的时间点上,README.md
文件的内容也恢复到了当时的状态。接着创建特性分支 fix-B
:
$ git checkout -b fix-BSwitched to a new branch 'fix-B'
在这个主题中,我们在 README.md
中添加一行文字:
# Git学习笔记- this is fix-B
然后直接添加并提交 README.md
文件:
$ git add README.md$ git commit -m "Fix B"[fix-B d4914ee] Fix B 1 file changed, 2 insertions(+), 1 deletion(-)
现在的状态,或者说当前 fix-B
分支的状态,如下所示:
$ git log --graph* commit d4914ee6e016a759393be87e517804d2061ee5ad (HEAD -> fix-B)| Author: memcpy0 <2183927003@qq.com>| Date: Mon Sep 7 11:17:40 2020 +0800|| Fix B|* commit a23d4ca4adf7c50ded2011debe3965e9d6343dc7 (master)| Author: memcpy0 <2183927003@qq.com>| Date: Sun Sep 6 23:37:59 2020 +0800|| Add index|* commit d938084c76d2a56a672b75cd91797fcdb3a8a078 Author: memcpy0 <2183927003@qq.com> Date: Sun Sep 6 22:27:59 2020 +0800 First commit
接着,fix-B
分支的目标是如下所示的状态,即主干分支合并 feature-A
分支的修改后,又合并 fix-B
的修改:
master | \ | \ | \ | \ | \ node | / | | / | | / | | / | |feature-A | fix-B \ | / \ | / \ | / \ | / node | | | node
c. 推进至 feature-A
分支合并后的状态
因此,我们需要恢复到 feature-A
分支被合并后的状态,这一操作被称为推进历史。git log
只能够查看以当前状态为终点的历史日志,这里需要使用 git reflog
查看当前仓库的操作日志,在其中找到回溯历史之前的哈希值,通过 git -reset --hard
恢复到回溯历史前的状态:
$ git reflogd4914ee (HEAD -> fix-B) HEAD@{ 0}: commit: Fix Ba23d4ca (master) HEAD@{ 1}: checkout: moving from master to fix-Ba23d4ca (master) HEAD@{ 2}: checkout: moving from fix-B to mastera23d4ca (master) HEAD@{ 3}: checkout: moving from master to fix-Ba23d4ca (master) HEAD@{ 4}: reset: moving to a23d4ca4adf7c50ded2011debe3965e9d6343dc7f5521b1 HEAD@{ 5}: merge feature-A: Merge made by the 'recursive' strategy.a23d4ca (master) HEAD@{ 6}: checkout: moving from feature-A to master8bdbe0d (feature-A) HEAD@{ 7}: checkout: moving from master to feature-Aa23d4ca (master) HEAD@{ 8}: checkout: moving from feature-A to master8bdbe0d (feature-A) HEAD@{ 9}: checkout: moving from master to feature-Aa23d4ca (master) HEAD@{ 10}: checkout: moving from feature-A to master8bdbe0d (feature-A) HEAD@{ 11}: checkout: moving from master to feature-Aa23d4ca (master) HEAD@{ 12}: checkout: moving from feature-A to master8bdbe0d (feature-A) HEAD@{ 13}: commit: Add feature-Aa23d4ca (master) HEAD@{ 14}: checkout: moving from master to feature-Aa23d4ca (master) HEAD@{ 15}: commit: Add indexd938084 HEAD@{ 16}: commit (initial): First commit
我们在日志中看到 commit, checkout, merge, reset
等Git命令的执行记录。只要不对Git进行GC,就可以通过日志随意调取近期的历史状态。即使我们错误执行了Git操作,也基本都可以通过 git reflog
命令恢复到原先的状态。
feature-A
特性分支合并后的状态,对应的哈希值是 f5521b1
,我们将 HEAD
、暂存区、工作树恢复到这个时间点的状态:
$ git checkout masterSwitched to branch 'master'$ git reset --hard f5521b1HEAD is now at f5521b1 Merge branch 'feature-A' into master
现在回到了回溯前的历史状态,此时的状态如下:
master / | / | / | / |feature-A | fix-B \ | / \ | / \ | / \ | / node | | | node
② 消除冲突
现在只要合并 fix-B
分支,就可以得到我们的目标状态:
$ git merge --no-ff fix-BAuto-merging README.mdCONFLICT (content): Merge conflict in README.mdAutomatic merge failed; fix conflicts and then commit the result.
发生什么状况了?原来是 README.md
文件发生了冲突 Conflict
,系统在合并 README.md
时,feature-A
分支更改的部分和本次想要合并的 fix-B
分支更改的部分发生了冲突。不解决冲突,就无法合并。
在编辑器中打开 README.md
,会发现其内容变成下述状态:
# Git学习笔记<<<<<<< HEAD- this is feature-A=======- this is fix-B>>>>>>> fix-B
=======
以上的部分是当前 HEAD
的内容,以下的部分是要合并的 fix-B
分支中的内容,在编辑器中修改成想要的样子:
# Git学习笔记- this is feature-A- this is fix-B
这里的修正,将 feature-A
与 fix-B
的内容并存于文件中。但是实际的开发中,往往需要删除其中一部分,所以发生冲突时需要仔细分析冲突部分的内容,再进行修改。
冲突解决后,执行添加和提交命令,由于本次解决了冲突,所以记录的提交信息为 "Fix conflict"
:
$ git add README.md$ git commit -m "Fix conflict"[master 81d143c] Fix conflict
查看日志图表,这次合并已经被记录下来:
$ git log --graph* commit 81d143c79a1a67d6a477115cd8c2dff48245bc6a (HEAD -> master)|\ Merge: f5521b1 d4914ee| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 12:49:18 2020 +0800| || | Fix conflict| || * commit d4914ee6e016a759393be87e517804d2061ee5ad (fix-B)| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 11:17:40 2020 +0800| || | Fix B| |* | commit f5521b1559418e0f784e067a340356bd2d21e640|\ \ Merge: a23d4ca 8bdbe0d| |/ Author: memcpy0 <2183927003@qq.com>|/| Date: Mon Sep 7 10:11:32 2020 +0800| || | Merge branch 'feature-A' into master| || * commit 8bdbe0de8eb9b2f2d40cc0fcbb04e630dbd1d254 (feature-A)|/ Author: memcpy0 <2183927003@qq.com>| Date: Mon Sep 7 01:32:55 2020 +0800|| Add feature-A|* commit a23d4ca4adf7c50ded2011debe3965e9d6343dc7| Author: memcpy0 <2183927003@qq.com>| Date: Sun Sep 6 23:37:59 2020 +0800|| Add index|* commit d938084c76d2a56a672b75cd91797fcdb3a8a078 Author: memcpy0 <2183927003@qq.com> Date: Sun Sep 6 22:27:59 2020 +0800 First commit
③ git commit --amend
修改提交信息
使用 git commit --amend
可以修改上一条提交信息。这里把上一条提交信息记为 "Fix conflict"
,但是这实际上是 fix-B
分支的合并,解决合并时发生的冲突只是过程之一,这样标记不太恰当。所以进行修改:
$ git commit --amendFix conflict# Please enter the commit message for your changes. Lines starting# with '#' will be ignored, and an empty message aborts the commit.## Date: Mon Sep 7 12:49:18 2020 +0800## On branch master# Changes to be committed:# modified: README.md#
执行命令后,编辑器启动,显示的内容包含之前的提交信息,把它修改为 Merge branch 'fix-B'
,然后保存文件,关闭编辑器:
[master 9faf393] Merge branch 'fix-B' Date: Mon Sep 7 12:49:18 2020 +0800
之后显示上面的结果。然后,执行 git log --graph
命令,可以看到提交日志中的内容也被修改:
$ git log --graph* commit 9faf393fe36699141f0727f09149716f6ca1b630 (HEAD -> master)|\ Merge: f5521b1 d4914ee| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 12:49:18 2020 +0800| || | Merge branch 'fix-B'| || * commit d4914ee6e016a759393be87e517804d2061ee5ad (fix-B)| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 11:17:40 2020 +0800| || | Fix B| |* | commit f5521b1559418e0f784e067a340356bd2d21e640|\ \ Merge: a23d4ca 8bdbe0d| |/ Author: memcpy0 <2183927003@qq.com>|/| Date: Mon Sep 7 10:11:32 2020 +0800| || | Merge branch 'feature-A' into master| || * commit 8bdbe0de8eb9b2f2d40cc0fcbb04e630dbd1d254 (feature-A)|/ Author: memcpy0 <2183927003@qq.com>| Date: Mon Sep 7 01:32:55 2020 +0800|| Add feature-A|* commit a23d4ca4adf7c50ded2011debe3965e9d6343dc7| Author: memcpy0 <2183927003@qq.com>| Date: Sun Sep 6 23:37:59 2020 +0800|| Add index|* commit d938084c76d2a56a672b75cd91797fcdb3a8a078 Author: memcpy0 <2183927003@qq.com> Date: Sun Sep 6 22:27:59 2020 +0800 First commit
④ git rebase -i
压缩历史
在合并特性分支前,如果发现已提交的内容中有一些拼写错误,可以提交一个修改,然后把这个修改包含在前一个提交中,压缩成一个历史记录。这是一个很有用的技巧。
先创建一个 feature-C
特性分支:
$ git checkout -b feature-CSwitched to a new branch 'feature-C'
在 feature-C
的实现 README.md
中,添加一行含有拼写错误的文字,以便之后修正:
# Git学习笔记- this is feature-A- this is fix-B- this is faeture-C
这是个小变更,提交时没必要先执行 git add
再执行 git commit
,用 git commit -am
一次完成这两步操作:
$ git commit -am "Add feature-C"[feature-C 3814a73] Add feature-C 1 file changed, 2 insertions(+), 1 deletion(-)
现在自行修正刚才提交内容中的错误,修改后的差别如下:
$ git diffdiff --git a/README.md b/README.mdindex b07bb4a..76679b1 100644--- a/README.md+++ b/README.md@@ -1,4 +1,4 @@ # Git学习笔记 - this is feature-A - this is fix-B-- this is faeture-C\ No newline at end of file+- this is feature-C\ No newline at end of file
进行提交,修正错字漏字 typo
:
$ git commit -am "Fix typo"[feature-C fb5281a] Fix typo 1 file changed, 1 insertion(+), 1 deletion(-)
事实上不应该在历史记录中看到这些提交,因为健全的历史记录不需要它们。我们来更改历史,把 "Fix typo"
修改的内容和前一次提交合并,在历史记录中合并为一次完美的提交:
$ git rebase -i HEAD~2
这样执行 git rebase
命令,将选定当前分支中包含 HEAD
(最新提交) 在内的两个最新历史记录为对象,并在编辑器中打开:
pick 3814a73 Add feature-Cpick fb5281a Fix typo# Rebase 9faf393..fb5281a onto 9faf393 (2 commands)## Commands:# p, pick= use commit# r, reword = use commit, but edit the commit message# e, edit = use commit, but stop for amending# s, squash = use commit, but meld into previous commit# f, fixup = like "squash", but discard this commit's log message# x, exec = run command (the rest of the line) using shell# b, break = stop here (continue rebase later with 'git rebase --continue')# d, drop = remove commit# l, label
我们将 fb5281a
的 Fix typo
的历史记录,压缩到 3814a73
的 Add feature-C
中。如下所示,将 fb5281a
左侧的 pick
部分删除,修改为 fixup
:
pick 3814a73 Add feature-Cfixup fb5281a Fix typo
随后,保存编辑器中的内容,关闭编辑器。显示 rebase
成功,也就是将上面的两个提交作为对象,将 "Fix typo"
的内容合并到上一个提交 "Add feature-C"
中,改写成一个新的提交:
Successfully rebased and updated refs/heads/feature-C.
现在查看提交日志,发现 "Add feature-C"
的哈希值已经变化,说明提交被修改:
$ git log --graph* commit b472ba0a73b25fb4691381171a4b2f8fc9c7008f (HEAD -> feature-C)| Author: memcpy0 <2183927003@qq.com>| Date: Mon Sep 7 15:20:27 2020 +0800|| Add feature-C|* commit 9faf393fe36699141f0727f09149716f6ca1b630 (master)|\ Merge: f5521b1 d4914ee| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 12:49:18 2020 +0800| || | Merge branch 'fix-B'| || * commit d4914ee6e016a759393be87e517804d2061ee5ad (fix-B)| | Author: memcpy0 <2183927003@qq.com>| | Date: Mon Sep 7 11:17:40 2020 +0800| || | Fix B| |省略
这样 "Fix typo"
就被抹去,相当于 "Add feature-C"
中从来没有出现过拼写错误,是一种良性的历史改写。feature-C
分支的使命结束,将它和 master
分支合并:
$ git checkout masterSwitched to branch 'master'$ git merge --no-ff feature-CMerge made by the 'recursive' strategy. README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
⑤ git checkout --<filename>
替换本地改动
假如操作失误(当然,这最好永远不要发生),可以使用如下命令替换掉本地改动:
$ git checkout --
此命令会使用 HEAD 中的最新内容替换掉我们的工作目录中的文件。已添加到暂存区的改动以及新文件都不会受到影响。
假如想丢弃在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将本地主分支指向它:
$ git fetch origin$ git reset --hard origin/master
4. 推送到远程仓库
我们了解了对本地单一仓库的操作,是时候接触远程仓库——和本地仓库相对独立的另一个仓库。先在GitHub上创建一个仓库,并且设置为本地仓库的远程仓库。
参考之前的步骤在GitHub上新建一个仓库,仓库名要和本地仓库保持一致,即 git-tutorial
。创建时不勾选 add a README file
选项(一旦勾选,GitHub一侧的远程仓库会自动生成 README
文件,从创建之初就和本地仓库失去了整合性,虽然也可以后期强制覆盖,但是麻烦),直接 Create repository
即可。
① git remote add
添加远程仓库
在GitHub上创建的仓库路径为 https://github.com/memcpy0/git-tutorial.git
,现在用 git remote add
命令将其添加为本地仓库的远程仓库:
$ git remote add origin https://github.com/memcpy0/git-tutorial.git
这一命令执行后,Git会自动将 https://github.com/memcpy0/git-tutorial.git
远程仓库的名称设置为 origin
(标识符)。
② git push
推送至远程仓库
我们的改动现在已经在本地仓库的 HEAD 中了。如果要将当前分支下本地仓库的内容推送到远程仓库,需要用到 git push origin master
命令。可以把 master
换成想要推送的任何分支。假定我们就是在 master
分支下操作:
$ git push -u origin masterEnumerating objects: 20, done.Counting objects: 100% (20/20), done.Writing objects: 100% (20/20), 1.81 KiB | 205.00 KiB/s, done.Total 20 (delta 0), reused 0 (delta 0), pack-reused 0To https://github.com/memcpy0/git-tutorial.git * [new branch] master -> masterBranch 'master' set up to track remote branch 'master' from 'origin'.
这样执行 git push
命令,当前本地仓库 master
分支的内容就会被推送到GitHub的远程仓库 origin
的 master
分支。-u
参数可以在推送的同时,将 origin
仓库的 master
分支,设置为本地仓库当前分支的 upstream
上游。此后,运行 git pull
从远程仓库获取内容时,本地仓库的分支就能直接从 origin
的 master
分支获取内容,省去另外添加参数的麻烦。
在GitHub上,可以确认远程仓库 master
分支的内容,和本地 master
分支相同:
master
分支以外,远程仓库还可以创建其他分支。如果要推送到 master
以外的分支,比如说,在本地仓库创建 feature-D
分支,并将它以同名形式 push
到远程仓库: $ git checkout -b feature-DSwitched to a new branch 'feature-D'$ git push -u origin feature-DTotal 0 (delta 0), reused 0 (delta 0), pack-reused 0remote:remote: Create a pull request for 'feature-D' on GitHub by visiting:remote: https://github.com/memcpy0/git-tutorial/pull/new/feature-Dremote:To https://github.com/memcpy0/git-tutorial.git * [new branch] feature-D -> feature-DBranch 'feature-D' set up to track remote branch 'feature-D' from 'origin'.
现在,在GitHub中可以看到 feature-D
分支:
git remote add origin <server>
。如此就能够将你的改动推送到所添加的服务器上去了。 5. 从远程仓库中获取
上面我们将GitHub上新建的仓库设置为远程仓库,并向其 push
了 feature-D
分支。现在,所有能够访问这个远程仓库的人,都可以获取 feature-D
分支并加以修改。
现在,我们装作又有一个开发者来共同开发目标仓库——在另一个目录下新建一个本地仓库,学习从远程仓库获取内容的操作。
① git clone
获取远程仓库
首先到其他目录下,将GitHub上的仓库 clone
到本地,不要和之前操作的仓库处于同一目录下。我创建了一个 my-git
目录,然后复制到这一目录下:
$ git clone https://github.com/memcpy0/git-tutorial.git my-gitCloning into 'my-git'...remote: Enumerating objects: 20, done.remote: Counting objects: 100% (20/20), done.remote: Compressing objects: 100% (10/10), done.remote: Total 20 (delta 0), reused 20 (delta 0), pack-reused 0Unpacking objects: 100% (20/20), 1.79 KiB | 10.00 KiB/s, done.
执行 git clone
命令后,我们默认处于 master
分支下,同时系统自动将 origin
设置成被 clone
的远程仓库的标识符。即,当前本地仓库的 master
分支和GitHub远程仓库 origin
的 master
分支在内容上完全相同。
用 git branch -a
命令查看本地仓库和远程仓库的分支信息,结果中显示了 remotes/origin/feature-D
,说明远程仓库中已经有了 feature-D
分支:
$ git branch -a* master remotes/origin/HEAD -> origin/master remotes/origin/feature-D remotes/origin/master
用 git checkout -b feature-D origin/feature-D
将远程的 feature-D
分支获取到本地仓库,-b
后面是本地仓库中新建分支的名称,命名为 feature-D
,和远程仓库中的对应分支保持同名;新建分支名称后,是获取来源的分支名称,这里是名为 origin
的(GitHub端的远程)仓库的 feature-D
分支作为来源,然后在本地仓库中创建 feature-D
分支:
$ git checkout -b feature-D origin/feature-DSwitched to a new branch 'feature-D'Branch 'feature-D' set up to track remote branch 'feature-D' from 'origin'.
然后向本地的 feature-D
分支提交更改。在 README.md
文件中添加一行文字 - this is feature-D
,按照之前的方式提交:
$ git commit -am "Add feature-D"[feature-D dcb426b] Add feature-D 1 file changed, 2 insertions(+), 1 deletion(-)
现在推送 feature-D
分支:
$ git pushEnumerating objects: 3, done.Counting objects: 100% (3/3), done.Writing objects: 100% (3/3), 289 bytes | 96.00 KiB/s, done.Total 3 (delta 0), reused 0 (delta 0), pack-reused 0To https://github.com/memcpy0/git-tutorial.git 2fd5cb9..dcb426b feature-D -> feature-D
② git pull
获取最新的远程仓库分支
回到原来的目录下,这边的本地仓库只是创建了 feature-D
分支,没有在其中进行任何提交。然而远程仓库的 feature-D
分支已经有了我们刚刚推送的提交。
此时,可以执行 git pull
命令,以在工作目录中获取 (fetch)
并合并 (merge)
远端的改动。将本地的 feature-D
分支更新到最新状态。当前分支为 feature-D
分支:
$ git pull origin feature-Dremote: Enumerating objects: 5, done.remote: Counting objects: 100% (5/5), done.remote: Compressing objects: 100% (2/2), done.remote: Total 3 (delta 1), reused 2 (delta 0), pack-reused 0Unpacking objects: 100% (3/3), 248 bytes | 9.00 KiB/s, done.From https://github.com/memcpy0/git-tutorial * branch feature-D -> FETCH_HEAD 2fd5cb9..dcb426b feature-D -> origin/feature-DUpdating 2fd5cb9..dcb426bFast-forward README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
GitHub远程仓库中的 feature-D
分支是最新状态,所以本地的 feature-D
分支就得到了更新。以后只要像平常一样在本地进行开发,再 push
到远程仓库,就可以和其他开发者一起在同一个分支进行作业。当然,如果两个人同时修改了同一部分的代码,push
时容易发生冲突。为减少冲突的发生,建议更频繁地进行 push, pull
操作。
总的来说,git pull
和 git push
两种情况下,Git都会尝试去自动合并改动。遗憾的是并非每次都成功,可能出现冲突 (conflicts)
,这时候就需要修改这些文件,手动合并这些冲突 conflicts
。改完之后,还需要执行如下命令以将它们标记为合并成功:
$ git add
在合并改动之前,可以使用如下命令预览差异:
$ git diff
6. 标签操作
为软件发布创建标签是推荐的做法。这个概念早已存在,在SVN中也有。可以执行如下命令创建一个叫做 1.0.0
的标签:
$ git tag 1.0.0 1b2e1d63ff
1b2e1d63ff
是你想要标记的提交记录的哈希值前 10 10 10 位字符。可以使用下列命令获取哈希值:
$ git log
也可以使用少一点的前几位哈希值,只要它的指向具有唯一性。
7. 学习Git的其他资料
助你入门Git的简明指南。
用图示学习Git的使用。 :记录常用的一些命令。 :记录了Git的完整命令,更方便查看。 Git的学习网站。 GitHub网站的使用文档。 可在Web上一边操作一边学习Git的基本功能,不过只有英文版。随便输入一些Git命令:
学习Git基本操作的网站,它的特点在于可以直观看到分支的树形结构,还可以切换各种语言进行学习。上面的知识,我会陆续整理到本文中。
就职于GitHub的Scott Chacon写的,零基础学习Git的资料,有各种语言版本。
转载地址:https://memcpy0.blog.csdn.net/article/details/108456149 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!