全面理解Git

Git简介

 Git的诞生确实是一个有趣的故事,我们知道,当年Linus创建了开源的Linux,从此,Linux系统不断发展,现在已经成为最大的服务器系统软件了。

 但是随着Linux的不断壮大,就需要各种版本控制了,起初Linus带着他的小弟们使用的是BitKeeper(商业版本控制系统),之后呢由于某种原因BitKeeper的公司不让他们使用了,于是Linus自己花了两周时间写出了Git并且开源了(BitKeeper已哭晕在厕所),阿弥陀佛,幸亏BitKeeper不让Linus他们用了,要不然我们现在也不会有这么好用的Git了。

这里先引用一张图解释Git工作原理:

  • Repository:git仓库,就是一个有.git目录的文件夹。有关的一切故事开始的地方。
    • 可以使用git init命令初始化一个git仓库。
    • 也可以使用git clone url命令从服务器上克隆仓库到本地。

    这里要说明的是,clone操作并不是将整个仓库下载下来,而是只下载.git目录。因为关于git的一切秘密都在这个目录里面,只要有了它,git就能复原到仓库的任意版本。

  • Workspace:工作区,又叫工作目录,就是不包括.git目录的项目根目录。我们要在这个目录下进行手头的工作,它就是版本管理的素材库。你甚至可以称任何与工作有关的目录为工作区,只不过没有.git目录git是不认的。
  • Index:暂存区(stage),是一个为提交到版本库做准备的地方。

    使用暂存区的好处是:

    • 它可以实现更小颗粒度的撤销。
    • 它可以实现批量提交到版本库。
  • Remote:远程仓库,就是类似github,coding等网站所提供的仓库。

放入暂存区

git默认是不会把工作区的文件放入暂存区的,如果要将文件或者文件夹放入暂存区,需要手动操作:

  • git add -u <path> :提交所有文件的删,改状态,不包括untracked file
  • git add .:提交所有文件的增、删、该状态
  • git add --no-all <path> :添加所有文件的增,改状态,不包括删除

跟踪内容

  • git status
  • git status -v命令相当于git status命令和git diff --staged之和。
  • git status -vv命令相当于git status命令和git diff之和。

commit

  • git commit :直接提交,终端填写提交日志
  • git commit -m "填写提交记录" :用于提交暂存区的文件
  • git commit -am "填写提交记录": 用于提交跟踪过的文件
  • git commit -amend :在提交时,如果加上 --amend 参数,Git 不会在当前 commit 上增加 commit,而是会把当前 commit 里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit,用这个新的 commit 把当前 commit 替换掉。

diff

比较工作区与暂存区

git diff <filename>

意义:查看文件在工作目录与暂存区的差别。如果还没 add 进暂存区,则查看文件自身修改前后的差别。也可查看和另一分支的区别。

git diff <branch> <filename>

比较暂存区与git仓库(本地库中最近一次commit的内容)

  • git diff --staged [<path>...]
  • git diff --cached [<path>...]

意义:表示查看已经 add 进暂存区但是尚未 commit 的内容同最新一次 commit 时的内容的差异。 也可以指定仓库版本:

  • git diff --cached <commit> <filename>
  • git diff --staged <commit> <filename>

比较工作目录和 Git仓库

git diff <commit> <filename>

意义:查看工作目录同Git仓库指定 commit 的内容的差异。 =HEAD 时:查看工作目录同最近一次 commit 的内容的差异。

比较Git仓库和Git仓库

git diff <commit> <commit>

意义:Git仓库任意两次 commit 之间的差别。

branch

分支在git中是一个模棱两可的概念。

  • 你可以认为它仅仅是一个指针,指向一个commit对象节点。
  • 你也可以认为它是指针指向的commit对象节点追溯到某个交叉节点之间的commit历史。

master分支

刚刚初始化的git仓库,版本库里还没有任何commit对象,而分支一定是指向commit对象的。一旦版本库里有了第一个commit对象,git就会自动生成一个master文件,它就是git的默认分支。不过它并不特殊,只是它充当的是一个默认角色而已。

刚刚初始化的git仓库会显示目前在master分支上,其实这个master分支是假的,git仓库目录下根本没有这个文件。只有等提交历史不为空时才有会真正的默认分支。

HEAD指针

形象的讲,HEAD就是景区地图上标注你当前在哪里的一个图标。

你当前在哪里,HEAD就在哪里。它一般指向某个分支,因为一般我们都会在某个分支之上。

因为HEAD是用来标注当前位置的,所以一旦HEAD的位置被改变,工作目录就会切换到HEAD指向的分支。

但是也有例外,比如我直接签出到某个没有分支引用的commit。这个时候的HEAD就叫做detached HEAD

要知道,只有在初始提交和某个分支之间的commit才是有效的。当你的HEAD处于detached HEAD状态时,在它之上新建的commit没有被任何分支包裹。一旦你切换到别的分支,这个commit(可能)再也不会被引用到,最终会被垃圾回收机制删除。因此这是很危险的操作。

如果不小心这么做了,要么在原地新建一个分支,要么将已有的分支强行移动过来。确保它不会被遗忘。

branch操作

  • git branch branchName : 创建分支
  • git checkout branchName: 切换分支
  • git checkout -b branchName:创建并切换分支
  • git branch -v :查看各个本地分支的最后一次提交
  • git branch -vv :查看本地分支与远程分支的关联情况
  • git branch :列出所有本地分支
  • git branch -r :列出所有远程分支
  • git branch -a :列出所有本地分支和远程分支
  • git branch -d branchName:删除分支
  • git branch -m|-M oldBranch newBranch :重命名分支(-M 为强制命名)

查看本地分支和远程分支的关联关系

git branch -vv

关联本地分支和远程分支

git branch --set-upstream-to=<upstream> [branchName]
或者
git branch -u <upstream> [branchName]
复制代码

git branch --set-uptream-to=origin/dev dev或者git branch -u origin/dev dev是指把dev分支和远程分支origin/dev进行关联,如果省略dev分支,则默认关联当前分支

解除远程分支的关联

git branch --unset-upstream [branch]
复制代码

git branch --unset-upstream dev是指解除dev分支和远程分支的关联,如果省略dev,则默认解除当前分支与远程分支的关联

branch操作注意事项

  • HEAD 指向的 branch 不能删除。如果要删除 HEAD 指向的 branch,需要先用 checkout 把 HEAD 指向其他地方。

checkout

当你checkout分支的时候,git做了这么三件事情

  1. 将HEAD指向那个分支的最后一次commit
  2. 将HEAD指向的commit里所有文件的snapshot替换掉Index区域里原来的内容
  3. 将Index区域里的内容填充到Working Directory里

所以你可以发现,HEAD、Index、Working Directory这个时候里的内容都是一模一样的。

  • 一般会误解为,Index中的内容是空的,只有git add后才会有东西。实际上不是,Index里一直是有东西的。
  • 在进行checkout 的时候,工作目录中含有未追踪的文件时,可以直接切换分支。当工作目录中含有修改文件或者暂存文件,是不可以直接切换分支的。
  • git checkout命令如果不带任何参数,默认会加上HEAD参数。而HEAD指针指向的就是当前commit。所以它并不会有任何签出动作。

git checkout -- <file>

首先提醒一下,这个参数的写法不是git checkout --<file>,而是git checkout -- <file>。其实它和git checkout file的效果是一样的,但实际上是由细微差别的。

独立的--参数在Linux命令行中指的是:视后面的参数为文件名。当 checkout后面跟文件名的时候,最好加上独立的--,以免歧义。

例如,在一个项目中正好有一个名为text.txt的分支名,那加独立的--就不会操作分支,而是操作文件。

reset

git reset命令与git checkout命令的区别在于,它会把HEAD指针和分支指针一起移动,如果HEAD指针指向的是一个分支指针的话。

我们前面说过使用git checkout命令从有分支指向的commit切换到一个没有分支指向的commit上,这个时候的HEAD指针被称为detached HEAD。这是非常危险的。

不带文件参数的reset

reset实际上有3个步骤,根据不同的参数可以决定执行到哪个步骤(--soft, --mixed, --hard)。

  1. 移动HEAD到所指向的commit(--soft)
  2. 执行第1步,将Index区域更新为HEAD所指向的commit里包含的内容,工作区内容不变(--mixed,或者不加参数,因为--mixed时默认参数)
  3. 执行第1、2步,将Working Directory区域更新为HEAD所指向的commit里包含的内容,让工作目录看起来像第2步中的索引。为什么说像第2步中的索引,这是因为工作目录含有Untracked文件,git中不会存有历史记录,所以Untracked 文件对git 来说是无效文件。(--hard)

带文件参数的reset,文件暂存区内容撤回工作区

git reset命令后面也可以跟文件名,它的作用是将暂存区的内容重置为工作区的内容,是git add -- 的反向操作。

git reset -- 命令是git reset HEAD --mixed -- 的简写。在操作文件时,参数只有默认的--mixed一种。

clean

git clean -n

说明:是一次clean的演习, 告诉你哪些文件会被删除. 记住他不会真正的删除文件, 只是一个提醒.

git clean -f

说明:删除当前目录下所有没有track过的文件. 他不会删除.gitignore文件里面指定的文件夹和文件, 不管这些文件有没有被track过.

git clean -f <path>

说明:删除指定路径下的没有被track过的文件.

git clean -df

说明:删除当前目录下没有被track过的文件和文件夹.

git clean -xf

说明:删除当前目录下所有没有track过的文件. 不管他是否是.gitignore文件里面指定的文件夹和文件.

merge

指定一个 commit,把它合并到当前分支。具体来讲,merge 做的事是: 从特性分支 和当前分支 分叉的位置起,把特性分支 的路径上的所有 commit 的内容一并应用到当前分支,然后在当前分支自动生成一个新的 commit,当前分支的commits 增加一个,特性分支的commits 不变

合并操作之前必须保证暂存区内没有待提交内容,否则git会阻止合并。这是因为合并之后,git会将合并后的版本覆盖暂存区。所以会有丢失工作成果的危险。

至于工作区有待添加到暂存区的内容,git倒不会阻止你。可能git觉得它不重要吧。

不过最好还是保持一个干净的工作区再执行合并操作。

merge操作

  • git merge commit : 合并特性分支到当前分支
  • git merge -abort :放弃解决冲突,取消 merge

合并冲突(merge conflict)

先理解冲突的概念

<<<<<<<<<<<<<<<<<<<<<< HEAD  
Creating a new branch is quick & simple.
=========================
Creating a new branch is quick AND simple.
>>>>>>>>>>>>>>>>>>>>>> somebranch 
复制代码
  1. 在 <<<<HEAD 和 ==== 之间的是我们当前分支的内容,称为ours
  2. 在 ==== 和 >>>>feature1 之间的是somebranch上面对应的内容,称为theirs

在merge时,ours指代的是当前分支,theirs代表需要被合并的分支。

解决步骤:

  1. 使用 git diff查询冲突内容,冲突查询出来后,有三种解决方法
    1. 使用当前分支内容
    2. 使用特性分支内容
    3. 当前分支和特性分支内容都要
  2. 修改冲突内容
    1. 使用我们当前的内容,执行git checkout --ours conflict-file-name
    2. 使用somebranch分支上面的内容,执行git checkout --theirs conflict-file-name
    3. 如果都需要使用,则 vim confict-file-name,直接编译冲突文件,修改之后wq退出编辑。
  3. 执行 git add conflict-file-name
  4. 执行 git rebase --continue如果执行之后没有成功,有其他提示,按照提示执行即可。
  5. 还没好,淡定,重复前面步骤,直接返回正常分支。

rebase

基础用法

展开来说就是,把你特性分支以及它所在的 commit 串,以指定的目标分支 为基础,依次重新提交一次,变基后特性分支的commits增加,目标分支的commits不变,如果需要同步目标分支和指定分支,需要对目标分支执行一次merge。

  • git rebase baseBranch:将当前所在分支变基到目标分支(baseBranch)
  • git rebase baseBranch topicBranch:将特性分支(topicBranch)变基到目标分支(baseBranch),可以省去先切换到特性分支,在对特性分支执行变基命令的操作

topicBranch 变基到 master的注意事项

// 1. 切换到topicBranch
git checkout topicBranch
// 2. 从topicBranch变基到master
git rebase master
// 3. 切回master,再把topicBranch 合并到 master,把master移到最新的commit
git checkout master
git merge branch1
复制代码

为什么要从 topicBranch 来 rebase,然后再切回 master 再 merge 一下这么麻烦,而不是直接在 master 上执行 rebase?
  这是因为rebase 后的 commit 虽然内容和 rebase 之前相同,但它们已经是不同的 commits 了。如果直接从 master 执行 rebase 的话,会导致 master 上之前的 commit 被剔除了。如果这两个 commit 之前已经在中央仓库存在,这就会导致没法 push 了。
  所以,为了避免和远端仓库发生冲突,一般不要从 master 向其他 branch 执行 rebase 操作。而如果是 master 以外的 branch 之间的 rebase(比如 branch1 和 branch2 之间),就不必这么多费一步,直接 rebase 就好。

变基冲突(rebase conflict)

先理解冲突的概念

<<<<<<<<<<<<<<<<<<<<<< HEAD  
Creating a new branch is quick & simple.
=========================
Creating a new branch is quick AND simple.
>>>>>>>>>>>>>>>>>>>>>> somebranch 
复制代码
  1. 在 <<<<HEAD 和 ==== 之间的是我们当前分支的内容,称为ours
  2. 在 ==== 和 >>>>feature1 之间的是somebranch上面对应的内容,称为theirs

rebase时,ours指代的是目标分支,theirs指代的是当前分支,造成的原因是,在rebase隐含了一个git checkout topicBranch的过程,将HEAD从local分支变成somebranch分支。

解决步骤:

  1. 使用 git diff查询冲突内容,冲突查询出来后,有三种解决方法
    1. 使用当前分支内容
    2. 使用特性分支内容
    3. 当前分支和特性分支内容都要
  2. 修改冲突内容
    1. 使用我们当前的内容,执行git checkout --theirs conflict-file-name
    2. 使用somebranch分支上面的内容,执行git checkout --ours conflict-file-name
    3. 如果都需要使用,则 vim confict-file-name,直接编译冲突文件,修改之后wq退出编辑。
  3. 执行 git add conflict-file-name
  4. 执行 git rebase --continue如果执行之后没有成功,有其他提示,按照提示执行即可。
  5. 还没好,淡定,重复前面步骤,直接返回正常分支。

Tag

标签种类

git标签又分为两种,轻量级标签和含附注标签。

  • 轻量级标签和分支的表现形式是一样的,仅仅是一个指向commit的指针而已。只不过它不能切换,一旦贴上就无法再挪动了。
  • 含附注标签才是我们理解的那种标签,它是一个独立的git对象。包含标签的名字,电子邮件地址和日期,以及标签说明。

标签操作

  • 列出所有标签 git tag
  • 创建轻量级标签 git tag <Tag 名字>
  • 创建附注标签 git tag -a <Tag 名字> -m <注释文字>
  • 切换到标签 git checkout <Tag 名字>
  • 查看指定Tag的详细信息 git show <Tag 名字>
  • 删除标签 git tag -d <Tag 名字>
  • 提交标签到远程,这里并不区分轻量级标签和含附注标签
    git push origin <Tag 名字> 将tagName提交到git服务器
    或者
    git push origin --tags 将本地所有的标签一次性提交到git服务器
  • 删除远程Tag
    git push origin --delete tag <Tag 名字>
    或者
    1. 删除本地Tag git tag -d <Tag 名字>
    2. 重新push到远程 git push origin :refs/tags/<Tag 名字>

提交错误

修复当前提交的错误

  • 当场再写一个修复这个错误的commit
  • git commit -—amend

"amend" 是「修正」的意思。在提交时,如果加上 --amend 参数,Git 不会在当前 commit 上增加 commit,而是会把当前 commit 里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit,用这个新的 commit 把当前 commit 替换掉。所以 commit --amend 做的事就是它的字面意思:对最新一条 commit 进行修正。

写错的不是最新的提交,而是倒数第二个

如果不是最新的 commit 写错,就不能用commit --amend来修复了,而是要用 rebase。不过需要给 rebase 也加一个参数:-i

例如修改倒数第二个提交
修改倒数第二个提交
git rebase -i HEAD^^
或者
git rebase -i HEAD~2
复制代码

修改倒数第几个提交,就在git rebase -i之后指定HEAD的偏移量为几。 rebase后面跟着的是一个自己分支的某个提交记录,实际上就是对rebase -i 后面跟着的那条记录开始(不包括开始点)往后的所有commit重新设置基础点,把这些commit重新生成一遍再接在这个新的基础点后面,对于文件历史变化来说,这个其实就是空操作.

丢弃刚写的提交

git reset --hard HEAD^

git remote

说明:git remote用于管理主机名。

列出所有远程主机

git remote

查看远程主机网址

git remote -v

clone 指定主机名

git clone -o jQuery https://github.com/jquery/jquery.git

说明:在克隆远程仓库时,指定远程主机叫做jQuery

查看该主机的详细信息

git remote show <主机名>

添加远程主机

git remote add <主机名> <网址>

删除远程主机

$ git remote rm <主机名>

远程主机的重命名

git remote rename <原主机名> <新主机名>

git fetch

说明:一旦远程主机的版本库有了更新,需要将这些更新取回本地,这时就要用到git fetch命令。git fetch命令通常用来查看其他人的进程,因为它取回的代码对你本地的开发代码没有影响。

取回所有分支的更新

git fetch <远程主机名>

取回特定分支的更新

git fetch <远程主机名> <分支名>

所取回的更新,在本地主机上要用远程主机名/分支名的形式读取。比如origin主机的master,就要用origin/master读取。

取回远程主机的更新以后,可以在它的基础上,使用git checkout命令创建一个新的分支。
git checkout -b newBrach origin/master
指在origin/master的基础上,创建一个新的分支 newBranch

git pull

说明:git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂。

git pull <远程主机名> <远程分支名>:<本地分支名>

例如:git pull origin next:master表示取回origin主机的next分支,与本地的master分支合并。

  1. 远程分支是与当前分支合并,则冒号后面的部分可以省略
    git pull origin next
    上面的命令表示,取回origin/next分支,在与当前分支合并。实质上,等同于先做git fetch ,在做 git merge
    1. git fetch origin
    2. git merge origin/next
  2. 当前分支与远程分支存在追踪关系,git pull就可以省略远程分支名。
    git pull origin
  3. 当前分支只有一个追踪分支,连远程主机名都可以省略。
    git pull

git push

说明:git push命令用于将本地分支的更新,推送到远程主机。它的格式与git pull命令相仿。

git push <远程主机名> <本地分支名>:<远程分支名>

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

git push origin master
上面命令表示,将本地的master分支推送到origin主机的master分支。如果后者不存在,则会被新建。

如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支。

git push origin :master
等同于
git push origin --delete master
复制代码

上面命令表示删除origin主机的master分支。

虽然要严格遵守分支中的提交对象发布到公共仓库,不要对该分支进行任何衍合操作的原则,但是对于自己的 temp 分支或者 feature 分支随时删除或者将历史分支进行美化操作,是不会造成太大的问题。如果进行了modified,并且执行了

git add -u fileName
git commit --amend
复制代码

再次进行push的时候会报错,此时可以使用

git push --force-with-lease origin temp:temp
复制代码

用于解决push失败的问题。

stash

说明:在 Git 中,stash 指令可以帮你把工作目录的内容全部放在你本地的一个独立的地方,它不会被提交,也不会被删除,你把东西放起来之后就可以去做你的临时工作了,做完以后再来取走,就可以继续之前手头的事了。

具体说来,stash 的用法很简单。当你手头有一件临时工作要做,需要把工作目录暂时清理干净,那么你可以:

git stash

就这么简单,你的工作目录的改动就被清空了,所有改动都被存了起来。

然后你就可以从你当前的工作分支切到 master 去给你的同事打包了……

打完包,切回你的分支,然后:

git stash pop

你之前存储的东西就都回来了。

注意:从来没有被 add 过的文件不会被 stash 起来,因为 Git 会忽略它们。如果想把这些文件也一起 stash,可以加上 -u 参数,它是 --include-untracked 的简写。就像这样:git stash -u

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值