Miracle_ma的专栏

马天猫重新起航

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 git@github.com: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 git@github.com: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 <shaonan.ma@gmail.com>
Date:   Sat Apr 14 14:08:43 2018 +0800

    a

commit 40a198e833a36598257ea0ae672e57c1006cf406
Author: MiracleMa <shaonan.ma@gmail.com>
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 git@github.com:MiracleMa/egnahc-hpec.git
 ! [rejected]        dev -> dev (fetch first)
error: failed to push some refs to 'git@github.com: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 = git@github.com:MiracleMa/egnahc-hpec.git
    fetch = +refs/heads/*:refs/remotes/origin/*

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

mashaonan@mashaonan:~/Desktop/gti$ cat ~/.gitconfig 
[user]
    email = shaonan.ma@gmail.com
    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 <file>..." 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 <file>..." 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 git@github.com:MiracleMa/6.828.git

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

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

    new file:   .gitmodules
    new file:   6.828

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

mashaonan@mashaonan:~/Desktop/gti$ cat .gitmodules 
[submodule "6.828"]
    path = 6.828
    url = git@github.com: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的回答 - 知乎

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Miracle_ma/article/details/79968969
文章标签: Git
个人分类: Git学习笔记
上一篇MIT6.824 Lab1 MapReduce
下一篇CSAPP lab1-6总结贴
想对作者说点什么? 我来说一句

git学习笔记

2013年07月03日 751KB 下载

GIT学习笔记

2015年09月07日 869KB 下载

git自我学习笔记

2017年11月10日 4.47MB 下载

GitHub学习笔记

2015年08月26日 864KB 下载

Git 学习记录

2017年09月29日 1.25MB 下载

Data structure and algorithm notes中文版

2016年02月24日 11.15MB 下载

没有更多推荐了,返回首页

关闭
关闭