概述

Git入门系列包括git客户端的安装配置,日常开发中常用命令。本文是第二篇,介绍日常开发中常用的命令。

Git初始化

配置Git提交时的用户名和邮箱

1
2
git config --global user.name "xxxxx"
git config --global user.email "xxxx@xxxx.com"

注:git config 命令有3个级别的范围参数,分别是global、local和system,建议上google百度一下他们的区别。

对已有代码目录进行Git初始化

假设你刚刚在GitLab上创建了一个Project,它的Git地址为 git@git.xxxx.com:xxxx/test.git 。这个项目里面什么文件也没有。
而现在我们在本地有一套项目代码,在test-project目录下,有两个文件:

1
2
ls test-project/
a.txt b.txt

现在想把它托管给GitLab,则可以通过下面的命令完成

1
2
3
4
5
6
cd test-project
git init
git remote add origin git@git.xxxx.com:xxxx/test.git
git add a.txt b.txt
git commit -m 'commit initialize'
git push origin master

Git 独奏

Git克隆

假设现在团队里另外一个成员(xxxx)加进这个项目了,他也需要为这个项目贡献代码。那么他要做的第一件事就是把代码从GitLab上克隆(clone)下来,如下:
git clone git@git.xxxx.com:xxxx/test.git

查看提交日志(commit log)

clone下来后,可以看看提交日志:

1
2
3
4
5
6
7
8
$ git log --stat --pretty=full
commit 37510d9d26ec125148815a596a6345176d65dc27
Author: xxxx <xxxx@xxxx.com>
Commit: xxxx <xxxx@xxxx.com>
commit initialize
a.txt | 1 +
b.txt | 1 +
2 files changed, 2 insertions(+)

可以看到,这个项目有一次提交,commit的唯一ID(37510d9d26ec125148815a596a 6345176d65dc27),提交人(xxxx),commit的说明(commit initialize),和提交了哪些文件等信息。

查看当前所在分支

1
2
$ git branch
* master

星号表示当前所在分支,是master。clone下来默认在master分支内。

新建分支

现在xxxx需要建一个新分支, 他的代码都提交到这个分支上。首先通过checkout命令创建分支:

1
2
3
4
5
$ git checkout -b f_test_1.2.0
Switched to a new branch 'f_test_1.2.0'
$ git branch
* f_test_1.2.0
master

checkout 命令加上-b 选项 可同时完成创建新分支和切换到该分支上。

提交代码

xxxx开始工作了:修改a.txt ,删除b.txt,增加c.txt,如下

1
2
3
4
5
6
$ echo "add something" >> a.txt
$ rm b.txt
$ echo "add by xxxx" > c.txt

$ ls
a.txt c.txt

通过 git status -s 命令 显示精简格式的状态输出:

前面红色字样 M - Modify,D- Delete,?-新增(新增文件是2个问号)
通过 git diff 可以查看修改前后文件的差异:

上面的修改能不能直接提交?试验下:

提交成功了吗?看下提交日志:

只有第一次的提交记录,说明刚刚的提交没有成功。留意上面git commit不成功后的提示“Changes not staged for commit” 一句,意思是说:下列修改没有加入到暂存区(stage),不会被提交。暂存区?什么东东?
下面简单说下Git做了什么。Git把本地文件分成3个区域:工作区、暂存区、版本库。我们一般做的修改、新增、删除文件是针对工作区的,只有执行git add 命令后才会把工作区的变动提交到暂存区;执行git commit命令后,才把暂存区的变动提交到版本库。执行git push命令后,把版本库的变动提交到远程代码仓库(好了,暂时只能说这么多了,想更详细的理解这三个区域的关系建议找google了)
为了能成功提交,让我们把上面所在的改动从工作区提交到暂存区吧。

git add -i

加上-i 选项的git add 是个很有Geek范的命令! 它的作用是让我们能挑选哪些文件来提交到暂存区。下面我们用它来把改动提交到暂存区。

最后输入7 或者 q退出 git add -i。 这时再输入git diff 发现已经没有差异输出,因为工作区和暂存区里的文件已经没有差异。 嗯,这也说明git diff 是比较工作区和暂存区的。加上–cached 选项再执行下git diff 看看:

差异又回来了,其实这是因为加了–cached 的 git diff 是比较暂存区和版本库的。因为我们还没把暂存区的改动提交到版本库,所以它们之间当然有文件差异。好,commit一下:

1
2
3
4
$ git commit -m 'commit by xxxx'
3 files changed, 2 insertions(+), 1 deletion(-)
delete mode 100644 b.txt
create mode 100644 c.txt

这时再运行git diff –cached 也没有差异输出了。看下提交日志,commit已成功。

下一步就是把这次提交push到远程代码库即GitLab上了。

1
2
3
4
5
6
7
$ git checkout f_test_1.2.0
Branch f_test_1.2.0 set up to track remote branch f_test_1.2.0 from origin.
Switched to a new branch 'f_test_1.2.0'

$ git branch
* f_test_1.2.0
master

这次push也顺便把新建的分支f_test_1.2.0提交到远程Gitlab了。 当然在本地新创建的分支可以在git checkout -b 后执行git push 推送到远程,不必等到有commit后再push。

GIt交响乐

上面介绍的都是用户单独使用Git的场景,实际工作中,更多的是团队协作。下面模拟2个用户同时在一个分支进行协同开发的场景,来介绍常用的Git命令。

切换分支

现在用户xxxx加入 f_test_1.2.0分支一起工作了。他执行git clone 把代码拉到本地后,执行git checkout命令切换到 f_test_1.2.0分支,如下

1
2
3
4
5
6
7
$ git checkout f_test_1.2.0
Branch f_test_1.2.0 set up to track remote branch f_test_1.2.0 from origin.
Switched to a new branch 'f_test_1.2.0'

$ git branch
* f_test_1.2.0
master

推送

现在用户xxxx和 xxxx1的工作区是相同的。假设这样一种场景:如果两人各自在本地版本库中进行独立的提交,然后再分别向共享版本库GitLab推送,会相互覆盖吗?通过下面的实践就能知道答案。
首先,用户xxxx修改了c.txt 的第一行,然后在本地版本库中提交,再把提交推送到GitLab。操作步骤如下:
(1)用户xxxx修改c.txt 的第一行并提交到本地版本库

1
2
3
4
5
6
7
8
9
10
11
12
$ cat c.txt
add by xxxx
test crlf
#修改第一行后保存,如下
$ cat c.txt
add by xxxx modify by xxxx
test crlf
#提交到本地版本库
$ git add c.txt
$ git commit -m 'xxxx modify'
[f_test_1.2.0 a362394] xxxx modify
1 file changed, 1 insertion(+), 1 deletion(-)

(2)用户xxxx将本地提交推送到GitLab上

1
2
3
4
5
6
7
8
$ git push origin f_test_1.2.0
Counting objects: 5, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 312 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@git.xxxx.com:xxxx/test.git
cff6bc9..a362394 f_test_1.2.0 -> f_test_1.2.0

通过上面的操作,用户xxxx已成功更新了远程GitLab上的代码。如果用户xxxx1不知道用户xxxx所做的上述操作,仍在基于GitLab旧数据同步而来的本地版本库中进行改动,然后用户xxxx1向GitLab推送,会有什么结果?用下面的操作验证过下。
(1)用户xxxx修改c.txt 的第二行并提交到本地版本库

1
2
3
4
5
6
7
8
9
10
11
12
13
$ cat c.txt
add by xxxx
test crlf
# 修改第二行,如下
$ cat c.txt
add by xxxx
test crlf modify by xxxx

#提交到本地版本库
$ git add c.txt
$ git commit -m 'xxxx modify'
[f_test_1.2.0 f17bbfe] xxxx modify
1 file changed, 1 insertion(+), 1 deletion(-)

(2)用户xxxx将本地提交推送到服务器会出错

1
2
3
4
5
6
7
8
9
10
11
bash
$ git push origin f_test_1.2.0
To git@git.xxxx.com:xxxx/test.git
! [rejected] f_test_1.2.0 -> f_test_1.2.0 (fetch first)
error: failed to push some refs to
'git@git.xxxx.com:xxxx/test.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

用户xxxx的推送被拒绝,hint里说明了原因:远程版本库包含你本地没有的改动,通常是因为有其他人push了。需要先通过git pull 命令来合并之后再push。git pull 命令会把改动拉到本地,并进行自动合并。

1
2
3
4
5
6
7
8
9
10
11
12
$ git pull origin f_test_1.2.0
Enter passphrase for key '/c/Users/dellzj/.ssh/id_rsa':
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From git1.kuaiwifi.com:xxxx/test
* branch f_test_1.2.0 -> FETCH_HEAD
cff6bc9..a362394 f_test_1.2.0 -> origin/f_test_1.2.0
Auto-merging c.txt
CONFLICT (content): Merge conflict in c.txt
Automatic merge failed; fix conflicts and then commit the result.

提示“CONFLICT (content): Merge conflict in c.txt”,自动合并过程出现了冲突。好,那就先解决冲突,执行命令 git mergetool 打开图形工具,默认会选择系统中已经安装的工具,如kdiff3。

####冲突解决
执行git mergetool 命令后,会提示使用kdiff3,按回车后自动打开kdiff3窗口,如下图

kdiff3上方三个窗口由左至右显示冲突文件的三个版本,分别是:
A:共同祖先版本
B:本地更改的版本(这里就是用户xxxx更改的版本)
C:远程版本(这里就是用户xxxx push到GitLab的版本)
kdiff3下方的窗口是合并后文件的编辑窗口。
通过下图红框内的箭头或者红框内的红色标记,选中某个冲突行(如第一行),然后选中紫框内的C,表示使用C窗口中的内容作为合并后的内容。同样的操作,第二行则使用B窗口的内容。确定完成后点击保存按钮,关闭kdiff3窗口退出。

这时执行git status,会看到冲突已经解决。不过mergetool会产生一些临时文件,如.orig,不要提交,手动删除即可。

执行git commit命令,会打开commit注释窗口(如下图),git已经为你准备好一个*默认注释,就是刚刚合并冲突的注释。保存退出即可。

查看刚刚解决冲突的提交日志

1
2
3
4
$ git log --oneline
0ffed81 Merge remote-tracking branch 'refs/remotes/origin/f_test_1.2.0' into f_test_1.2.0
f17bbfe xxxx modify
a362394 xxxx modify

执行推送,这次成功了。

1
2
3
4
5
6
7
8
$ git push origin f_test_1.2.0
Counting objects: 10, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 667 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@git.xxxx.com:xxxx/test.git
a362394..0ffed81 f_test_1.2.0 -> f_test_1.2.0

写在最后

Git说到底也只是个工具,足以应付日常工作即可,更多请参考廖雪峰的Git教程~