cover

生活中常常有这样的场景,你写一个文档,想退回以前的某个状态,但发现没有以前的存档。或者很多人协作做一个东西(比如和你的对象一起写小说),但发现大家各自做的东西总是有冲突。遇到这样的情况怎么办呢(恐慌脸),别怕,今天我给大家深入浅出的介绍一下git的基本的使用技巧。

首先,git是一个分布式版本控制系统。分布式版本控制,意味着版本库分布在每一个人的电脑上,这样你修改完后能直接提交到本地版本库,如果别人要得到你的修改从你那复制一份就好了。当然,一般我们会拿一台中央服务器用来交换大家的修改。

给大家介绍一下安装git的方法,这里是安装git的方法

好的,我没有偷懒。

大家现在在命令行里输入git,按回车,先看一下它的输出,上面包括的最基本的用法简介。 初次使用git,需要设置你的邮箱和名字,比如:

$ git config --global user.name "Tian Flower Flower"
$ git config --global user.email flower@example.com

接着给大家介绍一下怎么使用这项强大的工具和你的小伙伴一起敲代码。

首先建立一个空文件夹,打开终端(windows用户搜索cmd),进入那个文件夹下,然后输入git init,这时候那个文件夹就被git初始化了。还有一种初始化的方式,假如对方已经写了一部分代码,你可以直接git clone 下来他的仓库地址。被git初始化后,文件夹就会出现一个新的隐藏文件夹.git,于一个 Git 仓库来说,其 .git 目录保存了整个 Git 项目的所有数据与资源。

现在你的目录下有三个文件(它们已经在本地仓库里了),sing.py,dance.py,read.py,然后你对它们每个文件都加了一行代码。那么问题来了,你想提交到本地仓库的时候附加上提交的备注,"修改了音乐的部分"和"修改了阅读的部分",你需要先提交sing.py、dance.py,再提交read.py。同时为了便于管理这些文件,git还会对这些文件建立状态文件(你输入git status就能看到状态,输入git status --help可以查看具体的参数,其它的命令也是后面加个--help)。因为git一般是在命令行里使用的,所以为了便于添加文件,记录文件状态,git有了暂存区的概念,你可以通过git add sing.py dance.py将文件添加到暂存区,这个时候输入git status,会发现:

~/gitPresentation  master ✗                                                                                                                                                              ✚ ◒  
▶ git status
On branch master

No commits yet

Changes to be committed:  
  (use "git rm --cached <file>..." to unstage)

    new file:   dance.py
    new file:   sing.py

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

    read.py

由上面的信息可以看到dance.py和sing.py已经被加入到了暂存区,changes to be commited就是告诉你这些变化待提交,(use "git rm --cached ..." to unstage)是告诉你可以用那个命令把文件从暂存区里弄出去。下半部分就是没有在暂存区的。

然后git commit -m "your message"就可以提交到本地仓库了。read.py的操作也是git add 然后 git commit。

这个时候我们查看一下.git/objects

~/gitPresentation  master ✔                                                                                                                                                               0m  
▶ find .git/objects -type f
.git/objects/fe/80e07a67b964e66cf2ac5abb611c3e4b351b33
.git/objects/d3/c9a190f400e0f4e5837c25be288540945e1492
.git/objects/b4/43902473907ac4832f821db909f98964ee9f3e
.git/objects/9d/9991b2ac7598604dad272072a4979fb48abddc
.git/objects/f0/2275089261d075a1b33ddb281c310d2365cb04
.git/objects/60/59d3d2e913d238833e9cf2707b5379794fafb3
.git/objects/7d/a981ddf41583e61b41385ca86c7ba5953bc545

可以看到这里有了很多对象,那这个时候我们给read.py增加一个空行,提交后再输入一下这个命令:

~/gitPresentation  master ✔                                                                                                                                                               0m  
▶ find .git/objects -type f                             
.git/objects/fe/80e07a67b964e66cf2ac5abb611c3e4b351b33
.git/objects/36/f4d833fae786bf32082e289808bc8988c039d2
.git/objects/d3/c9a190f400e0f4e5837c25be288540945e1492
.git/objects/6d/fbda7939f771046777e8858bc4d742b4be5e2c
.git/objects/b4/43902473907ac4832f821db909f98964ee9f3e
.git/objects/9d/9991b2ac7598604dad272072a4979fb48abddc
.git/objects/f0/2275089261d075a1b33ddb281c310d2365cb04
.git/objects/60/59d3d2e913d238833e9cf2707b5379794fafb3
.git/objects/7d/a981ddf41583e61b41385ca86c7ba5953bc545
.git/objects/7d/9a8143b3d66a8cb5178f7b780015bcfdaa46ea

可以发现对象变多了。 你每次修改文件提交后git都会把修改后的文件完整的存储起来。你可能想git为什么不每次记录修改的地方,按前面那种方法太浪费空间了。其实是可以的,git默认使用松散对象来存储文件,当太多空间被浪费,或者推送到远程服务器的时候,git会调用git gc,来将松散对象打包好,来节省空间,在打包好的packfile中,git只存储变化的部分,然后用一个指针指向之前的文件。

我们可以手动调用git gc来试一下:

~/gitPresentation  master ✔                                                                                                                                                              16m  
▶ git count-objects -v                                  
count: 10  
size: 40  
in-pack: 0  
packs: 0  
size-pack: 0  
prune-packable: 0  
garbage: 0  
size-garbage: 0

~/gitPresentation  master ✔                                                                                                                                                              16m  
▶ git gc
Counting objects: 10, done.  
Delta compression using up to 4 threads.  
Compressing objects: 100% (10/10), done.  
Writing objects: 100% (10/10), done.  
Total 10 (delta 0), reused 0 (delta 0)

~/gitPresentation  master ✔                                                                                                                                                              16m  
▶ git count-objects -v
count: 0  
size: 0  
in-pack: 10  
packs: 1  
size-pack: 2  
prune-packable: 0  
garbage: 0  
size-garbage: 0

~/gitPresentation  master ✔                                                                                                                                                              16m  
▶ find .git/objects -type f
.git/objects/pack/pack-2c2c342dc3d81f01aa69d2bd2eedf5d4429264d9.pack
.git/objects/pack/pack-2c2c342dc3d81f01aa69d2bd2eedf5d4429264d9.idx
.git/objects/info/packs

~/gitPresentation  master ✔                                                                                                                                                              16m  
▶ ls
dance.py  read.py  sing.py  

可以看到之前那十个松散对象都被打包好了。

现在你想写有关巴赫的代码,你的朋友想写有关莫扎特的代码,于是为了避免冲突,你创建了一个叫巴赫的分支,你的朋友创建了一个叫莫扎特的分支。(这里提醒一下,git pull默认只获取远程的master分支,如果要checkout到远程其它的分支,首先要git branch -r,然后checkout到对应的分支。)

现在你先checkout到Bach分支,进行魔改操作,然后提交:

~/gitPresentation  master ✔                                                                                                                                                              22m  
▶ git checkout Bach
Switched to branch 'Bach'

~/gitPresentation  Bach ✔                                                                                                                                                                29m  
▶ ls
dance.py  read.py  sing.py

~/gitPresentation  Bach ✔                                                                                                                                                                29m  
▶ vim note.py 

~/gitPresentation  Bach ✗                                                                                                                                                              31m ◒  
▶ git add note.py 

~/gitPresentation  Bach ✗                                                                                                                                                              31m ✚  
▶ git commit -m "note"
[Bach 4f38850] note
 1 file changed, 5 insertions(+)
 create mode 100644 note.py

~/gitPresentation  Bach ✔                                                                                                                                                                 0m  
▶ git log --oneline
4f38850 (HEAD -> Bach) note  
36f4d83 (master, Mozart) add a empty line  
b443902 read.py  
7da981d music  

你先把Bach分支合并到master分支:

~/gitPresentation  master ✔                                                                                                                                                              37m  
▶ git merge Bach
Updating 36f4d83..4f38850  
Fast-forward  
 note.py | 5 +++++
 1 file changed, 5 insertions(+)
 create mode 100644 note.py
~/gitPresentation  master ✔                                                                                                                                                               9m  
▶ git log --oneline
4f38850 (HEAD -> master, Bach) note  
36f4d83 add a empty line  
b443902 read.py  
7da981d music  

你的朋友在Mozart分支下也创建了一个note.py。然后你想把Mozart分支也合并到master分支,那么问题来了,Mozart分支下也有note.py,内容差不多,和master分支下的note.py冲突了,我们可以git diff一下:

~/gitPresentation  master ✔                                                                                                                                                              11m  
▶ git diff master Mozart 
diff --git a/note.py b/note.py  
index ce070d4..0c85471 100644  
--- a/note.py
+++ b/note.py
@@ -1,5 +1,5 @@
 def note():
-    print('note')
+    print('Mozart')

 if __name__ == '__main__':
     note()

可以看出来有不同的地方,我们需要手动处理冲突。 这里讲一下git merge是如何判断冲突的,贴一段官方文档里的话:

For conflicting paths, the index file records up to three versions: stage 1 stores the version from the common ancestor, stage 2 from HEAD, and stage 3 from MERGE_HEAD (you can inspect the stages with git ls-files -u). The working tree files contain the result of the "merge" program; i.e. 3-way merge results with familiar conflict markers <<< === >>>.

就是说两个分支合并,先和公共的基点做对比,不然,要是一个分支比另一个分支多一个文件,你怎么知道是a分支增加了一个文件,还是b分支删除了一个文件。

手动处理冲突后,我们进行合并,然后查看提交历史:

~/gitPresentation  master ✔                                                                                                                                                              25m  
▶ git merge Mozart 
Merge made by the 'recursive' strategy.

~/gitPresentation  master ✔                                                                                                                                                               0m  
▶ git log --graph --oneline
*   0cfd35e (HEAD -> master) Merge branch 'Mozart'
|\  
| * cf331f2 (Mozart) solve
| * 756753d Mozart not
* | 4f38850 (Bach) note
|/  
* 36f4d83 add a empty line
* b443902 read.py
* 7da981d music

可以看到现在Bach分支和Mozart分支都已经合并到了master分支,此时要记住最好删除Bach分支和Mozart分支,避免历史混乱。

git还有一种合并方法叫rebase,它的原理是将另一条分支上面提交的commit在master上重演一遍,但是因为rebase会改写历史记录,所以不要在公共分支上用rebase,不然可能一不小心,你的朋友们都会仇恨你。不过这里提一个有用的rebase命令,如果你想修改上一个commit信息,自然是用git commit --amend,但是修改很久之前的呢,可以用git rebase -i(查看git rebase --help的INTERACTIVE MODE)。

好,git今天就简单的讲到这了,谢谢大家。

扫描二维码,分享此文章

蒲编's Picture
蒲编