likes
comments
collection
share

其实Git比你以为的更简单 -- 实践篇

作者站长头像
站长
· 阅读数 3

其实Git比你以为的更简单 -- 实践篇

前言

作为一个程序猿👨‍💻‍,git可以说一个必备的技能;但是大部分对git的认识还是停留在那几个基本命令,或者是使用图形工具完成日常操作(不得不说IDE自带的git用起来真爽),所以这片文章希望能让大家对git有一个更加全面的认识。

文章中例子所使用的git版本为2.18.0,不同版本之间的命令效果可能会有不同

告诉git你是谁

通常在安装完成之后,git会让我们配置两个基础的信息:

git congfig user.name "xxxx" --global
git config user.email "xxx@xx.com" -global

以上这两行命令就是在全局配置用户名和用户邮箱,用于后续对仓库提交时带上备注信息,跟踪是谁做的修改和提交

在git中,配置文件用三个作用域local, global, system; system作用域作用于当前电脑的所有用户,global作用于当前用户,而local只对某一个仓库起作用。所以local相对于其他两个作用域来说比较特殊,因为它是针对某一个仓库起作用,所以要求运行这个命令时必须在一个仓库的路径下,否则会出类似于fatal: --local can only be used inside a git repository的错误,而global和system则没有这个限制。

查看不同作用域配置项命令:

git config --list --local
git config --list --global
git config --list --system

既然配置文件有不同的作用域,那么不同的作用域肯定也有优先级的区别;在git中的优先级顺序是:local > global > system;这就意味着如果我在global范围设置了user.name为”global name“,并且同时在某一个仓库的local范围设置user.name为”local name“, 那么之后的提交操作中,user.name会使用”local name“。

跑通基本流程

在配置完自己的信息后,就开始进行实际的git相关的操作

初始化仓库

要想使用git的相关命令,就必须在一个git仓库中,所以第一步首先要初始化一个git仓库,一般包括两种方式:

  1. 在已存在的项目中新建git仓库
cd <project-path>
▶ git init

Initialized empty Git repository in /xx/xx/.git/
  1. 新建一个项目并且同时添加git仓库
▶ git init <project-path>

Initialized empty Git repository in /xx/xx/.git/

文件状态

在git中,文件的状态大致可以分为四种:

  • Untraked: 未跟踪的文件(git只记录tracked文件的修改)
  • Unmodified: 未修改的文件,指已经被git跟踪,但是相对于上一次提交没有修改的文件
  • Modified: 已修改的文件,指已经被git跟踪,但是相对于上一次提交有修改的文件
  • Staged: 已被git存储的文件

下图是项目文件在git中基本的状态的lifecycle,结合下文的内容食用更佳。

其实Git比你以为的更简单 -- 实践篇

在上文已经初始化了git仓库,在此时可以运行git status命令查看仓库状态

▶ git status

On branch master
No commits yet
nothing to commit (create/copy files and use "git add" to track)

此时的仓库并没有提交(commit),并且它提醒我们可以使用git add命令来跟踪文件,我们在项目中新建一个readme.txt文件,在看看仓库的状态:

▶ touch readme.txt
▶ git status

On branch master
No commits yet
Untracked files:
  (use "git add <file>..." to include in what will be committed)
  
        readme.txt
    
nothing added to commit but untracked files present (use "git add" to track)

这个此时的readme.txt文件就是Untracked状态,此时可以使用git add readme.txt命令将文件加入到暂存区(这个概念待会解释),然后在看看仓库的状态:

▶ git add readme.txt
▶ git status

On branch master
No commits yet
Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   readme.txt
        

下一步,使用git commit -m "<message>"将本次的修改提交到本地仓库:

▶ git commit -m "create readme.txt"

[master (root-commit) 40cb86b] create readme.txt
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 readme.txt


▶ git status

On branch master
nothing to commit, working tree clean

此时的readme.txt文件就是Staged状态,其中的40cb86b这个字符串就是本次提交的id(每次提交的id都是唯一的)。并且由于工作区的readme.txt文件与本次提交相比并没有做出新的修改,所以也是Unmodified状态,如果此时在readme.txt文件中更改了内容,并且没有进行git addgit commit操作,那么readme.txt将会变成Modified状态:

echo "hello git" > readme.txt
▶ 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.txt

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

此时,可以在重复之前的git addgit commit命令来提交新的修改。

既然git保存了修改的记录,自然也可以查看记录,通过git log命令查看提交了那些修改:

▶ git log

commit 832b59c98986a04cb8008f1eb39333504729dc45 (HEAD -> master)
Author: wenjun <wjxu@mail.com>
Date:   Wed Sep 18 00:00:51 2019 +0800

    add new line in readme.txt

commit 40cb86b7830e0606897d0f792f1a332fe74f5037
Author: wenjun <wjxu@mail.com>
Date:   Tue Sep 17 23:44:29 2019 +0800

    create readme.txt

git log命令将提交的id,提交者和提交时间等信息都统统打印了出来。

到此为止介绍的就是git内文件状态的转换流程,以及git statusgit addgit commitgit log命令的最基础的使用。

三个区域

在前文中提到了暂存区,工作区等名词,本节就来讲讲git中这几个重要的概念。在git中,有三个区域:工作区,暂存区和仓库。工作区就是项目所在的目录区域(你可以在项目工程中看到区域),而仓库就是存储所有提交的区域,暂存区算是工作区和仓库之前的过渡区域,可以简单的看成是用来存储需要提交的文件修改的区域。

其实Git比你以为的更简单 -- 实践篇

结合上图,以及前一节已经提到了的内容,git add命令会将所做的修改提交到暂存区,然后git commit命令会将修改提交到仓库中,此时的暂存区就是干净的(可以暂时认为暂存区被清空,后面会具体说说暂存区的情况);并且如果此时工作区没有更新,那么工作区就是干净的(nothing to commit, working tree clean)。

并不是每一个修改都必须是git add然后在git commit,还可以多次修改,然后使用多次git add命令,最后在使用git commit命令一次提交所有修改。

首先,我们在readme.txt文件中再修改一些内容,并且使用git add放入暂存区;在新建一个chapter1.txt文件,再使用git add放入暂存区;最后使用git commit命令提交两个修改。来看看会发生什么?

▶ 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.txt

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


▶ git add readme.txt
▶ git status

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

        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter1.txt


▶ git add chapter1.txt
▶ git status

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

        new file:   chapter1.txt
        modified:   readme.txt


▶ git commit -m "modify readme.txt & create chapter1.txt"

[master 640029f] modify readme.txt & create chapter1.txt
 2 files changed, 1 insertion(+)
 create mode 100644 chapter1.txt

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

在修该了readme.txt后,git会提示你readme.txt已经有改动,可以提交到暂存区;然后新建chapter1.txt文件后,git会提示readme.txt以及在提交到暂存区中,但是此时chapter1.txt还未被git跟踪,再使用git add命令后,此时git会提醒暂存区有两个修改。当把两个修改commit后,工作区就是干净的。

这个就是git三个区域的基本的变动过程。可能有些人会问:为什么要有暂存区的存在呢?为什么不能直接从工作区提交到仓库,这样不是更简单方便吗?其实暂存区看似使得git的结构更复杂,但是正是由于暂存区的存在,使得我们对修改的保存更加灵活。举个例子,通常情况下我们希望每一个提交都是单纯的,只做一件事,这样在code review或者版本回退的时候能够更清晰;但是可能有使用写代码进入了一个很高效专注的状态,一下子完成了function A, function B(假设两个功能没有交集),此时我们可以先将function A的代码加入暂存区并且提交,然后在提交function B。如果没有暂存区,要么就是不提交,要么就是全提交,可能就比较难实现这种情况。

学习更多的命令

通过之前的内容,大概把git基础的流程过了一遍,也讲了一些基本的命令;本节继续深化对git的更多的流程和常用命令(太偏的我也不会)的认识。

提交修改

git add

之前使用git add命令时,都是一次add一个文件。如果需要多个文件可以使用:

  • git add -u
  • git add --all或者git add .

git add -u只提交所有已跟踪过的文件的修改。比如我们在readme.txtchapter1.txt同时修改内容,并且再新建一个chapter2.txt;此时使用git add -u只会提交readme.txtchapter1.txt的修改到暂存区

▶ 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:   chapter1.txt
        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter2.txt

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


▶ git add -u
▶ git status

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

        modified:   chapter1.txt
        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter2.txt
        

git add --allgit add .两个命令是等价的,会把所有的修改都提交,不管这个文件有没有被git所跟踪。还是以上面的例子为参照

▶ 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:   chapter1.txt
        modified:   readme.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter2.txt

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


▶ git add .
▶ git status

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

        modified:   chapter1.txt
        new file:   chapter2.txt
        modified:   readme.txt
        

如果在同一个文件中改动了多个区域,但是又想分别提交,可以使用git add -p更细致话的控制(该命令也会进入一个vim操作界面)。我们在readme.txt增加两行内容,并且分两次提交两行内容

# 此时的readme.txt多了两行改动
▶ cat readme.txt

hello git
get further in git

use git commit --amend

-----------------------------
this is first line

-----------------------------
this is second line

# 交互式提交修改,首先会打印出readme.txt的修改内容
▶ git add -p

diff --git a/readme.txt b/readme.txt
index 84790cd..cf524cc 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,4 +1,10 @@
 hello git
 get further in git

-use git commit --amend
\ No newline at end of file
+use git commit --amend
+
+-----------------------------
+this is first line
+
+-----------------------------
+this is second line
\ No newline at end of file
# 使用不同的命令,进行不同操作;选择?可以查看各个命令的说明
Stage this hunk [y,n,q,a,d,e,?]? e

# 进入vim界面,最下面会有解释如何操作修改,此时我们只需提交第一行内容,就直接将first line下面的内容删除保存即可
Manual hunk edit mode -- see bottom for a quick guide.
@@ -1,4 +1,10 @@
 hello git
 get further in git

-use git commit --amend
\ No newline at end of file
+use git commit --amend
+
+-----------------------------
+this is first line
+
+-----------------------------
+this is second line
\ No newline at end of file
# ---
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
#
# If the patch applies cleanly, the edited hunk will immediately be
# marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again.  If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.

# 可以看到readme.txt的一部分修改已经提交到了暂存区,接下来就正常的commit即可
▶ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   readme.txt

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.txt

git commit

之前已经介绍过git commit -m命令提交,还可以使用命令合并add操作直接将工作区的修改提交到仓库中git commit -a -m或者git commit -am,它相当于git add -ugit commit -m的结合,所以显然它提交的修改的文件都是已经被git跟踪。对于commit message的规范可以参考阮一峰大佬的文章:Commit message 和 Change log 编写指南

git commit --amend用于重新提交上一次commit,并且可以加入新的修改。比如上一个提交中只包含了readme.txt的修改,忘了包含chapter1.txt的修改,可以用这个命令修改上一次commit

▶ 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:   chapter1.txt
        modified:   readme.txt

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


▶ git add readme.txt
▶ git commit --m "only commit readme.txt"

[master d5ffbec] only commit readme.txt
 1 file changed, 3 insertions(+), 1 deletion(-)


▶ git log --oneline
# 注意看此时的commit id
d5ffbec (HEAD -> master) only commit readme.txt
640029f modify readme.txt & create chapter1.txt
832b59c add new line in readme.txt
40cb86b create readme.txt


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

        modified:   chapter1.txt


▶ git commit --amend
[master b15b67f] only commit readme.txt
 Date: Thu Sep 19 01:45:12 2019 +0800
 2 files changed, 4 insertions(+), 1 deletion(-)


▶ git log --oneline
# 虽然是对上一个提交的修改,但是此时的commit id和之前不一样
cd52b05 (HEAD -> master) commit readme.txt & chapter1.txt
640029f modify readme.txt & create chapter1.txt
832b59c add new line in readme.txt
40cb86b create readme.txt

在使用git commit --amend时会进入一个vim的操作界面,在这个界面中第一行就是之前的commit message以及下方要被提交的文件信息。可以修改新的commit message或者不改也行

only commit readme.txt

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Thu Sep 19 01:45:12 2019 +0800
#
# On branch master
# Changes to be committed:
#       modified:   chapter1.txt
#       modified:   readme.txt
#
~

如果你不需要修改上一次提交的commit message,可以直接使用git commit --amend --no-edit可以之前沿用之前的message,不需要进入vim界面。(其实如果我们直接使用git commit不加-m也会进入vim界面让你添加commit message

查看提交历史

git diff

随着我们不断的add,commit;三个区域的状态都会不一样,使用git diff命令即可像上面git add -p那样打印出不同区域之间的区别。

其实Git比你以为的更简单 -- 实践篇

  • git diff:用于查看工作区和暂存区之前的区别
  • git diff --cached:用于查看暂存区和上一个提交之间的区别
  • git diff HEAD:用于展示工作区和上一次提交之间的区别
  • git diff <commit-id> <commit-id>:用于展示两个提交之间的区别
▶ git status
# 对readme.txt做出更改
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.txt

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


▶ git diff
# 加号开始的行表示新加的行,减号开始的行表示删除的行
# 此时使用git diff HEAD效果一致 
diff --git a/readme.txt b/readme.txt
index cf524cc..267e34f 100644
--- a/readme.txt
+++ b/readme.txt
@@ -7,4 +7,7 @@ use git commit --amend
 this is first line

 -----------------------------
-this is second line
\ No newline at end of file
+this is second line
+
+
+change again
\ No newline at end of file
(END)

# 此时查看暂存区和上一次提交并没有任何修改
▶ git diff --cached

▶ git add .
▶ git diff --cached

# 此时暂存区和上一次的修改与之前一致
diff --git a/readme.txt b/readme.txt
index cf524cc..267e34f 100644
--- a/readme.txt
+++ b/readme.txt
@@ -7,4 +7,7 @@ use git commit --amend
 this is first line

 -----------------------------
-this is second line
\ No newline at end of file
+this is second line
+
+
+change again
\ No newline at end of file
(END)

其实虽然用git diff能看出不同区域直接的区别,但是命令行的方式并不是很直观,这种时候还是推荐使用图形化的界面查看更加直观。

其实Git比你以为的更简单 -- 实践篇

git log

随着不断的提交修改,git仓库中存储着很多的提交记录,git log命令就是用于查看提交历史。

  • git log:用于查看所有的提交
  • git log -<number>:用于查看最近n次提交
  • git log -p:用于展示每次提交log以及每次的改动
  • git log --stat:用于展示每次提交log以及每次的改动的简要统计
  • git log --oneline:用于展示一行简略信息
  • git log --graph:用简单图形展示
▶ git log --oneline

c22c913efa87642532eccc264cfcb6b3318506f9 (HEAD -> master) commit the rest part
1cf3c61d3c1de7e6b4225c1b96f3e1a75313144d partial commit
a8b85b8054510688602bb3c7736205e539303d56 commit readme.txt & chapter1.txt
640029f6f64ced21f8588236217a788beae9ac44 modify readme.txt & create chapter1.txt
832b59c98986a04cb8008f1eb39333504729dc45 add new line in readme.txt
40cb86b7830e0606897d0f792f1a332fe74f5037 create readme.txt

当然还是推荐使用图形化界面操作

其实Git比你以为的更简单 -- 实践篇

git show

如果我们想看某一次提交做了什么,就轮到了git show上场了。

  • git show:用于展示上一个提交的修改
  • git show <commit-id>:用于展示特定提交的修改
  • git show --name-only <commit-id>:用于展示特定提交的修改的文件名
▶ git show --name-only  a8b85b8054510688602bb3c7736205e539303d56

commit a8b85b8054510688602bb3c7736205e539303d56
Author: wenjun <wjxu@thoughtworks.com>
Date:   Thu Sep 19 01:45:12 2019 +0800

    commit readme.txt & chapter1.txt

chapter1.txt
readme.txt
(END)

不用怀疑,这个还是推荐用图形界面查看

其实Git比你以为的更简单 -- 实践篇

文件删除&安全重命名&忽略

git rm

之间的讲解都是如何往git里面添加文件,但是我们并不总是在做加法,也需要减法。

  • git rm <file-name>:从工作区删除文件,并且从仓库中移除对某个文件的跟踪(确切的说只是从暂存区移除,仓库的提交历史依旧会保留该文件
  • git rm --cached <file-name>:只移除对该文件的跟踪,但是依旧在工作区保留文件
# git rm相当于 rm && git add
▶ git rm chapter1.txt

rm 'chapter1.txt'


▶ git status
# chapter1.txt从工作区删除
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    chapter1.txt


# 先恢复chapter1.txt,试试另一个命令
▶ git rm --cached chapter1.txt
rm 'chapter1.txt'

▶ git status
#可以明显看到这个之前命令的区别,它在暂存区删除了对chapter1.txt的跟踪,所以有一个删除的修改(相对于上一次提交对比而言);此时工作区chapter1.txt显示的状态是未跟踪
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    chapter1.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter1.txt


git mv

在一般情况下,重命名文件没有什么问题,git也能正确的推断出你所做的修改修改

# 将chapter1.txt改名为chapter12.txt
# 虽然在工作区显示的是删除了chapter1.txt,新建了chapter12.txt
▶ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    chapter1.txt

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        chapter12.txt

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

#但是在提交到暂存区后,git还是能正确的推荐是重命名操作
▶ git add .
▶ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    chapter1.txt -> chapter12.txt

如果我们只是更换了文件名的大小写,又是另一种情况:

# 将chapter1.txt改名为CHAPTER1.txt
# git并没有检测到修改
▶ git status
On branch master
nothing to commit, working tree clean

# 在CHAPTER1.txt中修改内容,发现git还是认为这个chapter1.txt做出的修改
▶ 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:   chapter1.txt

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

出现这个情况的原因就在于git默认是不区分文件名大小写的,所以对于这个情况使用git mv <old-name> <new-name>就能保证得到正确的结果

▶ git mv chapter1.txt CHAPTER1.txt
▶ git status

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

        renamed:    chapter1.txt -> CHAPTER1.txt

git mv <old-name> <new-name>相当于mv <old-name> <new-name>git rm --cached <old-name>git add <new-name>三条命令的结合

.gitignore

一个项目下并不是所有的文件都需要使用git跟踪,往往我们真正需要追踪的就是源代码即可,并不需要跟踪打包构建文件,也不希望它们总是显示Untracked状态。这时候可以在项目的根目录新建一个.gitignore文件来告诉git哪些文件不需要跟踪。

.gitignore基本规则:

  • 所有空行或者#开头的行都会被忽略
  • *代表0或多个任意字符
  • ?代表1个任意字符
  • [abc]匹配任何一个[]中的字符
  • /代表目录
  • **任意多级中间目录
  • !代表取反

.gitignore文件例子

# 别看了,这是注释

# 忽略所有的.txt结尾的文件
*.txt

#但是index.txt例外
!1.txt

#仅忽略根目录下的index.js文件,如果是sub/index.js则不忽略
/index.js

#忽略build目录下所有文件
build/

#忽略src下面任意层级目录中的index.js
src/**/index.js

有一个常见的场景就是一个被跟踪的文件需要使用.gitignore来忽略。比如某次提交忘了.gitignore加入对.log的忽略,导致.log被git跟踪,就算此时在.gitignore中加入这个规则,git依旧不会忽略.log的修改(简而言之就是.gitignore中的规则对已经被git跟踪的文件不起作用

所以此时需要首先移除git对该文件的的跟踪,使用git rm --cached命令,然后提交修改。新的规则就可以生效了。

git clean

  • git clean:用于删除所有未被git跟踪的文件,但是不包括.gitignore中忽略的文件
  • git clean -n:打印出将会删除的文件名,但不会真正执行删除操作
# 我们先在项目准备两个未被跟踪的文件other.txt和index.txt;但是index.txt被.gitignore忽略
# 提示只会删除other.txt
▶ git clean -n                      
Would remove other.txt

撤销修改,代码回滚

人非圣贤孰能无过,自然我们也会犯错。但是好在git给了我们后悔的机会。

git checkout

现在,我们在工作区改动了一些代码,还没有git add推到暂存区中。但是突然不想要工作区的改动了(就是这么任性);使用git checkout命令可以撤销工作区的修改

  • git checkout <file-name>:撤销某个文件
  • git checkout .:撤销所有文件
  • git checkout <commit-id> <file-name>:恢复某个commit的指定文件到暂存区和工作区(不管你当前的工作区暂存区是否有更新,都会被强制恢复,所以谨慎使用
▶ 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:   chapter1.txt
        modified:   readme.txt

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


▶ git checkout chapter1.txt
▶ 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.txt

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

git checkout的功能还不只这些,下文会继续补充

git reset

如果我们已经将工作区的一些更改推到的暂存区,我们希望撤销暂存区的修改(暂存区的修改会被回退到工作区),可以使用git reset HEAD <file-name>

# 此时的改动已经推到了暂存区
▶ git stauts

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

        modified:   readme.txt


▶ git reset HEAD readme.txt 
Unstaged changes after reset:
M       readme.txt


# 改动被回退到了工作区
▶ 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.txt

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

这里出现HEAD关键字(之前前文也出现了),它代表了仓库当前的版本(不一定是最新版本哦);后文分支部分会详细讲解

除了能撤销暂存区修改,还可以回退版本,你还可以使用该命令回到某一次的提交的状态。 使用git reset <commit-id>,该命令有三个模式--soft--hard--mixed(默认模式)

  • --soft:只移动仓库中HEAD指针的位置,工作区和暂存区的修改都不变
  • --mixed:移动HEAD指针的位置,并使用回退的到版本重置暂存区,工作区的修改保持不变
  • --hard:移动HEAD指针位置,并使用回退的到版本重置工作区和暂存区,保持与指定的提交一致

通常这三个模式直接的区别很容易让人迷糊,但是其实他们的区别就是上面所说的那么简单

其实Git比你以为的更简单 -- 实践篇

# 先看看当前有那些提交
▶ git log --oneline 

# 从`23bcc3e`到`93127f0`这三个提交就是分别在readme.txt新建一行
9c127f0 (HEAD -> master) add third line in readme.txt
ff29824 add second line in readme.txt
23bcc3e add first line in readme.txt
a84a18c add .gitignore

# 现在我们想回到`23bcc3e`这个提交
# commit id 不需要全部打完,打出前几位即可
▶ git reset --soft 23bcc3e  
▶ git log --oneline 
# 可以看到当前的版本已经成功回到了`23bcc3e`
23bcc3e (HEAD -> master) add first line in readme.txt
a84a18c add .gitignore

# 由于使用的是soft模式,只移动了HEAD指针,所以工作区和暂存区之前没有改变,但是由于HEAD指针的改变,导致了当前仓库状态和暂存区出现了差异
▶ git status

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

        modified:   readme.txt



# 此时用hard模式试试
▶ git reset --hard 9c127f0 

HEAD is now at 9c127f0 add third line in readme.txt

▶ git log --oneline 

# 仓库状态回到了最初的版本
9c127f0 (HEAD -> master) add third line in readme.txt
ff29824 add second line in readme.txt
23bcc3e add first line in readme.txt
a84a18c add .gitignore

# 并且因为hard模式会强制重置工作区和暂存区,使其和HEAD状态保持一致,所以显示工作区是干净的
▶ git status  

On branch master
nothing to commit, working tree clean

# 此时用mixed模式在回到`23bcc3e`试试
# mixed是默认模式,所以不需要加--mixed
▶ git reset 23bcc3e  
▶ git status    

# 由于使用的是mixed模式,移动了HEAD指针并重置了暂存区,所以仓库状态和暂存区之前没有改变,而工作区和暂存区出现了差异
# 和之前的soft模式对比看看区别呢
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.txt

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

git log命令只会打印出HEAD指针所指向的提交及其之前的所有提交,如果我们不小心回退多了几个版本,怎么撤销呢?只要你记得之前的commit id,就可以再次回退回去,就像上面的hard模式所使用的命令那样,但是如果不记得id呢,可以使用git reflog命令,它会打印你最近操作所对应的commit id

▶ git reflog  

23bcc3e (HEAD -> master) HEAD@{0}: reset: moving to 23bcc3e
9c127f0 HEAD@{1}: reset: moving to 9c127f0
23bcc3e (HEAD -> master) HEAD@{2}: reset: moving to 23bcc3e
9c127f0 HEAD@{3}: reset: moving to 9c127f08158bac02b3ce9456160fbc3bcf8ed078
ff29824 HEAD@{4}: reset: moving to ff29824a23747234d142b966889399d702443ab5
9c127f0 HEAD@{5}: commit: add third line in readme.txt
ff29824 HEAD@{6}: commit: add second line in readme.txt
23bcc3e (HEAD -> master) HEAD@{7}: commit (amend): add first line in readme.txt
4f251eb HEAD@{8}: commit (amend): add first line in readme.txt
5cbd51c HEAD@{9}: commit: add first line in readme.txt
a84a18c HEAD@{10}: commit (initial): add .gitignore

git revert

git reset可以让你回到某一个版本,但是如果你想重置众多提交中的某些提交的操作,那你就需要用到git revert,它会新建一个提交来重置之前的某一个提交(反向操作)

其实Git比你以为的更简单 -- 实践篇

  • git revert <commit-id>:新建一个提交重置目标提交
  • git revert -n <commit-id>:重置目标提交,但是不会新建提交,而是修改工作区和暂存区
  • git revet <start-id>...<end-id>:重置多个提交(不包括start-id,但是包括end-id)
# 使用该命令后会出现vim窗口提示输入新的提交的message
▶ git revert head          
[master 2c9da62] Revert "add third line in readme.txt"
 1 file changed, 1 insertion(+), 2 deletions(-)

▶ git log --oneline
# 如果不更改,这个就是默认的message 格式
2c9da62 (HEAD -> master) Revert "add third line in readme.txt"
9c127f0 add third line in readme.txt
ff29824 add second line in readme.txt
23bcc3e add first line in readme.txt
a84a18c add .gitignore

分支

这一节,我们开始进入git最为重要的一个特性:分支。分支使得多人协作开发变得什么方便,每个人可以在他们自己专属的分支干活,分支之间互不影响;就要同一台电脑的不同账号一样;当完成了操作后,再将所有的工作合并在一起。

在前面已经提到了HEAD指针,它指向的是当前仓库的版本。其实在之前打印log时,在第一行都能看到类似于9c127f0 - (HEAD -> master)这样的标识。其中的master就是一个分支,当我们新建了一个git仓库时,就会默认创建一个master分支。我们之前所有的改变都是在master分支所有的改变。

其实Git比你以为的更简单 -- 实践篇

这里首先抛出一个结论:git内部的每个提交会以形如单向链表的结构组织起来形成如上图一样的链式结构。而master,HEAD等就是一系列指向不同提交的指针,不同的是master指针始终指向它所在的分支的最新提交节点,而HEAD则是表示当前仓库版本,所以它可以随意的移动到不同的节点。同一个提交节点可以有多个指针指向它。

基于上面的结论,我们可以看出,新建一个分支其实就是新建了一个指针,而从这个指针所指向的节点向前推就是当前分支的所有节点。如上图develop分支的所有节点是:e137e9b -> f5b32c8 -> c1c3dfe -> e088135

查看分支

  • git branch:列出本地所有分支
  • git branch -r:列出所有远程分支(后文会讲解什么是远程分支)
  • git branch -a:列出所有本地和远程分支
  • git branch -v:查看分支的详细信息
# 我们想看看现在本地有哪些分支
▶ git branch
# 行首的*表示当前仓库所指向的是哪个分支
* master


▶ git branch -v

* master 9c127f0 add third line in readme.txt

新建&切换&删除分支

  • git branch <branch-name>:新建分支
  • git checkout -b <branch-name>:新建分支并切换分支
  • git checkout <branch-name>:切换分支
  • git checkout -:切换到上一个分支
  • git branch -d <branch-name>:删除分支(针对已经合并过的分支)
  • git branch -D <branch-name>:删除分支(不管是否合并)
# 新建second分支
▶ git branch second                
▶ git branch -v    
# 可以看到已经新建了second分支,它和master分支现在只想同一个节点
* master 9c127f0 add third line in readme.txt
  second 9c127f0 add third line in readme.txt

# 切换到second分支
▶ git checkout second  
Switched to branch 'second'

# 再新建一个分支
▶ git branch third
▶ git branch
  master
* second
  third

#删除分支
▶ git branch -D third  
Deleted branch third (was 9c127f0).

分支合并

每个人在不同的分支进行了自己工作的提交后,最后肯定是要将各自的工作合并在一起的,这个时候就需要使用git merge命令。

我们现在second分支上添加几个提交,现在的历史如下图所示:second分支添加了两个提交,而master分支没有添加新的提交。所以现在second分支领先master分支两个提交(黄色的小🏷表示的是HEAD指针)。

其实Git比你以为的更简单 -- 实践篇

  • git merge <branch-name>:分别目标分支到当前分支
#先看看second分支readme.txt的状态
▶ cat readme.txt
this is first line
this is second line
this is third line

this is added by second branch
this is also added by second branch  

# 我们现在想合并second分支上两个更新,首先要切换到master分支
▶ git checkout master
# 可以看到master分支中的readme.txt并没有second分支的更新
# 这就是我们之前所说的分支之间互不影响
▶ cat readme.txt
this is first line
this is second line
this is third line

▶ git merge second                
Updating 9c127f0..a5e61dd
Fast-forward
 readme.txt | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

再来看看此时的状态:master分支已经和branch分支指向了同一个提交。此时在master分支就可以看到second分支的更新了。

其实Git比你以为的更简单 -- 实践篇

相信你也注意到了在时候git merge命令时,git提示了Fast-forward的文字,其实Fast-forward表示:如果当前分支合并目标分支时,当前分支没有任何相较于目标分支之后的提交,此时目标分支就是当前分支的直接上游,此时合并只需要将当前分支的指针移到目标分支的最新提交即可。在我们的例子中,就是second分支两个新的提交,但是master分支并没有新的提交,所有此时second分支就是master的直接上游,master分支合并second就只需要移动指针到second即可。

如果你不想使用这个Fast-forward模式,可以使用git merge --no--ff <branch-name>命令,此时不管目标分支是不是当前分支的直接上游,在合并时,都会新建一个合并提交。

▶ git merge --no-ff second 
Merge made by the 'recursive' strategy.
 readme.txt | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

此时的仓库状态是这样:新建一个合并提交,用于合并second分支内容。

其实Git比你以为的更简单 -- 实践篇

如果在两个分支改动了同一个地方,此时合并就会出现冲突。现在我们在master分支新增一个提交,这个提交改了和second分支的新提交相同的地方

其实Git比你以为的更简单 -- 实践篇

▶ git merge second 
# 此时git提示有冲突
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

# 我们来看看此时的readme.txt内容是什么
▶ cat readme.txt
this is first line
this is second line
this is third line
# 下面展示有冲突的部分
# 当前master分支的改动
<<<<<<< HEAD
this is added by master branch
# second分支的改动
=======
this is added by second branch
this is also added by second branch
>>>>>>> second

此时就需要手动处理冲突,删减不需要的部分,然后正常的提交即可。

但是相信大家也会觉得不值观,容易出错;而且如果冲突的地方很多,工作量也很大。所以这个时候还是推荐是一些git的图形化工具,里面有很直观的冲突解决的图形界面,下图所示的是webstorm内置的工具:

其实Git比你以为的更简单 -- 实践篇

解决冲突后正常提交后的git状态:

其实Git比你以为的更简单 -- 实践篇

捡起这颗樱桃(cherry-pick)

有时候,我们并不希望合并其他分支的所有更新的提交,可能我们只需要其中某一些提交即可,此时就需要git cherry pick命令。

  • git cherry-pick <commit-id>:挑选一个commit合并到当前分支
  • git cherry-pick <branch-name>:挑选指定分支的最新提交
  • git cherry-pick <start-comm-id>...<end-commit-id>:挑选连续多个提交(左开右闭,不包括start-commit)
  • git cherry-pick <start-commid-id>^...<end-commit-id>:挑选连续多个提交(左闭右闭,包括start-commit)

其实Git比你以为的更简单 -- 实践篇

我们先恢复一下git仓库的状态:

其实Git比你以为的更简单 -- 实践篇

# 将`add new line`这个提交加入到master分支
▶ git cherry-pick 469a80b44
[master 72ec082] add new line in readme.txt by second branch
 Date: Sun Sep 22 22:58:18 2019 +0800
 1 file changed, 3 insertions(+), 1 deletion(-)

此时的状态:

其实Git比你以为的更简单 -- 实践篇

如果遇到冲突,可以使用:

  • git cherry-pick --continue:在解决冲突后,继续执行下一个cherry-pick
  • git cherry-pick --quit:退出操作,保留当前进度
  • git cherry-pick --abort:撤销本次操作

打上Tag

在git中,tag也是一种指针,和分支,HEAD一样;但是和它们不同的是:tag一旦确定指向那个提交后就不能在移动;所以它特别适合用来标记哪个提交对应的版本号,也十分易于查找。

其实Git比你以为的更简单 -- 实践篇

由于tag一旦确定就无法更改,所以tag的操作相对于分支来说就显得十分的简单了。

查看tag

  • git tag:查看所有tag
  • git tag -l <tag-name>:筛选相应的tag
  • git tag --points-at <commit-id>:查看某个commit上所有的tag
  • git show <tag-name>:查看某一个tag
  • git show-ref --tags:查看所有tag以及它们分别对应的commit

新建&删除tag

  • git tag <tag-name>:新建tag
  • git tag <tag-name> <commit-id>:在指定的提交新建tag
  • git tag -a <tag-name> -m <message>:添加一个tag和message
  • git tag -d <tag-name>:删除tag
# 在指定的commit新建tag
▶ git tag v1.0 71019                                   

#在当前分支的最新提交新建tag
▶ git tag v2.0   

#打印一下当前的tag
▶ git show-ref --tags 
71019047bf04999b475f23409b3158c4a728dfe7 refs/tags/v1.0
91d4eb5400168f699bc33a19959daac89a068406 refs/tags/v2.0

#删除tag
▶ git tag -d v1.0
Deleted tag 'v1.0' (was 7101904)

紧急加塞,使用stash

你现在正在feature分支上开发新的功能,突然QA告诉你master分支有一个bug需要赶紧修复;而这个时候你的工作区还有做到一半的修改,这个时候直接切分支会导致本地的更改会消息,如果新建一个提交,又不合理,毕竟不是一个完成的功能;这个时候就要使用git stash命令暂存本地的修改。

新建stash

git stash会将工作区和暂存区的所有修改暂存起来,是工作区和暂存区与上一次提交一致。

# 先在工作区和暂存区添加一些修改
▶ git status

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

        modified:   readme.txt

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.txt

▶ git stash

Saved working directory and index state WIP on master: 91d4eb5 add sixth line in readme.txt by second branch

# 工作区和暂存区已经干净了
▶ git status

On branch master
nothing to commit, working tree clean

如果不单独指定stash的messge,默认的格式将是WIP on <current-branch>: <last-commit-id> <last-commit-message>

可以使用git stash save <message>指定你想要的messge信息。

▶ git stash save "this is a temporary message"

Saved working directory and index state On master: this is a temporary message

git stash默认的作用范围是已经跟踪过的文件,也可以使用下面的命令扩大作用范围:

  • git stash -u:将未跟踪的文件也加入暂存
  • git stash -a:将所有文件加入暂存(即使该文件被git忽略

查看暂存

  • git stash list:查看暂存的历史
  • git show stash@{<number>}:查看某一个次特定的暂存
▶ git stash list     

stash@{0}: On master: this is a temporary message
stash@{1}: WIP on master: 91d4eb5 add sixth line in readme.txt by second branch

▶ git show stash@{0}

# 展示当前stash的diff
commit 1efaf35f6e15fff0f6545dce28c95d11480d2a8c (refs/stash)
Merge: 91d4eb5 4b8a600
Author: wenjun <wjxu@thoughtworks.com>
Date:   Mon Sep 23 17:19:01 2019 +0800

    On master: this is a temporary message

diff --cc readme.txt
index 394b8bc,394b8bc..ac801af
--- a/readme.txt
+++ b/readme.txt
@@@ -3,4 -3,4 +3,7 @@@ this is second lin
  this is third line
  
  this is added by second branch
--this is also added by second branch
++this is also added by second branch
++
++
++yes

取出&删除暂存

  • git stash apply:取出最近的暂存
  • git stash apply <number>:取出目标暂存
  • git stash pop:取出最近暂存,并删除该暂存的记录
▶ git stash apply          

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.txt

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

▶ git stash list 
# 最近的暂存历史依旧存在
stash@{0}: On master: this is a temporary message
stash@{1}: WIP on master: 91d4eb5 add sixth line in readme.txt by second branch


▶ git stash pop 
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.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (1efaf35f6e15fff0f6545dce28c95d11480d2a8c)

▶ git stash list
# 最近的暂存历史已经被删除
stash@{0}: WIP on master: 91d4eb5 add sixth line in readme.txt by second branch

除了直接把暂存取出来,我们还可以为暂存单独建立一个分支

  • git stash branch <branch-name>:为最近一次暂存建立新的分支,并且删除记录
  • git stash branch <branch-name> <number>:为目标暂存建立新的分支,并且删除记录
# 新建一个stash-branch分支
▶ git stash branch stash-branch

Switched to a new branch 'stash-branch'
On branch stash-branch
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   readme.txt

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.txt

Dropped refs/stash@{0} (8ba8cdc6e530fc0c827f6f3c1978039dd2c19bc4)

# 可以看到已经建立并且切换到了新的分支
▶ git branch

  master
  second
* stash-branch

对于不需要的暂存,可以使用下面命令删除:

  • git stash drop:删除最近暂存
  • git stash drop <number>:删除目标暂存
  • git stash clear:清空历史

变基(rebase)

git里面有一个超级厉害的操作就做变基(rebase), 虽然听起来怪怪的,但是人家是一个正经的命令。我们之前所讲的都是如何回退版本,如何撤销修改。但是并没有改变历史的提交。而rebase可以真正的做到改变历史提交,还可以做到合并分支。

合并分支

先从熟悉的内容入手,我们之前已经学习了使用git merge命令合并其他分支,其实git rebase也可以实现合并分支的功能;使用git rebase <branch-name>

先来看看现在的git状态,master分支有一个新的提交,second分支有两个新的提交

其实Git比你以为的更简单 -- 实践篇

▶ git rebase second

First, rewinding head to replay your work on top of it...
Applying: add a.txt

现在的仓库状态变成了:

其实Git比你以为的更简单 -- 实践篇 rebase操作会将master分支的更新接在second分支两个更新的后面,最后就形成了上图的最终状态。 其实Git比你以为的更简单 -- 实践篇

如果是直接使用git merge将是:

其实Git比你以为的更简单 -- 实践篇

明显能看到同时使用rebase操作能够得到一个更简单的提交历史,这是要有些人更愿意使用rebase的原因。

重写历史

使用git rebase -i <commit-id>可以交互式的操作到commi-id为止的提交(不包括commit-id所指向提交)的所有提交。

之前所学习到的git commit --amend也算是重写历史的一种方式

其实Git比你以为的更简单 -- 实践篇

如果我们想更改最后三个提交,对add a.txt重命名,然后合并update readme.txt aaginupdate readme.txt两个提交

▶ git rebase -i HEAD~3

pick 3baae50 update readme.txt
pick c951be0 update readme.txt again
pick 5a57f77 add a.txt

# Rebase 9c127f0..5a57f77 onto 9c127f0 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
#       However, if you remove everything, the rebase will be aborted.
#
#
# Note that empty commits are commented out

之后就会就如vim界面,我们所有涉及到的每一次提交前面都有pick关键字。我们可以替换不同的关键字实现不同的效果:

  • p or pick:不更改,直接保持该commit
  • r or reword:保留该commit,但是更改commit的信息
  • e or edit:编辑commit,类似于amend
  • s or squash:和前一个commit合并,并编辑新的commit的信息
  • f or fixup:和前一个commit合并,直接使用前一个commit的信息
  • d or drop:删除该commit(或者直接删除该行,效果一致)

基于我们想要的效果,最后将关键字改写成:

pick 3baae50 update readme.txt
f c951be0 update readme.txt again
r 5a57f77 add a.txt

然后保存提交就能得到想要的效果:

其实Git比你以为的更简单 -- 实践篇

如果在rebase过程中,需要冲突可使用:

  • git rebase --continue:继续执行下一个操作(解决冲突后)
  • git rebase --skip:跳过当前操作
  • git rebase --abort:放弃rebase操作

rebase常用操作技巧

把最初始的提交也放入rebase的范围内

由于使用git rebase -i <commit-id>时不会包括commit-id这次提交,所有一般情况对于第一次提交默认情况是无法加入rebase中的,可以在使用命令后的vim操作界面中第一行添加pick <commit-id>即可囊括其中

将几个不连续的commit合并成一个

在使用git rebase -i <commit-id>后,把要合并的几个提交位置调整到一起,将除了要合并的第一个提交外的所有提交的操作符都变成squash或者fixup即可

将一个commit拆分成几个

在使用git rebase -i <commit-id>后,在该提交使用操作符为edit, 使用命令git reset HEAD^将本次提交所有更多撤回到工作区 多次使用git addgit commit就行提交直到工作区干净为止, 最后使用git rebase --continue完成rebase操作

恢复rebase之前的状态简易操作

在rebase开始前,新建一个分支,在执行各种rebase操作,并且完成操作后,可使用git reset --hard <branch-name>恢复到一开始的状态

远程仓库

我们之前所有的内容都是基于本地仓库实现的,但git现在普及度这么高,不仅因为其优秀的版本控制能力,还离不开远程仓库的支持,例如全球最大的同性交友网站github,因为远程操作的存在,使得我们可以将本地的修改推送到远端,也可以十分方便的从远端拉取别人提交的更新。

克隆&管理远程仓库

如果是一个已经存在的远程仓库,我们希望下载到本地,可以使用:

  • git clone <url>:拉取代码,并使用默认的远端仓库的名字
  • git clone <url> <new-name>::拉取代码,自定义本地仓库名字

如果是像本文中已经有本地提交的仓库,可以在github中新建一个仓库,赋值链接,然后使用:

  • git remote add <remote-name> <remote-url>:添加远程仓库
  • git remote -v:查看远端仓库信息
  • git remote remove <remote-name>:删除远程仓库
  • git remote rename <old-remote-name> <new-remote-name>:重命名远程操作名字
▶ git remote add origin git@github.com:AHOhhhh/git-practice.git                                                                     
▶ git remote -v                                       

origin  git@github.com:AHOhhhh/git-practice.git (fetch)
origin  git@github.com:AHOhhhh/git-practice.git (push)

推送和更新

  • git push <remote-name> <branch-name>:推送分支到特定的远程仓库,如果只有一个远程仓库可以省略,第一次推送需要加入-u参数
  • git push <remote-name> <tag-name>:推送指定tag到远端
  • git push <remote-name> --tags:推送所有tag到远端
# 由于是第一次提交,提示需要加`-u`
▶ git push       
fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin master


▶ git push -u origin master 

Enumerating objects: 15, done.
Counting objects: 100% (15/15), done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (15/15), 2.00 KiB | 1.00 MiB/s, done.
Total 15 (delta 0), reused 0 (delta 0)
To github.com:AHOhhhh/git-practice.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

# 将所有分支推送到远端后
# 打印所有分支,会发现多了两个remote的分支,它们表示远端对应的分支
▶ git branch -a 

* master
  second
  remotes/origin/master
  remotes/origin/second

# 推送所有tag
▶ git push --tags   

Total 0 (delta 0), reused 0 (delta 0)
To github.com:AHOhhhh/git-practice.git
 * [new tag]         v1.0 -> v1.0

当远端有更新时,可以使用:

  • git fetch:拉取更新
  • git pull:获取远端仓库更新,并自动合并到本地分支(相当于git fetch && git merge
  • git pull --rebase:使用rebase方式拉取更新(与之前使用rebase合并本地分支效果类似)
▶ git pull

remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1), pack-reused 0
Unpacking objects: 100% (2/2), done.
From github.com:AHOhhhh/git-practice
   e771fff..a8207fe  master     -> origin/master
Updating e771fff..a8207fe
Fast-forward
 e.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 e.txt

特别提醒:由于之前所讲的git commit --amend,rebase等操作会改变提交历史,所以最好只作用于本地提交,对于已推送到远端的提交尽量不要这样操作,不然会和远端有大量的不同和冲突

结语

其实Git比你以为的更简单 -- 实践篇

至此,关于git常用的操作大概讲解完了,虽然通篇都是在讲命令行的使用,但是这并不代表笔者认为命令行党比UI党要高级,其实文章中不少的图片也是使用图形的界面结果。但是一直使用图形界面,对git很多命令会理解的不够深刻,导致老是会被git的一些错误绊住脚。

所以希望通过这篇文章,能够对git的常用知识点进行梳理,希望大家能到git有更深入的认识,并结合图形界面方便的操作,使得以后在git的使用能够更加顺利。

更多文章