一篇入门 -- Git

一. Git 介绍

Git作为一款分布式的版本控制工具,作为一名程序员,是必须要掌握的.
最初由林纳斯·托瓦兹(Linus Torvalds)创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计.后来git内核已经成熟到可以独立地用作版本控制,使的很多著名的软件都开始使用git进行版本控制.

了解更多,可点击
维基百科 - Git廖雪峰的Git博客

原文地址

Git虽然是文件的版本控制工具,但它所管理的并不是文件,比较贴切的说,它控制的应该是文件的修改.

目前Git有很多桌面应用,可以方便实现各种功能,比如我推荐的GitKraken,但是如果要更好使用桌面应用,对于下文中的概念好命令还是很有必要的.

1.1 Git和SVN的对比

Git Svn
分布式的 集中式的
把内容按元数据(记录改动)方式存储 按文件
下载后,脱网也可查看全部log 需要联网需要联网查看log
没有一个全局的版本号
内容存储使用的是SHA-1哈希算法,内容完整性要优于SVN 差于Git

二. Git入门

2.1 基础概念介绍

git共有四个

  • 工作区(Working Area)
  • 暂存区(Stage)
  • 本地仓库(Local Repository),仅自己可见
  • 远程仓库(Remote Repository),全组成员可见

同时还有五种状态

  • 未修改(Origin)
  • 已修改(Modified)&未追踪(Untracked)
  • 已暂存(Staged)
  • 已提交(Committed)
  • 已推送(Pushed)

image

对于图上的所有命令,之后都会有详细的介绍,各位不要捉急.

2.2 Git的安装和GitHub的使用

Git的安装就不介绍了,网上很多.
GitHub是通过Git进行版本控制的软件源代码托管服务,并且它免费,在小组开发时,我们可以将代码托管到GitHub上,非常方便.官网https://github.com/

2.3 创建版本库

版本库,说白了就是我们要交给Git管理的文件夹.

$ cd ~/workspace/git/
$ git init

命令行进入到目标目录,然后输入git init,就会在当前目录下生成一个.git的文件夹,此时,这个版本库就创建好了.所有在此目录下和子目录下的文件改动,都会被git发现..git文件夹就是这个工程的本地版本库.

我们也可以直接在GitHub clone代码到本地,方法如下:

$ git clone git@github.com:michaelliao/bootstrap.git

2.4 基本使用

此时在git目录下,新建文件helloWorld.scala.然后想继续修改,但是怕最近的修改出错,想可以随时回到当前状态,那要怎么做呢?如果我想让这个开发的文件,在小组内的其他成员也见,又要怎么做呢?

git add => 暂存区

第一步就是先将要保存的文件加入暂存区:

$ git add helloWorld.scala

注意,add可以一次提交多个文件,如git add a.scala b.scala,也可以多次提交,如git add a.scala git add b.scala.

如果使用git add --all,提交当前工作区的全部更改到暂存区
如果使用git add .,提交当前目录下的全部更改到暂存区

git status 状态查看器

使用下面命令,可以查看当前工作区的修改情况:

$ git status

Git非常清楚地告诉我们,helloWorld.scala还从来没有被添加过,所以它的状态是Untracked。如果我们已经添加过helloWorld.scala文件,在对其进行修改,那么它就是modified状态

git checkout 放弃修改

如果想放弃现在没有add,可以使用下面命令,将工作区的改动还原

$ git checkout -- helloWorld.scala

注意,这个--是必须要有的,不然它就会切换分支了,分支概念后面说.

可是,如果add之后,又想放弃目前暂存区的更改呢?,就要使用下面的语句,然后重复上面的步骤.

git reset HEAD helloWorld.scala

关于 reset 和 HEAD 的概念,之后会讲到.

git commit => 本地仓库

第二步,将缓存区的内容提交到本地仓库中:

$ git commit -m "wrote a hello file"

此时,我们已经将本次更改保存到本地仓库,现在就可以放心大胆的继续开发hello了.因为我们已经有了一个现在时间的代码快照,随时可以回到快照.

-m后面的String,是对于本次commit的备注,可以没有,也可以使任何内容,但建议是一段可描述本次提交的语言,方便日后自己追溯,也方便其他人可以了解本次提交,方便他大胆的更新或合并你本次的代码.关于更新和合并下文讲.

现在可以使用git status查看一下当前什么状态.

git push

但是当前的hello文件只有你自己可见,要想小组的其他人也能看到并且编辑,需要将它同步到远程.方法如下:

$ git push origin master

这样就讲本地仓库中提交的代码push到远程的master分支上了.此时,其他小组成员就可以看到你之前编写的文件并且可以同步和修改了.

git pull

那么其他成员如何下载你的最新代码呢?使用如下命令即可:

$ git pull

如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

三. 版本穿越

如果想要回到之前的某个版本,应该如何做呢?

3.1 git历史书

首先应该查询我之前的所有操作,通过命令:

$ git log

查看最近的操作,但是内容有些多,可以加上参数--pretty=oneline简化输出信息.

输出信息类似下面:

f078079284c567571286ef7a168f095af9acdd03 (HEAD -> Clock-0704, tag: v1.0, origin/Clock-0704) Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
bbb87262b8d8d61d85fc6579ab46527e431ee176 (origin/alex-0704) 修复无限bug
4f0a047023f8a4902f40f03fb5f6040775a9e1ee Merge remote-tracking branch 'origin/alex-0704' into Clock-0704

第一个空格前面的一串数字,就是那次commit的commit id(提交版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准.为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了.

知道了历史书,我们就可以定位具体的穿越位置,使用

$ git reset --hard 4f0a047023f8a4902f40f03fb5f6040775a9e1ee

我们就穿越成功了.

reset 的 --hard参数后面讲解

最后跟的commit id不用写全,git会自动查询,但也不要写的太少,推荐写七位左右

此时有个语法糖, 如果我们只是想返回最近的上一次提交,可以使用

$ git reset --hard HEAD^

返回上上次呢git reset --hard HEAD^^,上三次呢?
没错,是你想的那样,但是有别的方法git reset --hard HEAD~3,不然100次不是很累.

使用git log查询一下历史,已经看不到4f0a之后的提交了.我们穿越成功了,但是git log没有了之后的commit id,如果我们反悔了,岂不是回不去了.

3.2 git万年历

不要怕,在Git中,总是有后悔药可以吃的,Git提供了一个命令git reflog用来记录你的每一次命令:

$ git reflog

输出信息类似下面:

f078079 (HEAD -> Clock-0704, tag: v1.0, origin/Clock-0704) HEAD@{0}: commit (merge): Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
4f0a047 HEAD@{1}: commit (merge): Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
89529a2 HEAD@{2}: commit: report over

再次穿越:

$ git reset --hard f078079

没错,我又回来了.

这里要说一下,git切换分支的速度非常快,因为git把你的每次commit编程了链表的一个点.内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD换个指向而已.

3.3 代码回滚的讲解

代码回滚的方式分三种Reset、Checkout、Revert
那么应该如何使用和选择呢?

reset 详解

reset 是移动HEAD指针,如果HEAD指针指向了之前提交的commit id,就等于放弃了近期的代码更改,回到了当时,如上面的例子,但是它比想象的要强大,同时支持几个参数,如下:

  • --mixed – 默认选项。缓存区和你指定的提交同步,但工作目录不受影响,也就是缓存区移入工作区,然后 工作区 > 本地仓库
  • --soft – 缓存区和工作目录都不会被改变,也就是最近修改优先
  • --hard – 缓存区和工作目录都同步到你指定的提交,也就是本地仓库优先

checkout 详解

主要是处理工作区的修改

revert 详解

创建一个修改来修改之前的提交,不会影响历史,是最安全的回滚办法.好像Ctrl + Z

文件层面操作

git resetgit checkout 命令也接受文件路径作为参数。这时它的行为就大为不同了。它不会作用于整份提交,参数将它限制于特定文件。

如:

git reset HEAD~2 helloWorld.scala

会把最近的第二次提交中的helloWorld.scala文件提取出来放到暂存区.

而:

git checkout HEAD~2 helloWorld.scala

会把最近的第二次提交中的helloWorld.scala文件提取出来放到工作区.

总结

  • git revert当做Ctrl + Z,它不会修改历史记录,并且会生成记录.所以执行之前,要先stash.
  • git reset HEAD用来撤销没有提交的更改.
  • git checkout主要是处理工作区中没有add的修改
命令 作用域 常用情景
git reset 提交层面 在私有分支上舍弃一些没有提交的更改
git reset 文件层面 舍弃缓存区中的更改
git checkout 提交层面 切换分支或查看旧版本
git checkout 文件层面 舍弃工作目录中的更改
git revert 提交层面 在公共分支上回滚更改
git revert 文件层面 (然而并没有)

四. 分支概念

如果对于同一个项目,领导要张三去开发新功能的同时,要李四去修复当前版本的一个bug呢?

这时,我们就要做一个比穿越时间更神奇的事情了,就是创建平行宇宙 => 分支.

分支可以将某个时间节点的代码分别放到两个平行线上,这两条线上的开发互不影响,只有在两者需要合并的时候重合即可.

4.1 分支基础

在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。

就像这样:
image

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

image

你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!

git checkout

首先,我们创建dev分支

$ git branch dev

查看现在都有哪些分支:

$ git branch
* master
  dev

有*号的表示是当前分支

然后切换到dev分支:

$ git checkout dev

也有语法糖,可以二步合一

$ git checkout -b dev

参数-b就是分支不存在,则创建分支

现在我们在dev分支已经开发完了,要把代码统一回master分支.要先切换回master
合并分支:

$ git checkout master
$ git merge dev

此时,用

$ git log --graph --pretty=oneline --abbrev-commit

可以查看分支合并过程

合并完成,删除之前的dev分支

$ git branch -d dev

五. 番外

5.1 stash 暂存

可以缓存工作区的修改到stash中,那在什么场景使用呢?

比如,你正在开发新模块,但之前分支中有改动,需要合并分支,可是你们项目中有一些配置文件,如数据库配置等你连接到了自己的数据库上,每次合并分支还要重新修改,很麻烦,这时就可以先stash,然后merge,在把stash中的文件修改提取出来.

如何提取:

$ git apply
$ git pop

这两句的功能一样,都是拿出刚才stash的文件修改,区别在于,apply只是提取,不会删除刚才的stash,而pop就像弹栈一样,在提取的同时删除了stash.

可以使用git stash list查看全部stash列表.使用git stash apply stash@{0}恢复指定stash

5.2 Rebase 变基

不是很好理解,就当做是最后一次的 merge 吧。
因为rebase 后,两个分支的修改记录都会合并为一个分支的记录,好像另一个分支从来没有出现过一样。

5.3 tag 标签管理

可以理解为commit的收藏功能.

$ git tag v1.0

使用上面命令,就会在最近的commit上创建一个tag,也可以使用 git tag v1.0 commitId给指定commit打tag

删除标签

$ git tag -d v1.0

从远程删除。删除命令也是push,但是格式如下:

$ git push origin :refs/tags/v0.9

5.4 .gitignore 忽略文件

忽略文件的原则是:

  1. 忽略操作系统自动生成的文件,比如缩略图等;
  2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
  3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

5.5 设置别名

$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
原文地址:https://www.cnblogs.com/clockq/p/9330546.html