一、简介
介绍Git之前,还得先介绍下 版本控制系统(VCS), 和它的发展历史
纵观版本控制系统的发展历史,广义上讲,版本控制工具的历史可以分为三代:
第一代
第一代版本控制系统被称为本地版本控制系统。通过加锁将并发执行转换成顺序执行。 一次只能有一个人处理文件。具体流程如下:首先,应该把文件放在一个服务器上,方便使用者上传或下载文件;其次,任何人想对文件修改时,需要先把这个文件加锁,通过checkout指令,使得其他人无法修改;最后,当修改完成之后,需要释放锁,通过checkin指令,形成一个新的版本,存放到服务器端。
第一代版本控制系统主要有 RCS、SCCS(1972年发布)和 DSEE(被认为是 Atria ClearCase 的前身)。目前,有些项目还在使用!
悲观锁
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。
第二代
第二代版本控制系统被称为集中式版本控制系统(Centralized Version Control Systems,CVCS),其对同步修改更加宽容,但有一个明显的限制,用户必须在允许提交之前将当前修订合并到他们的工作中。
由上图可看到,在集中式版本控制系统中,如果服务器嗝屁了,那么所有的开发者就只能干瞪眼了!因为,SVN 对于项目的管理是依赖于服务器中的中心仓库的!我们的更改必须要提交到服务器中的中心仓库。
第二代版本控制系统主要有 CVS、SourceSafe、Subversion、Team Foundation Server、SVK。
乐观锁
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
乐观锁一般会使用版本号机制或 CAS 算法实现。
第三代
第三代版本控制系统被称为分布式版本控制系统(Distributed Version Control Systems,DVCS),其允许合并和提交分开。在每个使用者电脑上就有一个完整的数据仓库,没有网络依然可以使用。
由上图可看到,分布式式版本控制系统也可以有个服务器端的仓库,用来同步各开发者的私有仓库!在分布式版本控制系统中,每个参与者的本地也会有一个完整的仓库。及时服务器端崩溃,我们仍然可以使用 Git(仅在本地仓库管理我们的代码),在网络具备时,再和服务器进行同步即可!
第三代版本控制系统主要有 Bazaar、Git、Mercurial、BitKeeper,、Monotone。目前,第三代版本控制系统已经大有一同江湖的趋势!
那么市面上常用的版本控制系统有哪些呢?
SVN 一个集中式版本控制系统(Centralized VCS)
SVN(Subversion 的缩写)是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统。SVN 由 CollabNet 公司于 2000 年资助并发起开发,目的是创建一个更好用的版本控制系统以取代 CVS。
2000 年 2 月,CollabNet 联系了 Open Source Development with CVS(Coriolis, 1999)的作者 Karl Fogel,问他是否愿意为这个新项目工作。这时 Karl 已经在和他的朋友 Jim Blandy 讨论一个新的版本控制系统的设计。他不仅已经起好了名字 “Subversion”,而且有了 Subvesion 资料库的基本设计。
经过 14 个月的编码,在 2001 年 8 月 31 号,Subversion 可以“自我寄生”了。就是说,Subversion 开发人员停止使用 CVS 管理 Subversion 的源代码,开始使用 Subversion 代替。
2009 年 11 月,Subversion 被 Apache Incubator 项目所接收。2010 年 1 月,正式成为 Apache 软件基金会的一个顶级项目。
以下是几款常用的 SVN 客户端图形化软件:
-
TortoiseSVN:开源软件,在 Windows 上非常受到欢迎的一套客户端软件,它与资源管理器集成得相当不错,可以透过资源管理器在文件或目录上用鼠标右键的菜单完成 SVN 的操作。官方网站:Home · TortoiseSVN
-
SnailSVN:Mac OS X 平台下类似 TortoiseSVN 的图形化 SVN 客户端,可自动标记文件状态,并通过右键菜单提供各种常用 SVN 功能。 网站:SnailSVN
-
Ankhsvn:将 subversion 的操作集成进 Visual Studio 的 SVN 客户端软件。官方网站:http://ankhsvn.open.collab.net/
-
Subclipse:将 Subversion 的操作集成进 Eclipse 的 SVN 客户端软件。官方网站:http://subclipse.tigris.org/
-
SmartSVN:同时支持 Mac、Linux、Windows。是一款收费软件。
BitKeeper
BitKeeper 是一套 BitMover 公司开发的分布式版本控制软件,它曾是一款专有软件。BitKeeper 是最初的分布式源代码控制系统。BitKeeper 的许多概念取自 TeamWare(Larry McVoy 在 Sun 公司时开发的产品)。
BitMover 公司 CEO Larry McVoy 与 Linus 曾是好友, Larry 说服 Linus 在内核开发中使用 BitKeeper。而 BitKeeper 在免费使用的许可证中加入很多限制条件,惹恼了内核开发者,最终促使 Linus 开发出了毁灭 BitMove r的 Git。
2016 年 5 月 11 日,BitKeeper 宣布以 Apache 2.0 许可证开源。
Git 一个分布式版本控制系统(Distributed VCS)
在 Linux 开源的初期,Linux 开源项目的代码是 linus 本人通过 linux 命令 diff 和 patch 两条命令手动完成。随着 Linux 代码越来越壮大,靠 Linus 一个人来手动合并已经不现实。2002 年,Linus 选择了一个商业版本控制系统 BitKeeper 作为 Linux 内核的代码管理工具(BitKeeper 的开发商 BitMover 授权 linux 社区免费使用)。但是,免费使用是有很多的限制的,因此 linux 社区的大佬开始破解 BitKeeper。其中,samba 的作者 andrew 破解成功了。但是被 BitMover 公司发现,收回免费使用权。
迫不得已,Linus 选择了自己开发一个分布式版本控制工具以替代 BitKeeper。linus 闭关一个月,写出了 Git。在一个月后,Git 成功接管了 Linux 社区的版本控制工作,并且开始开源。维基百科中,有如下历史记录:
-
2005 年 4 月 3 日,开始开发 Git。
-
2005 年 4 月 6 日,项目发布。
-
2005 年 4 月 7 日,Git 就可以作为自身的版本控制工具了。
-
2005 年 4 月 18 日,发生第一个多分支合并。
-
2005 年 4 月 29 日,Git 的性能就已经达到了 Linus 的预期。
-
2005 年 6 月 16 日,Linux 核心 2.6.12 发布,那时 Git 已经在维护 Linux 核心的源代码了。
-
在 2005 年 7 月 26 日,Linus 功成身退,将 Git 的维护交给另外一个 Git 的主要贡献者 Junio C Hamano。
在 Linus Torvalds 开发出 Git 分布式版本控制系统 11 年后的 2016 年,BitKeeper 宣布在 Apache 2.0 许可证下开源。
二、Git常用命令
-
git config --global user.name "你的名字" 让你全部的Git仓库绑定你的名字
-
git config --global user.email "你的邮箱" 让你全部的Git仓库绑定你的邮箱
-
git init 初始化你的仓库
-
git add . 把工作区的文件全部提交到暂存区
-
git add ./<file>/ 把工作区的<file>文件提交到暂存区
-
git commit -m "xxx" 把暂存区的所有文件提交到仓库区,暂存区空空荡荡
-
git remote add origin https://github.com/name/name_cangku.git 把本地仓库与远程仓库连接起来
-
git push -u origin master 把仓库区的主分支master提交到远程仓库里
-
git push -u origin <其他分支> 把其他分支提交到远程仓库
-
git status查看当前仓库的状态
-
git diff 查看文件修改的具体内容
-
git log 显示从最近到最远的提交历史
-
git clone + 仓库地址下载克隆文件
-
git reset --hard + 版本号 回溯版本,版本号在commit的时候与master跟随在一起
-
git reflog 显示命令历史
-
git checkout -- <file> 撤销命令,用版本库里的文件替换掉工作区的文件。我觉得就像是Git世界的ctrl + z
-
git rm 删除版本库的文件
-
git branch 查看当前所有分支
-
git branch <分支名字> 创建分支
-
git checkout <分支名字> 切换到分支
-
git merge <分支名字> 合并分支
-
git branch -d <分支名字> 删除分支,有可能会删除失败,因为Git会保护没有被合并的分支
-
git branch -D + <分支名字> 强行删除,丢弃没被合并的分支
-
git log --graph 查看分支合并图
-
git merge --no-ff <分支名字> 合并分支的时候禁用Fast forward模式,因为这个模式会丢失分支历史信息
-
git stash 当有其他任务插进来时,把当前工作现场“存储”起来,以后恢复后继续工作
-
git stash list 查看你刚刚“存放”起来的工作去哪里了
-
git stash apply 恢复却不删除stash内容
-
git stash drop 删除stash内容
-
git stash pop 恢复的同时把stash内容也删了
-
git remote 查看远程库的信息,会显示origin,远程仓库默认名称为origin
-
git remote -v 显示更详细的信息
-
git pull 把最新的提交从远程仓库中抓取下来,在本地合并,和git push相反
-
git rebase 把分叉的提交历史“整理”成一条直线,看上去更直观
-
git tag 查看所有标签,可以知道历史版本的tag
-
git tag <name> 打标签,默认为HEAD。比如git tag v1.0
-
git tag <tagName> <版本号> 把版本号打上标签,版本号就是commit时,跟在旁边的一串字母数字
-
git show <tagName> 查看标签信息
-
git tag -a <tagName> -m "<说明>" 创建带说明的标签。 -a指定标签名,-m指定说明文字
-
git tag -d <tagName> 删除标签
-
git push origin <tagname> 推送某个标签到远程
-
git push origin --tags 一次性推送全部尚未推送到远程的本地标签
-
git push origin :refs/tags/<tagname> 删除远程标签<tagname>
-
git config --global color.ui true 让Git显示颜色,会让命令输出看起来更醒目
-
git add -f <file> 强制提交已忽略的的文件
-
git check-ignore -v <file> 检查为什么Git会忽略该文件
git commit提交规范
type: commit 的类型
feat: 新特性
fix: 修改问题
refactor: 代码重构
docs: 文档修改
style: 代码格式修改, 注意不是 css 修改
test: 测试用例修改
chore: 其他修改, 比如构建流程, 依赖管理.
scope: commit 影响的范围, 比如: route, component, utils, build...
subject: commit 的概述, 建议符合 50/72 formatting
body: commit 具体修改内容, 可以分为多行, 建议符合 50/72 formatting
footer: 一些备注, 通常是 BREAKING CHANGE 或修复的 bug 的链接.
三、Git异常场景处理
场景1:git 文件名大小写的坑
当你创建了一个 Components.jsx 的文件,后来给它改了个名,components.jsx, 会发现一个神奇的现象:git 对这个改动没有跟踪记录;
git 默认情况下是不区分大小写的,需要单独设置
git config core.ignorecase false
场景2:刚刚提交的代码,发现写错了怎么办?
刚提交了一个代码,发现有几个字写错了:
怎么修复?
当场再写一个修复这几个错别字的 commit?可以是可以,不过还有一个更加优雅和简单的解决方法:commit -—amend。
"amend" 是「修正」的意思。在提交时,如果加上 --amend 参数,Git 不会在当前 commit 上增加 commit,而是会把当前 commit 里的内容和暂存区(stageing area)里的内容合并起来后创建一个新的 commit,用这个新的 commit 把当前 commit 替换掉。所以 commit --amend 做的事就是它的字面意思:对最新一条 commit 进行修正。
具体地,对于上面这个错误,你就可以把文件中的错别字修改好之后,输入:
git add 笑声.txt
git commit --amend -m 'XXX'
Git 会把你带到提交信息编辑界面。可以看到,提交信息默认是当前提交的提交信息。你可以修改或者保留它,然后保存退出。然后,你的最新 commit 就被更新了。
场景3:想直接丢弃刚写的提交?
有的时候,刚写完的 commit 写得实在太烂,连自己的都看不下去,与其修改它还不如丢掉重写。这种情况,就可以用 reset 来丢弃最新的提交。
git reset --hard HEAD^
// --hard 标志告诉 git 要完全重置工作目录和暂存区,去匹配最后一次提交。在这个过程中,所有未提交的改动和新添加的东西都会被删除。
// HEAD 是一个指向你最后一次提交的指针
// 如果你只是想回滚到之前的一个特定提交
git reset --hard commit_id
// --soft 参数:仅仅在本地库移动 HEAD 指针。
// --mixed 参数:在本地库移动 HEAD 指针、重置暂存区。
// --hard 参数:在本地库移动 HEAD 指针、 重置暂存区、重置工作区。
场景4:需求代码一顿敲,自信push,结果发现推错了分支?
方法1:reset
# 取消最新的提交,然后保留现场原状
git reset HEAD~ --soft
git stash
# 切换到正确的分支
git checkout feature2
git stash pop
git add . # 或添加特定文件
git commit -m "commit message"
方法2:cherry-pick(摘樱桃)
git log //在dev分支找到要合并的commit记录
git checkout feature2
# 把主分支上的最新提交摘过来
git cherry-pick commitId //需合并到feature2的commitId
# 再删掉主分支上的最新提交
git checkout dev
git reset HEAD~ --hard
场景5:一个需求分支代码commit了太多次,合并到master前想简化提交记录?
新建分支就改了一行代码,推送前看了下提交记录,
发现修改了n次,想要精简提交记录,可以使用
git rebase -i <branchName>
// -i 是希望调整commit message
// branchName目的分支
pick完当前分支相对目标分值的commit 之后,会提示代码冲突,解决完需要
git rebase --continue // 继续变基操作
git push -f //强推
场景6:代码已经 push 上去了才发现写错?
(1)出错的内容在你自己的 branch
需要修改的commit 未 push 到远端,可以直接对需要修改的commit进行操作,然后push;
若修改的commit 已经 push 到远端,由于你在本地对已有的 commit 做了修改,这时你再 push 就会失败,因为中央仓库包含本地没有的 commits。但这个和前面讲过的情况不同,这次的冲突不是因为同事 push 了新的提交,而是因为你刻意修改了一些内容,这个冲突是你预料到的,你本来就希望用本地的内容覆盖掉中央仓库的内容。那么这时就不要乖乖听话,按照提示去先 pull 一下再 push 了,而是要选择「强行」push:
git push origin branch1 -f
-f 是 --force 的缩写,意为「忽略冲突,强制 push」。
(2)出错的内容已经合并到 master
同事的工作都在 master 上,你永远不知道你的一次强制 push 会不会洗掉同事刚发上去的新提交。所以除非你是人员数量和行为都完全可控的超小团队,可以和同事做到无死角的完美沟通,不然一定别在 master 上强制 push。
在这种时候,你只能退一步,选用另一种策略:增加一个新的提交,把之前提交的内容抹掉。
有两种回退方案可供选择 git revert 和 git reset
git revert commit_id
// git revert是用一次新的commit来回滚之前的commit,此次提交之前的commit都会被保留;
// git revert --abort:冲突发生后,当前的操作会回到指令执行之前的样子,回到原始的状态,相当于什么事没有发生
// git revert --quit:冲突发生后,从反做操作行为中退出,该指令会保留文件冲突
// git revert --skip:冲突发生后,跳过此次反做操作
// git revert --continue:冲突发生后,解决冲突并add后,执行该命令,会继续之前的操作流程
git reset [--soft | --mixed | --hard] [HEAD]
// git reset是回到某次提交,提交及之前的commit都会被保留,但是此commit id之后的修改都会被删除;
注意:在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现;但是git reset是直接把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit还会被引入。