白话GIT

文章更新

  1. 20170308-初次成文
  2. 20170406-大更新,完成了“白话GIT”的初稿

为什么会有这篇文章

很多时候,git是一个让人又爱又恨的东西,初期接触觉得很酷很方便,但是要系统的学习起来,很难,命令很多,想系统的靠一篇文章来学透git的想法,对我这种智商为负的人来说是不可能,只好在使用的过程中逐渐掌握。所以,如果你凑巧看到了这篇文章,觉得git很难学,那么你可以稍微安慰一下,因为还有我为你垫底。

另外一个原因呢,就是发现网上很多的教程文章,都是总结性质的,而不是"白话"性质的,所以就自己白话一篇。

关于git的那些糊涂账

最新的想法是,网上的系统的GIT教程,都弄得非常的高深莫测,在自己接触GIT的一年左右的时间内,尤其是最近这一个月,发现StackOverFlow和GIT的官方算得上的解释概念非常清晰的地方了,遇到问题GOOGLE一下,会讲解得很清楚,而其他的地方,经常是长篇大论,从头讲到尾,弄得很多时候无从下手。

由于一直看不下去系统介绍GIT的教程,都是用到什么学什么,所以还是很一些概念无法有彻底的明白。比如一些很常见的名词,什么是orgin, master, branch, repo?

简单的说,origin是远程库的默认名字,这个名字一般用的时候最好修改成更有意义的名字,比如github, gitcafe等,master并不是库名字,而是主要的意思,比如,master branch,表示主要分支。

localmaster则分别表示本地远程,比如常见的是local master branch,表示的意思是本地主分支

当使用git init folder的时候,GIT会创建了一个GIT管理的目录(如果你已经在目录内了,命令变为git init),这个目录就是GIT的仓库,也被称做版本库,这个目录(仓库版本库)内部主要包括暂存区master分支,是不是感觉一句话就让你乱了?别着急,下面更乱。

先说说git clone

先说说,GIT一般是你本地的一个库,或者说是一个目录,一个仓库,那么如果你不是一开始就本地建立,肯定要先从远程的网站复制一个目录到你的本地来,对其进行更改、上传、更行等等操作。那么命令就比较简单了,一句话

git clone git@github.com:username/他的项目名字.git 你的本地目录名字

上面命令组合中,第一个命令就是我们要学习的GIT,第一个参数clone,就是复制克隆的意思,第二个参数是远程仓库名字,第三个参数是你想把远程的仓库复制到本地的名字,你可以重新命令一个新的目录名,比如下面这句

git clone https://github.com/tinyvane/bug.n.git mybugn

git clone命令用法

上面就是把博主的一个公开项目复制到你的本地,本地目录是mybugn(本文下文都会用这个目录名字)

然后你进入到这个目录后,如果你的shell装了必要的插件,目录名字会变成

git目录的名字

那么现在你本地已经有了mybugn这个目录,这个目录下面比较重要的文件,一是.git目录,这个目录下面保存着关于这个git项目的配置信息,.gitignore文件,用来记录不需要GIT管理的文件名,.gitmodules记录着关于子项目的信息。

好了,下面,来说说基本的和常用的git命令,我觉得如果由浅入深的话,很多时候根本分不清,不如按照场景分类,比如上传代码时候的命令,合并代码时候的命令,冲突的时候,等等。

推送命令

执行过git clone后,项目已经从远程到了本地,那么本地修改了代码之后,如何让他们从本地远程呢?很简单

git add . #添加本地所有文件到GIT管理下
git commit -m 'update' #提交,update是关于这次提交的说明
git push origin master #推送内容

是不是觉得没什么难的?但是这里面涉及originmaster这两个东西,是什么呢?

如果你的项目不大,origin就是GIT给远程仓库默认起得名字,而master主要分支的意思,当执行git push origin master的时候,GIT会把当前分支下(git checkout了的分支)的所有提交全部推送到远程仓库下(这里是origin)下的master分支下。

GIT的初始配置

在推送之前,你需要指定一些东西哦,还要让GIT知道你是谁。

git config --global user.name "foolman at thinkpad"
git config --global user.email foolman@gmail.com

如果用了 --global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

要检查已有的配置信息,可以使用 git config --list 命令:

还有一些其他的东西可以指定,比如GIT使用哪个文本编辑器、何种差异比较工具等等,具体可以看GIT官方说明1.5 起步 - 初次运行 Git 前的配置

关于远程仓库

首先说命令

  1. git remote -v 查看对应的远程分支,如果结果为空,就说明你需要指定了。
  2. git remote add [alias] [url] 将本地文件和云端仓库映射起来,这步很重要,这里[url]应该写类如https://git.coding.net/tinyvane/hexo.git这样的远程仓库地址
  3. git remote rm [name] 删除远程仓库
  4. git remote set-url --push[name][newUrl] 修改远程仓库

上面的的[alias]是别名的意思,具体是指你可以用一个短小精悍的名字,来替代那长长的https://github.com/tinyvane/tinyvane.git

下面用实例来说说

git remote add origin git@github.com:tinyvane/randomfiles.git

在这里,我们将远程分支git@github.com:tinyvane/randomfiles.git赋予了别名origin,英文的意思是初始的意思,或者是的意思。这个origin可以随便修改的。

如果想删掉呢? git remote rm origin

是不是很简单?但是当这样做了之后,就不能执行git push origin master命令,GIT会提示fatal: 没有配置推送目标,就是说GIT觉得远程没有对应的推送位置了,需要重新指定一次

``` bash
git remote add origin git@codingnet1:tinyvane/hexo.git
git push --set-upstream origin master
```

这样,才又重新建立起本地master和远程master的对应关系

好了,这里介绍了一个远程默认分支的概念,其实这个就是github上和你本地分支对应的远程分支,如果说得再深入一些,是两个repositoryrepository存储库的意思,一般简称为库repo。两个repo,一个是remote repo,在远程服务器上,另外一个是local repo,在自己本地电脑上。

所以,一个标准的GIT远程或者本地地址,包含3项信息,分别是serverrepobranch,用中文分别就是服务器分支

继续上栗子,比如https://git.coding.net/tinyvane/hexo.git

这句中git.coding.netserver,是服务器地址tinyvanerepo,是hexo.gitbranch,是分支。但是这里要说一下,这个hexo.git,并不是GIT所说的分支或者branch,这里面

如果按照本地和远程来区分,那么就变为6个概念

remote serverremote reporemote branch,分别是远程服务器远程仓库远程分支

local serverlocal repolocal brach,分别是本地服务器本地仓库本地分支

当你使用git clone,从远程服务器上,这里以github为例,你就是克隆了远程服务器上的仓库里的一个分支,复制到本地,在git push之前,你所有的文件改动,都是在你本地服务器的local repo上进行的,你的改动(local repo)和远程仓库(remote repo)是独立的,就是说你无论怎么改动本地,在git push之前,都不会影响远程仓库的。

好了,继续说。

当你git clone之后,git会自动将你下载过的那个远程仓库,给一个别名(alias),默认是origin

下面要说的东西,开始有些不好理解了,请做好思想准备。

远程仓库指定完别名之后,GIT会建立一个指向远程仓库remote repo)的某个分支(就是branch的概念了)的指针

继续用最开始的例子

https://github.com/tinyvane/hexo.git,这条信息中,除去github.com这个服务器名,接下来就是用仓库名/分支名的方式来表示的分支路径。

所以tinyvane/hexo.git,就是这样一条路径。

好了,懂了这个概念,就可以继续说默认分支的问题了,本地的分支,默认是origin,那么远程的分支呢?

远程分支,首先是有一个master branch的概念,就是主线分支,这个分支中基本上都是发布的代码,其他分支呢?假如你新开了一个betadev分支,用来发布调试版本的代码,那么这个betadev分支,就是对这个master branch的修修补补,当修补成熟了,你可以将betadev分支的代码合并到master branch中去。

所以,远程的默认分支,就是master

懂了这些,我们再回头看我们开始的时候使用的推送命令

git push origin master

git push命令的完整表示形式是 git push [remote-name] [branch-name]

注意,分支(上面语句中的[branch-name])推送顺序的写法是<来源地>:<目的地>,就是从左到右的顺序,所以git push<本地分支>:<远程分支>,而git pull<远程分支>:<本地分支>。这是一个方向的问题,理解了就非常好记忆,拉取代码是从 远程 到 本地 拉回来,推送是从 本地 到 远程 推过去。这种:的写法,提前说一下,称为引用规格(refspec)。这个概念,先记着,不用想太多,继续往下看。

所以,git push origin master这条命令的完整的形式,应该是 git push origin refs/remotes/origin/master(本地的,带着remote字样):refs/origin/master(远程的,不带remotes哦,这点刚开始容易混淆),并且里面都没有heads字样哦!

这里说明一下,refs/heads/master 是你本地的的工作分支名称,而带有remotes字样的,比如refs/remotes/origin/master ,一般叫做“跟踪分支”,跟踪分支并不是真正的分支,而是指向远程仓库中上一次提交的那个分支。而“master”或者“origin”并不是固定的,比如origin,当你第一次git clone的时候,如果不指定别名,那么git就认为本地对应的远程仓库别名是Origin,而master也是一个默认的名称,表示主要分支的意思。

所以,现在我们可以总结一下,refs/remotes/origin/masterrefs/heads/master基本是一样的,分别是远程分支本地的追踪分支,以及本地的master分支,这两个分支具有对应的关系,而refs/heads/origin/master则是在远程仓库上的分支。

所以

git push origin refs/heads/master:refs/heads/origin/master

git push origin refs/remotes/origin/master:refs/heads/origin/master

效果相同,当然了,如果想省事,也可以直接简化成为

git push origin 

我们再来想想看,这个refs/heads/master:refs/heads/origin/master的写法,在哪里出现过呢?

在本地的GIT仓库路径下执行命令

cat .git/config

会看到类似这样的结果:

[remote "origin"]
        url = git@codingnet1:tinyvane/hexo.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master

这个[remote]字段,就是表示远程仓库部分,下面的[branch]表示本地仓库部分。

从上面的结果中可以得知如下信息:

远程分支是refs/heads/*,远程分支对应的本地分支则是:refs/remotes/origin/*,这个地方的规则是:,分别表示源,和目标,

远程仓库的默认分支是origin,他的路径是git@codingnet1:tinyvane/hexo.git,那么+refs/heads/*:refs/remotes/origin/* ,这句话中的加号啊,星号啊又是什么鬼??

这里附上GIT官方说明中文版

先说这句话怎么来的吧,在最开始说git clone的时候,提到了这样一条语句

git remote add origin git@github.com:tinyvane/randomfiles.git

这条命令,会在.git目录下的config文件中添加一个小节

[remote "origin"]
        url =  git@github.com:tinyvane/randomfiles.git
        fetch = +refs/heads/*:refs/remotes/origin/*

并在其中指定远程版本库的名称(origin)、URL 和一个用于获取操作(fetch)的引用规格(refspec)

引用规格的格式由一个可选的 + 号和紧随其后的 : 组成,其中 是一个模式(pattern),代表远程版本库中的引用; 是那些远程引用在本地所对应的位置。 + 号告诉 Git 即使在不能快进的情况下也要(强制)更新引用。

默认情况下,引用规格由 git remote add 命令自动生成, Git 获取服务器中 refs/heads/ 下面的所有引用,并将它写入到本地的 refs/remotes/origin/ 中。 所以,如果服务器上有一个 master 分支,我们可以在本地通过下面这种方式来访问该分支上的提交记录:

git log origin/master
git log remotes/origin/master
git log refs/remotes/origin/master

这三条命令的作用相同, 因为 Git 会把它们都扩展成 refs/remotes/origin/master

如果想让 Git 每次只拉取远程的 master 分支,而不是所有分支,可以把(引用规格的)获取那一行修改为:

fetch = +refs/heads/master:refs/remotes/origin/master

这仅是针对该远程版本库的 git fetch 操作的默认引用规格。 如果有某些只希望被执行一次的操作,我们也可以在命令行指定引用规格。 若要将远程的 master 分支拉到本地的 origin/mymaster 分支,可以运行:

git fetch origin master:refs/remotes/origin/mymaster

注意2,如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。

比如git push origin :master,这样一句话,master前面的冒号之前,并没有通常所说的<本地分支>的名字,那么git就会认为你要推送一个空的本地分支到远程覆盖远程master分支,相当于新建了远程的master分支内容。

那么如果想把git push origin master拓展一下,应该是什么样的呢?

那就是

git push origin refs/remotes/origin/master:refs/origin/master

是不是看着有点蛋疼?但是却对这些格式比较清晰了呢?

那么git push origin master就是表示,将本地仓库的分支内容,推送到远程服务器的origin仓库的master分支,这样说,是不是理解了呢?如果没有理解,请评论,我将继续改进,将这个白话git教程做到更好。

好了,光说远程的事情,回头看看本地。

git clone之后,git为远程的仓库也好,分支也好,做了这么多工作之后,git对本地下载过来的那些东西做了什么呢?

git会为本地repo(仓库),同样命名为origin,本地的那个branch,同样命名为master branch,也就是,本地也是master branch(主分支),为什么?因为本地和远程是对应的。

git同样也为这个本地的master branch建立了一个指针,随着你不断进行改动文件git addgit commit等操作,这个指向master branch指针也会不断移动。不懂?没关系,我刚开始的时候也不懂,请继续看就好了。

在你的git仓库,使用命令,查看所有远程跟踪及本地分支branch情况123

`git branch -a` #(或--all)

会显示

* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

上图

如果你的bash环境下开了颜色,可以看到红色的结果是远程分支,绿色的是当前工作的分支,白色的是本地分支。远程有两个分支,绿色哪一行,星号*表示当前分支所在的位置,箭头表示追踪。请注意,HEAD并不是一个分支,所以这里remotes/origin/HEAD并不是说这里有一个叫HEAD的分支,而是一个指针,它指向你最近工作过的分支上。

可以使用 git reset HEAD^ 命令来让指针的位置往前挪一个提交,也可以使用git show HEAD或者git show origin/HEAD来查看本地或者远程的指针位置。

或者使用 git remote set-head origin -a 进行设置,使用参数-d删除

那么,这里显示结果第一行中的 * master,就表示当前你工作在master分支上,remotes/origin/HEAD表示本地的HEAD(也就是指针)正在追踪远程的origin/master分支,第三行,远程的另外一个分支是remotes/origin/master,这是一个tracking branch,直译为追踪分支。关于追踪分支的概念先不多说,请大家慢慢了解,这是另外一种分支类型。

使用下面的命令,查看所有远程分支

`git branch -r` #(或--remotes)

结果会显示

  origin/HEAD -> origin/master
  origin/master

上图更生动一些,如下图

可以看到,远程有一个分支origin/master,master就是local branch,origin/master是remote branch(master is a branch in the local repository. remotes/origin/master is a branch named master on the remote named origin)

在我个实际个人使用中,发现远程分支无意中多了一个remotes/origin/origin/master的远程分支,如何删掉呢?如果按照上面说的,使用语句git

用下面一个图来表示一下:

在这个图中,我们共有C1 C2 C3 C4 C5 5次提交,

git push origin master

在简单的条件下,我们就是远程一个分支origin,本地一个分支

git remove add

下载代码

git clone https://github.com/tinyvane/tinyvane.git localpath

这是克隆操作,也是下载的一种,可以使用https或者ssh方式,如果是ssh,上述命令中的后半段,可以修改为 git@github.com:tinyvane/tinyvane.git localpath。当然了,github.com还可以使用别名(这个别名和git别名不是一个概念,但是思路一样),那就涉及修改.ssh目录下的config文件了。下文继续说,或者可以看我的其他文章。

git pull

git fetch

git merge

参考文章

  1. What’s the meaning of ‘origin’ in ‘git push origin master’
  2. Is “refs/heads/master” same as “refs/remotes/origin/master” in Git?
  3. Git 的origin和master分析
  4. 10.5 Git 内部原理 - 引用规格
  5. Git branch named origin/HEAD -> origin/master
  6. git 常用命令(含删除文件)
  7. Git查看、删除、重命名远程分支和tag