Git学习笔记

以前一直没有系统的学习一遍git,导致每次使用都会有各种奇怪的问题。这次一定要把git学明白了。

学习资料主要参考廖雪峰的git教程,git官方文档
其中git官方文档有手册也有书,非常适合查看

git是一个分布式的版本控制系统,每个人的机器都可以当做一个代码仓库。git保存的是文件的修改,而不是每次修改后的文件,所以非常适合回溯到之前的版本。git只能管理文本文件的修改,像视频,图片,word文档之类的都只能显示出二进制编码的改变,不能显示内容的修改。

创建git仓库

首先需要在通过git init命令将一个目录变成git可以管理的仓库。

之后会出现一个.git的目录,这是跟踪管理版本库的目录,可以看一下里面有啥

.git目录下的内容

这里面的内容还没有仔细研究过,之后会仔细的研究一下,本篇仅介绍使用和理解。

代码的提交

在执行过git init之后的目录称为工作区,也就是存放你代码的地方。

git的提交分为两阶段:

//第一阶段
git add [file]

会将你指定的文件(可以是单个文件,也可以是.,整个目录)存到暂存区stage。

//第二阶段
git commit -m "first commit"

-m参数后面要加本次commit的相关信息
会将暂存区里面的修改提交到版本库。这样在你本地的版本库里就可以存多个版本的代码的修改。

工作区和暂存区

借用廖雪峰的教程中的图

工作区和暂存区

图中有git自动为我们创建的第一个分支master,以及一个指向master的指针HEAD

我们修改文件是在工作区操作,git add之后修改会被放到暂存区,git commit之后修改会被提交到当前分支,并且清空暂存区,HEAD会指向当前分支的最新提交。

查看工作区和暂存区的状态

有一个命令可以查看当前本地代码仓库的情况:

git status

会显示你当前是否有修改,修改是否放进暂存区,是否已经提交到分支上。非常重要并且好用的命令!

远程仓库

Github是专门为git设计的远程代码仓库,上面都是开源代码。也可以把自己的代码保存到Github上。

首先需要将你本地用户的ssh公钥传到Github上,因为本地仓库连接远程仓库主要通过的是ssh协议。

之后我们只需要在本地的代码仓库(即存代码的目录)下运行命令指定远程仓库即可:

git remote add origin [email protected]:MiracleMa/[].git

这样就把本地仓库和远程仓库联系起来了,远程仓库名字默认为origin,也可以自己指定,可以和多个远程仓库连接。

可以通过相关命令查看远程库的信息:

git remote //查看远程库的名字
git remote -v //查看远程库的详细信息
git remote show name //查看指定name的远程库的详细信息

如果你需要修改远程仓库地址,也是使用git remote相关的命令进行修改,详情可以查看git remote -h

提交代码到远程仓库

如何把本地的代码提交到远程仓库呢?首先本地工作区的修改需要被commit到版本库,然后通过以下命令推送到远程仓库:

git push [-u] origin master

origin表示远程仓库名称
master表示本地仓库分支,完整表示应该是<本地分支>:<远程分支>,省略:<远程分支>意味着push到和本地分支一样名字的分支
-u参数表示会将本次的远程仓库和分支当做默认的,之后执行git push就默认推送到这个仓库和分支(不太建议使用)

默认提交到dev分支(会自动创建远程分支)

克隆远程仓库的代码
git clone [email protected]:MiracleMa/[].git

就可以通过ssh协议把远程的仓库clone到本地,也可以使用https协议,用参数-b或者-t也可以指定相关的分支或者标签,可以查看git clone -h

clone过程中,git自动的把远程的master分支和本地的master分支对应起来,并且远程仓库默认为origin。(如果clone的是xxx分支,就会自动的在本地创建xxx分支,然后对应起来)

版本回退

每次commit都有一个commit_id,是一个十六进制编号,因为这是分布式的版本管理系统,为了防止不同用户的commit_id发生冲突,所以需要用哈希方法计算。

可以通过下面这个命令来查看曾经的修改:

mashaonan@mashaonan:~/Desktop/gti$ git log
commit 4114e6eec347fb4dddecd007150932ffa1423fb3
Author: MiracleMa 
Date:   Sat Apr 14 14:08:43 2018 +0800

    a

commit 40a198e833a36598257ea0ae672e57c1006cf406
Author: MiracleMa 
Date:   Sat Apr 14 14:07:29 2018 +0800

    merge

很长的数字串就是commit_id,还包括commit时的相关信息。

git log可以用相关参数来更好的表示commit的情况,比如--pretty=oneline输出到一行,--graph输出图状commit,用于观察分之合并。

通过以下命令可以回退到某个以前提交的版本:

git reset --hard [commit_id]

只需要输入commit_id的前几位,git就会自动去寻找,也看用HEAD^代替commit_idHEAD表示当前版本,HEAD^就表示前一个版本,每多一个^就表示往前一个,也可以写成HEAD~100

通过commit_id可以版本回退,也可以恢复之后的版本(如果有的话)。

查看所有执行过的命令

还有个命令可以查看所有执行过的命令:

mashaonan@mashaonan:~/Desktop/gti$ git reflog
4114e6e HEAD@{0}: commit: a
40a198e HEAD@{1}: commit: merge
2451d7e HEAD@{2}: checkout: moving from master to dev
08a6450 HEAD@{3}: checkout: moving from dev to master
2451d7e HEAD@{4}: commit: c.txt
08a6450 HEAD@{5}: checkout: moving from master to dev
08a6450 HEAD@{6}: checkout: moving from dev to master

也可以查到每次操作的id值。

撤销修改

git还可以通过命令来撤销对工作区以及暂存区的修改。

git checkout -- file命令可以取消工作区中file文件的操作。

git reset HEAD file可以取消暂存区中的修改,回退到工作区,还需要执行上一句命令才可以取消这个修改。

如果已经commit到了本地分支,就要使用上面的版本回退了。

分支管理

HEAD主要是指向当前分支,我们可以创建其他的分支,新创建的分支会指向当前分支的最新节点。

创建分支

主要使用的命令是:

//创建并且切换到dev分支
git checkout -b dev 

//创建dev分支
git branch dev

//切换到dev分支
git checkout dev

第一条命令是下面两条命令结合起来的效果

master分支切换到dev分支
合并与删除分支

所以我们一般都在dev分支上做开发,然后提交之后,切换到master分支,然后git merge dev去合并dev分支,然后git branch -d dev删除dev分支,如果dev分支没有被merge,删除的时候会出现错误提示,需要git branch -D dev才能删除。

合并分支的时候会出现冲突,也就是,两个分支都在同一个文件上提交了修改,但是修改不同,然后合并的时候就无法增量的合并,也就是无法Fast-Forward合并,这是只增加,不修改原来的数据的合并方式,所以就会出现冲突。

出现冲突之后,需要手动修改相关文件,然后commit到当前分支。

错误情况如下:

mashaonan@mashaonan:~/Desktop/gti$ git merge dev
Auto-merging d.txt
CONFLICT (add/add): Merge conflict in d.txt
Removing 6.828
Automatic merge failed; fix conflicts and then commit the result.

相关文件中的输出结果为:

mashaonan@mashaonan:~/Desktop/gti$ cat d.txt
<<<<<<< HEAD
ddd
=======
jkbfdf df
dff
>>>>>>> dev
出现冲突的分支合并

解决冲突后再提交后的图如下:


解决冲突后的分支

并且可以通过git log的命令来查看分支合并情况:

mashaonan@mashaonan:~/Desktop/gti$ git log --graph --pretty=oneline --abbrev-commit 
*   1c92543 a
|\  
| * 463966f conflicted
| * 8328f90 add
| * 9b62157 rm
| * 4114e6e a
| * 40a198e merge
| * 2451d7e c.txt
* | 7653ac7 ddd
|/  
* 08a6450 stash

--graph参数会显示出合并的log图。

禁止使用Fast forward模式

使用--no-ff参数来合并分支,会强行给当前分支增加一次提交,命令如下:

git merge --no-ff -m "merge with no-ff" dev

-m参数是这次提交附带的信息。

两种merge方式如图所示:

不使用Fast forward模式的merge

使用Fast forward模式的merge

为什么要禁止Fast forward模式,因为禁止了这个模式,可以从git log --graph中查看到曾经合并过分支,否则看不出分支合并的迹象。

stash

如果一条分支上提交了修改,另一条分支是看不到的,但是如果没有修改了但是没有提交,保留在工作区或者暂存区,别的分支是看得到的。

这样可能会导致一些错误,所以有了stash这个功能。比如你正在一个分支上干活,但是突发情况不能让你干完,你必须先去别的分支上干活,这时候就需要用git stash这个命令暂时储存你之前的修改。

这个命令有些trick,就是如果你创建一个文件,但是不写它,git stash是无法发现它的,必须要git add file到暂存区之后才能发现它,但是如果是修改,只要在工作区里修改了,不需要放到暂存区,这个命令也能发现。

可以通过命令git stash list来查看当前存储的工作区和暂存区的状态,在每个分支下都能看到,因为工作区和暂存区的状态每个分支下都能看到。然后可以用git stash pop来恢复第一个stash,类似栈一样,如果用git stash apply的话,还需要使用git stash drop来弹出这个stash

如果多次工作区或者暂存区操作用stash存储,然后恢复的时候没有提交,多次一起恢复,如果是同一个文件的话,则会发生merge冲突。

多人协作

如果你从远程库clone的是master分支,但是你想切换到dev分支,由于你没有clone,所以直接git checkout dev是不行的。

你需要创建dev分支并且是用的是origin/dev分支的内容,命令如下:

git checkout -b dev origin/dev

就会把远程库的dev分支也弄下来,然后可以在上面愉快的操作了。

如果别人也往这个远程库的某个分支上提交代码,我们也正巧往这个分支上提交代码,就很有可能会发生冲突,比如:

mashaonan@mashaonan:~/Desktop/egnahc-hpec$ git push origin dev
To [email protected]:MiracleMa/egnahc-hpec.git
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to '[email protected]:MiracleMa/egnahc-hpec.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.

下面的hint会告诉你应该怎么做,比如使用git pull,把远程库分支的修改pull下来,然后手动合并之后再提交再推送到远程库。

git pull的情况下可能会失败,因为本地的dev分支没有和远程的origin/dev分支链接,所以需要根据提示链接一下:

git branch --set-upstream dev origin/dev
Branch dev set up to track remote branch dev from origin.

标签

标签和分支非常类似,但是分支可以移动,标签不能移动,标签是和一个commit绑定的。

标签有一些常用的命令:

git tag //查看所有标签名字
git tag [name] [commit_id]//对于指定的commit_id创建一个标签,默认为最新的commit
git show [name] //显示name标签的详细信息
git tag -a [name] -m "version" [commit_id] //创建标签另外附带信息,用-m参数,-a参数后面跟tag的名字
git tag -d [name] //删除tag

也可以被推送到远程库,推送命令为:

git push origin [tag] //推送指定名字的tag到远程库
git push origin --tags //一次性推送全部尚未推送到远程库的本地标签

如果要删除远程库的标签,首先需要本地删除
git tag -d [name]
然后从远程删除
git push origin :refs/tags/[name]

git配置

git的配置文件我们来看一下。

首先是当前版本库的配置文件,在./git/config里,打开看到是这样的:

mashaonan@mashaonan:~/Desktop/gti/.git$ cat config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[remote "origin"]
    url = [email protected]:MiracleMa/egnahc-hpec.git
    fetch = +refs/heads/*:refs/remotes/origin/*

这是当前版本库的配置,还有当前用户全局版本库的配置,在~/.gitconfig里:

mashaonan@mashaonan:~/Desktop/gti$ cat ~/.gitconfig 
[user]
    email = [email protected]
    name = MiracleMa

如何修改配置呢?可以直接修改配置文件,可以用命令git config来添加配置,加上参数--global就是当前用户的全局配置,如果不加就是当前版本库的配置。

.gitignore

很多时候我们的工程在本地工作区都编译运行了,但是提交的时候不要编译运行后的执行文件,就需要.gitignore文件来帮忙忽略掉那些不要的文件,但是.gitignore不需要自己手写,github上有各种配置文件了:https://github.com/github/gitignore
,直接拿过来用就行了。

我在本地操作如下:

mashaonan@mashaonan:~/Desktop/gti$ git status
On branch master
Untracked files:
  (use "git add ..." to include in what will be committed)

    a.c

nothing added to commit but untracked files present (use "git add" to track)
mashaonan@mashaonan:~/Desktop/gti$ vim .gitignore
mashaonan@mashaonan:~/Desktop/gti$ git status
On branch master
Untracked files:
  (use "git add ..." to include in what will be committed)

    .gitignore

nothing added to commit but untracked files present (use "git add" to track)
mashaonan@mashaonan:~/Desktop/gti$ git add .
mashaonan@mashaonan:~/Desktop/gti$ git commit -m "gitignore"
[master 7774969] gitignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore

可以看到只有一个文件被commit了,也就是.gitignore,可以看下远程库里的内容。

mashaonan@mashaonan:~/Desktop/gti$ ll
total 32
drwxrwxr-x  3 mashaonan mashaonan 4096 Apr 15 19:54 ./
drwxr-xr-x 23 mashaonan mashaonan 4096 Apr 14 16:04 ../
-rw-rw-r--  1 mashaonan mashaonan    5 Apr 15 19:50 a.c
-rw-rw-r--  1 mashaonan mashaonan   45 Apr 14 13:42 a.txt
-rw-rw-r--  1 mashaonan mashaonan    4 Apr 14 16:35 c.txt
-rw-rw-r--  1 mashaonan mashaonan   51 Apr 14 16:58 d.txt
drwxrwxr-x  8 mashaonan mashaonan 4096 Apr 15 19:53 .git/
-rw-rw-r--  1 mashaonan mashaonan    4 Apr 15 19:50 .gitignore
远程库a.c被ignore了

submodule

子模块可以让你在一个项目中使用别的项目当做子模块,同时还能保持两个模块提交的独立。

如何为你的项目添加一个子模块呢?

git submodule add [email protected]:MiracleMa/6.828.git

就可以为你的项目添加一个子模块,查看此时工作区的状态:

mashaonan@mashaonan:~/Desktop/gti$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD ..." to unstage)

    new file:   .gitmodules
    new file:   6.828

首先可以看到多了一个.gitmodules文件,里面写的是子模块的相关信息:

mashaonan@mashaonan:~/Desktop/gti$ cat .gitmodules 
[submodule "6.828"]
    path = 6.828
    url = [email protected]:MiracleMa/6.828.git

然后可以通过git diff --cache [6.828|--submodule]的命令来显示相关子模块和原先的差别,--submodule命令可以输出更多内容。

提交的时候,会发现远程库里的情况是这样的:

6.828文件夹后面有一串数字

点击6.828之后会进入另外一个远程库

发现6.828这个文件夹,并没有真的被上传到我的远程库,那个目录其实是一个链接,链接到了6.828原来的远程库。

大型的项目一般都需要用到子模块,比如ceph,当时一直不明白为啥有些目录点进去就到了别的库,下载下来是空的。

可以看下ceph的子模块:

[submodule "ceph-object-corpus"]
    path = ceph-object-corpus
    url = https://github.com/ceph/ceph-object-corpus.git
[submodule "src/civetweb"]
    path = src/civetweb
    url = https://github.com/ceph/civetweb
[submodule "src/erasure-code/jerasure/jerasure"]
    path = src/erasure-code/jerasure/jerasure
    url = https://github.com/ceph/jerasure.git
    branch = v2-ceph
[submodule "src/erasure-code/jerasure/gf-complete"]
    path = src/erasure-code/jerasure/gf-complete
    url = https://github.com/ceph/gf-complete.git
    branch = v3-ceph

这只是一小部分,每个都写了path是存放到本仓库的哪个目录下,url是对应的远程库地址,branch是对应的分支。

如何克隆带有子模块的项目呢?

git clone xxx //克隆项目
git submodule init //子模块初始化
git submodule update //克隆子模块

但是这样只能把当前项目的子模块克隆下来,并不能克隆子模块的子模块,所以需要--recursive参数,递归地克隆子模块。

git clone --recursive [url]
//或者
git clone [url]
git submodule update --init --recursive

其余还有很多在子模块上开发的操作,我暂时应该还用不上,以后用上了再查看吧。

pull request

pull request是你要给某个开源项目做共享的时候需要用到的操作。

你需要把那个开源项目fork过来到你的远程库,然后clone到你本地,在某个分支上做一些修改,然后push到你的远程库。

github上切换到你push的分支,在边上有个New pull request的按钮,可以把修改提交给作者,如果作者认可了,就会merge到他的项目里。

创建pull request的按钮

这个方面主要参考GitHub 的 Pull Request 是指什么意思? - beepony的回答 - 知乎

你可能感兴趣的:(Git学习笔记)