Git最常用功能,这一篇就够了!(结合开发场景)

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/h247263402/article/details/74849182

毫无疑问,Git是当下最流行、最好用的版本控制系统。Git属于分布式版本控制系统,相较于Subversion等集中式版本控制系统有很明显的优势。对于我们开发人员来说,熟练使用Git是最基本的技能之一。那么,今天就来说一下在开发工作中,使用到的Git的最基本、最常用的功能有那些?

克隆版本库

工作中,当接手维护一个项目时,需要从远程代码库将项目源码克隆到本地。或者,在Github上发现了一个非常好的开源项目,想要搞下来研究研究,第一步也是克隆版本库。

$ git clone [email protected]:hxdaxu/GitDemo.git
正克隆到 'GitDemo'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
接收对象中: 100% (3/3), done.
检查连接... 完成。

从Github上克隆代码库至本地的当前目录。

通过暂存区控制提交

Git有暂存区的概念,对于一次Commit只会将加入暂存区的变更提交,我们可以通过暂存区控制提交的内容。

$ echo home page ... done. >> homePage.txt
$ echo show page ... developing... >> showPage.txt
$ git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
未跟踪的文件:
  (使用 "git add <文件>..." 以包含要提交的内容)

    homePage.txt
    showPage.txt

提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)

我们假设homePage功能已经开发完成,showPage功能正在开发中,此时最好将已完成的功能先提交。可以看出,git的提醒功能很强大,通过 “git add <文件名>…” 指令将文件加入暂存区。

$ git add homePage.txt
$ git status
位于分支 master
您的分支与上游分支 'origin/master' 一致。
要提交的变更:
  (使用 "git reset HEAD <文件>..." 以取消暂存)

    新文件:   homePage.txt

未跟踪的文件:
  (使用 "git add <文件>..." 以包含要提交的内容)

    showPage.txt

homePage已经处于暂存区了,提交只是针对暂存区的,我们将homePage功能提交。showPage不会受影响,可以继续showPage的开发。

$ git commit -m "首页开发完成"
[master ec9541d] 首页开发完成
 1 file changed, 1 insertion(+)
 create mode 100644 homePage.txt
$ git status
位于分支 master
您的分支领先 'origin/master'1 个提交。
  (使用 "git push" 来发布您的本地提交)
未跟踪的文件:
  (使用 "git add <文件>..." 以包含要提交的内容)

    showPage.txt

提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)

使用 “git commit -m ” message”” 对暂存区内容提交,”-m”参数是对本次提交添加一个描述信息,Git 不建议没有描述信息的提交。

diff差异比较

在本地修改了代码,准备将代码提交到服务器之前,一般我们需要对修改的代码进行下检查。如果修改点比较多,直接看代码检查时,往往容易遗漏,而且不够直观。这个场景下使用Git差异比较工具再合适不过了,差异比较工具会对比修改前后的文件,将差异点显示出来。

$ git diff
diff --git a/showPage.txt b/showPage.txt
index 0d62ae4..dd01952 100644
--- a/showPage.txt
+++ b/showPage.txt
@@ -1,7 +1,7 @@
 show page ... developing...
 我没被修改。

-这一行被删除了。
-这一行将被修改。
+这一行将被修改。追加,被修改。
+我是新添加的行。

 我也没有被修改。

对showPage修改后,使用 ” git diff ” 查看修改内容。看不懂??那可不行!

diff --git a/showPage.txt b/showPage.txt
这一行表明diff的输出格式是git类型的,后面的内容可以理解为是a版本的showPage和b版本的showPage进行比较。

index 0d62ae4..dd01952 100644 这一行涉及到Git对象的概念,意思是版本库index区的0d62ae4对象与工作区的dd01952对象进行比较。对象的ID是版本的哈希值,这里没有显示完全,前面显示出来的部分能确保独一无二即可。后面数字 100 表示文件类型为普通文件,644 表示对文件的操作权限,即 -rw-r–r– 。

--- a/showPage.txt用 ” — ” 表示a版本的showPage,+++ b/showPage.txt用 ” +++ ” 表示b版本的showPage。

@@ -1,7 +1,7 @@这里显示了两个版本的信息,前面的 ” -1,7 ” 表示从 a 版本的第 1 行开始,往下 7 行。后面的 ” +1,7 ” 表示从 b 版本的第 1 行开始,往下 7 行。

再下面展示的是文件内容,以 ” - ” 开头的行表示 a 版本存在,而 b 版本不存在。以 ” + ” 开头的行表示 b 版本存在,而 a 版本不存在。这两个符号都没有的行是上下文,两个版本中都存在。

这里还有一个问题,我们将修改后的文件加入暂存区后,再执行 ” git diff ” 指令发现没有差异信息输出。这是因为直接使用 ” git diff ” 查看的是工作区和暂存区的差异。不同参数的指令还有两个。

$ git diff --cached     // 暂存区和 HEAD 的差异
$ git diff HEAD         // 工作区和 HEAD 的差异

(HEAD 可以理解为一个头指针,表示当前工作区的基础版本,关于分支和 HEAD 的概念后面会说)
使用这两条指令都可以得到和上面相同的差异输出。

git忽略

对于一些编译产生文件,或者本地配置文件是不需要提交到版本库的。但是每次使用 git status 查看状态时,总会提示文件处于未跟踪状态,看着心烦。Git 提供了文件忽略功能,可以将不关心的文件添加到忽略清单中,再使用 git status 就不会看到这些文件了。需要说一点,忽略只对未加入版本库的文件有效。下面介绍两种忽略方式。

第一种是共享式。创建一个名为 ” .gitignore ” 的文件,将要忽略的文件和目录写入,然后把该文件提交到版本库中,这样所有克隆的版本库都可以共享这个忽略文件。(也可以不加入版本库,做为独享忽略文件使用,可以把忽略文件自身也加入到忽略清单中)

$ vi .gitignore
$ cat .gitignore 
*.class
bin/
*.iml
note.txt

文件名可以使用通配符。

第二种是独享式。编辑.git/info/exclude,将要忽略的文件和目录写入即可。

分支是什么?

git中的每次提交都是基于上次提交的(除了初始提交,它没有父提交),所以整个提交历史可以串成一条线,而一次提交就可以理解为线上的一个节点。如图:
Markdown

图中A、B、C、D…均是一次提交,master和branch_feature为两个分支。我们实际操作演示下图中的场景。

$ git branch
* master

使用git branch命令查看本地分支情况,有 ” * ” 号标识的为当前所处分支,可以看出目前本地只有一个分支master。

$ git log --oneline -5
a4ecd06 commit E
d27551e commit D
156bc90 commit C
eaa734e commit B
fc0645e commit A

对 master 分支进行 5 次提交,使用$ git log --oneline -5命令查看最近 5 次的提交信息,提交信息简短显示。git中的一次提交是一个commit对象,有自己的ID和描述,commitID使用40位的SHA1哈希值字符串表示(这里没有完全显示,只要不与其他ID冲突即可)。” commit E ” 是本次提交的描述信息。

$ git branch branch_feature d27551e
$ git branch 
  branch_feature
* master
$ git checkout branch_feature 
切换到分支 'branch_feature'
$ git log --oneline -4
d27551e commit D
156bc90 commit C
eaa734e commit B
fc0645e commit A

使用$ git branch branch_feature d27551e命令基于提交 ” d27551e commit D ” 创建新分支 ” branch_feature ” 。使用检出命令 $ git checkout branch_feature切换到 ” branch_feature ” 分支,然后查看提交历史,当前分支最后提交为commit D ,并且具有自 D 以前的所有提交历史。

那么分支到底是什么呢?分支其实就是一个引用,它指向一次提交,该提交是本分支的最后一次提交。分支的存在形式是.git/refs/heads目录下的一个文件。我们研究一下这个文件。

$ ls .git/refs/heads/
branch_feature  master

这个目录下有两个文件,刚好对应本地的两个分支。看一下这个文件的内容:

$ cat .git/refs/heads/branch_feature 
d27551e1d893e3bb52ec255fda33102419a0f8a8

文件内容是一个id,再看下这个id对应的是什么:

$ git cat-file -t d27551e1d89
commit

是一个commit对象,看一下这个commit对象的详细信息:

$ git cat-file -p d27551e1d89
tree c350f8c30083edb9c60b688e5f09bd9f7896d2f3
parent 156bc9054a5e6a3e3f3ec8914644e70829dcd01c
author huangxu <hxdaxu9934@163.com> 1499238679 +0800
committer huangxu <hxdaxu9934@163.com> 1499238679 +0800

commit D

正是 commit D 。

既然分支是一个引用,当该分支上有新的提交发生时,这个引用会发生什么变化呢?以下创建一个新的提交:

$ echo F > F
$ git add F
$ git commit -m "commit F"
[branch_feature aaea208] commit F
 1 file changed, 1 insertion(+)
 create mode 100644 F
$ git log -1
commit aaea2082dfcb5ec21dcc14dda1e207c53325eeff
Author: huangqingchun <[email protected]>
Date:   Wed Jul 5 17:47:29 2017 +0800

    commit F

看一下分支文件内容:

$ cat .git/refs/heads/branch_feature
aaea2082dfcb5ec21dcc14dda1e207c53325eeff

正是 commit F 的 ID ,分支引用已经指向了提交 F 。分支上有新的提交发生时,分支文件内容会被更新,内容始终是该分支的最后一次提交。

分支也可以理解为一条提交历史线,提交或重置操作会使提交历史线增长或缩短。

现在本地版本库存在了两个分支,在这两个分支上可以分别提交代码,彼此不会受影响。

HEAD是什么?

在对提交重置时,会用到git reset --soft HEAD^命令。HEAD 是什么呢?

HEAD 也是一个引用或者指针,不同于分支,HEAD代表的是当前工作区,它的存在形式是文件.git/HEAD

$ cat .git/HEAD
ref: refs/heads/branch_feature

HEAD 指向了 branch_feature 分支!分支指向具体的提交,这样便确定了工作区的状态。当切换分支时, HEAD 的内容会被改变,指向新的分支。

使用检出命令 git checkout < branch_name > 切换分支,如果此命令后面不使用分支名,而用具体的提交 id ,HEAD 的内容也会变为具体的提交id(分离头指针状态),这个状态下的提交不会被任何分支记录,如非特殊需要,一般也不会 checkout commitID 。

其实,上面我们做的提交,并不是直接作用于分支的,而是 HEAD ,是不过当前 HEAD 是指向分支 branch_feature 的。表现就是提交和重置操作改变了 HEAD ,而 HEAD 指向当前分支,当前分支所指向的具体 commitID 被改变。如果 HEAD 指向具体的 commitID (分离头指针状态),提交或重置操作就不会影响分支,也就不会被分支记录下来。

分支可以理解为分出来的不同提交历史线,头指针HEAD 可以在这些提交历史线之间切换,也可以在某条线上面滑动。

cherry-pick 功能

开发工作中不要在主分支直接操作。如果新功能开发周期比较长,应该创建一个新的本地分支,在该分支开发提交,新功能完成后再合入主分支。

cherry-pick 功能是拣选一个提交应用于当前分支,就是将一个提交放在当前HEAD上形成一个新的完全一样的提交。下面在 branch_feature 分支创建一个提交,然后 cherry-pick 到 master 分支。

$ git diff
diff --git a/showPage.txt b/showPage.txt
index 0d62ae4..aea47d9 100644
--- a/showPage.txt
+++ b/showPage.txt
@@ -1,7 +1,6 @@
 show page ... developing...
 我没被修改。

-这一行被删除了。
-这一行将被修改。
+这一行已经被修改。修改人huangxu。

 我也没有被修改。

$ git add showPage.txt 
$ git commit -m "修改showPage"
[branch_feature b6e1a56] 修改showPage
 1 file changed, 1 insertion(+), 2 deletions(-)

$ git status
位于分支 branch_feature
无文件要提交,干净的工作区

然后切换到 master 分支,并查看 branch_feature 分支的提交历史。

$ git checkout master
$ git log branch_feature  --oneline -1
b6e1a56 修改showPage
$ git cherry-pick b6e1a56
[master 25f1dc0] 修改showPage
 1 file changed, 1 insertion(+), 2 deletions(-)
$ git log --oneline -1
25f1dc0 修改showPage

使用 git cherry-pick 执行拣选操作,上面的输出看出已经在 master 分支形成了新的提交,下面看一下内容是否发生了变化。

$ cat showPage.txt 
show page ... developing...
我没被修改。

这一行已经被修改。修改人huangxu。

我也没有被修改。

内容也已经发生了变化。

冲突解决

以 cherry-pick 为例,如果双方对同一文件的同一位置分别做了不同的提交,在合入时就会发生冲突,需要将冲突解决才能完成合入。

上个例子中在 branch_feature 修改了showPage 文件,下面在 master 分支修改相同位置并完成提交,这样在 cherry-pick 时就会发生冲突。

$ git diff
diff --git a/showPage.txt b/showPage.txt
index 0d62ae4..cd2ad48 100644
--- a/showPage.txt
+++ b/showPage.txt
@@ -1,7 +1,6 @@
 show page ... developing...
 我没被修改。

-这一行被删除了。
-这一行将被修改。
+这一行被修改。修改人hxdaxu。

 我也没有被修改。
$ git add .
$ git commit -m "hxdaxu修改showPage"

下面执行拣选操作,查看输出信息。

$ git cherry-pick b6e1a56
error: 不能应用 b6e1a56... 修改showPage
提示:冲突解决完毕后,用 'git add <paths>' 或 'git rm <paths>'
提示:对修正后的文件做标记,然后用 'git commit' 提交

提示错误,需要解决冲突,查看下当前状态。

$ git status
位于分支 master

您在执行拣选提交 b6e1a56 的操作。
  (解决冲突并运行 "git cherry-pick --continue")
  (使用 "git cherry-pick --abort" 以取消拣选操作)

未合并的路径:
  (使用 "git add <file>..." 标记解决方案)

    双方修改:     showPage.txt

修改尚未加入提交(使用 "git add" 和/或 "git commit -a"

git 给出提示有双方修改的位置,git 是不能帮用户决定以哪次修改为准的。我们使用手动修改的方式解决冲突,先查看一下文件内容:

$ cat showPage.txt 
show page ... developing...
我没被修改。

<<<<<<< HEAD
这一行被修改。修改人hxdaxu。
=======
这一行已经被修改。修改人huangxu。
>>>>>>> b6e1a56... 修改showPage

我也没有被修改。

git 对冲突位置做了标记。我们已经知道 HEAD 代表的是当前工作区的一个基础版本,在<<<<<<< HEAD=======之间的是当前分支的修改,在=======>>>>>>> b6e1a56... 修改showPage之间是所合入版本的修改,也就是 branch_feature 分支做的修改。下面修改文件内容将冲突解决,标记去掉:

$ cat showPage.txt 
show page ... developing...
我没被修改。

这一行被修改。修改人hxdaxu和huangxu.

我也没有被修改。

解决冲突后,提交,完成合入操作。

$ git add showPage.txt
$ git commit -m "共同对showPager的修改"
[master bf9069c] 共同对showPager的修改
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git status
位于分支 master
您的分支领先 'origin/master'2 个提交。
  (使用 "git push" 来发布您的本地提交)

无文件要提交,干净的工作区

提交后在查看状态,错误信息已经不见了,冲突解决了。

git stash 保存工作进度

当在 branch_feature 开发新功能,还在开发中,突然有个紧急任务,需要切换到 master 分支修复一个 bug 。我们希望切换到 master 分支时有个干净的工作区,这时有两个选择,将未完成的任务提交,或者保存当前工作进度。我们采用保存工作进度的做法:

$ git add settingsPage
$ git status
位于分支 branch_feature
要提交的变更:
  (使用 "git reset HEAD <file>..." 撤出暂存区)

    新文件:       settingsPage

对于未跟踪的文件需要先添加到暂存区,才可以保存进度。

$ git stash 
Saved working directory and index state WIP on branch_feature: b6e1a56 修改showPage
HEAD 现在位于 b6e1a56 修改showPage
$ git status
位于分支 branch_feature
无文件要提交,干净的工作区

使用git stash保存进度后,工作区的变动都被保存了下来,这时可以切换到其他分支处理问题,之后再切换回来恢复进度就可以了:

$ git stash pop
位于分支 branch_feature
要提交的变更:
  (使用 "git reset HEAD <file>..." 撤出暂存区)

    新文件:       settingsPage

丢弃了 refs/stash@{0} (729d13b1a47a81479b75175f7677182acd6c1ce4)
$ git status
位于分支 branch_feature
要提交的变更:
  (使用 "git reset HEAD <file>..." 撤出暂存区)

    新文件:       settingsPage

使用git stash pop恢复进度,工作区的变动又回来了,关于进度保存还有些实用的功能:

$ git stash save "保存settingsPage"
Saved working directory and index state On branch_feature: 保存settingsPage
HEAD 现在位于 b6e1a56 修改showPage
$ git stash list
stash@{0}: On branch_feature: 保存settingsPage
$ git stash pop stash@{0}
位于分支 branch_feature
要提交的变更:
  (使用 "git reset HEAD <file>..." 撤出暂存区)

    新文件:       settingsPage

丢弃了 stash@{0} (04036d18ac1845b900951f94df76c9b378c4ef59)

进度保存时可以使用git stash save "message"添加一个描述。进度保存可以保存多处,使用git stash list查看进度保存列表,并可以选择需要恢复的进度。

文中指令汇总

$ git config --global user.name "Your Name"     // 配置用户名
$ git config --global user.email "Your Email"   // 配置邮箱

$ git add <file name>           // 将修改内容添加至暂存区
$ git add --all                 // 添加所有改变的已跟踪文件和未跟踪文件

$ git diff                      // 工作区和暂存区差异比较
$ git diff --cached             // 暂存区和 HEAD 的差异
$ git diff HEAD                 // 工作区和 HEAD 的差异

$ git status                    // 查看当前状态
$ git commit -m "message"       // 提交说明

$ git reset --soft HEAD^        // 重置上次提交,保留修改的内容
$ git reset --hard HEAD^        // 彻底重置上次提交,不保留修改
$ git reset --hard <commit id>  // 彻底重置至某次提交,不保留修改

$ git log                       // 查看提交历史
$ git log --oneline             // 提交历史简短显示
$ git log -5                    // 查看最近5条提交历史

$ git checkout <branch_name>    // 切换分支
$ git checkout -b <branch_name> // 创建并切换到该分支

$ git branch                    // 查看本地分支列表
$ git branch <branch_name>      // 创建新分支,基于当前HEAD
$ git branch -D <branch_name>   // 删除分支

$ git stash                     // 保存进度
$ git stash pop                 // 恢复最近一次保存的进度
$ git stash save "message"      // 保存进度,并添加一个描述
$ git stash list                // 查看保存的进度列表

$ git cherry-pick <commit ID>   // 拣选一次提交应用于当前HEAD

猜你喜欢

转载自blog.csdn.net/h247263402/article/details/74849182