什么是版本控制?
简单来说,版本控制在软件开发中,可以帮助程序员进行代码的追踪、维护、控制等等一系列的操作。
对于我们日常开发,我们常常面临如下一些问题,通过版本控制可以很好的解决:
不同版本的存储管理:
重大版本的备份维护:对于很多重大的版本,我们会进行备份管理;
恢复之前的项目版本:当我们开发过程中发生一些严重的问题时,想要恢复之前的操作或者回到之前某个版本;
记录项目的点点滴滴:如果我们每一个功能的修改、bug的修复、新的需求更改都需要记录下来,版本控制可以很好的解决;
多人开发的代码合并:项目中通常都是多人开发,将多人代码进行合并,并且在出现冲突时更好的进行处理;
版本控制的史前时代(没有版本控制):人们通常通过文件备份的方式来进行管理,再通过diff命令来对比两个文件的差异;
CVS(Concurrent Versions System)
SVN(Subversion)
Git(Linus的作品)
CVS和SVN都是是属于集中式版本控制系统(Centralized Version Control Systems,简称 CVCS)
这种做法带来了许多好处,特别是相较于老式的本地管理来说,每个人都可以在一定程度上看到项目中的其他人正在做些什么。
但是集中式版本控制也有一个核心的问题:中央服务器不能出现故障:
Git是属于分布式版本控制系统(Distributed Version Control System,简称 DVCS)
电脑上要想使用Git,我们需要先对Git进行安装:
在window操作系统按照默认配置全局安装即可;
Bash,Unix shell 的一种,Linux 与 Mac OS X 都将它作为默认 shell。
Git CMD
Git GUI
既然已经在系统上安装了 Git,你会需要做几件事来定制你的 Git 环境:
Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量:
- 如果在执行 git config 时带上 --system 选项,那么它就会读写该文件中的配置变量;
- 由于它是系统配置文件,因此你需要管理员或超级用户权限来修改它。(开发中通常不修改)
- 你可以传递 --global 选项让 Git 读写此文件,这会对你系统上 所有 的仓库生效;
- 你可以传递 --local 选项让 Git 强制读写此文件,虽然默认情况下用的就是它;
安装Git后,要做的第一件事就是设置你的用户名和邮件地址。
git config --global user.name "coderwhy"
git config --global user.email "[email protected]"
检测当前的配置信息:git config --list
查看某一项配置信息(例如查看用户名)
git config user.name
Git 并不会在你输入部分命令时自动推断出你想要的命令:
如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
我们需要一个Git来管理源代码,那么我们本地也需要有一个Git仓库。
通常有两种获取 Git 项目仓库的方式:
方式一:初始化Git仓库
git init
方式二:从Git远程仓库
git clone https://github.com/coderwhy/hy-react-web-music.git
现在我们的电脑上已经有一个Git仓库:
那么我们需要对文件来划分不同的状态,以确定这个文件是否已经归于Git仓库的管理:
已跟踪的文件又可以进行细分状态划分:
在工作时,你可以选择性地将这些修改过的文件放入暂存区;
然后提交所有已暂存的修改,如此反复;
我们在有Git仓库的目录下新建一个文件,查看文件的状态:
git status
Untracked files:未跟踪的文件
我们也可以查看更加简洁的状态信息:
git status –s
git status --short
左栏指明了暂存区的状态,右栏指明了工作区的状态;
跟踪新文件命令:使用命令 git add 开始跟踪一个文件。
git add aaa.js
跟踪修改的文件命令:如果我们已经跟踪了某一个文件,这个时候修改了文件也需要重新添加到暂存区中;
通过git add . 将所有的文件添加到暂存区中:
git add .
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。
在实际开发中,这个文件通常不需要手动创建,在必须的时候添加自己的忽略内容即可;
比如右侧是创建的Vue项目自动创建的忽略文件:
现在的暂存区已经准备就绪,可以提交了。
- git commit –m "提交信息"
如果我们修改文件的add操作,加上commit的操作有点繁琐,那么可以将两个命令结合来使用:
git commit -a -m "修改了bbb文件"
Git 中所有的数据在存储前都计算校验和,然后以 校验和 来引用。
在提交了若干更新,又或者克隆了某个项目之后,有时候我们想要查看一下所有的历史提交记录。
这个时候我们可以使用git log命令:
git log
git log pretty= oneline
git log --pretty=oneline --graph
如果想要进行版本回退,我们需要先知道目前处于哪一个版本:Git通过HEAD指针记录当前版本。
我们可以通过HEAD来改变Git目前的版本指向:
git reset --hard HEAD^
git reset --hard HEAD~1000
git reset --hard 2d44982 (可以不用写完id,写id的前几位也行,前提是保证这几位id是唯一的,如果不是在继续多写几位,直至唯一)
但是这个时候会有一个问题,这个时候我们如果反悔了,想要退回降级版本之前的那个版本,这个时候使用 git log 命令,会发现之前现在最新的id是现在降级版本的id,我们之前版本的id丢失了。这个时候我们就该使用 git reflog 这个命令,这个命令非常详细的记录着各版本的id,并且还记录着操作步骤。
git reflog
什么是远程仓库(Remote Repository)呢?
那么如何创建一个远程仓库呢?
目前我们有如下方式可以使用Git服务器:
常见的远程仓库有哪些呢?目前比较流行使用的是三种:
对于私有的仓库我们想要进行操作,远程仓库会对我们的身份进行验证:
如果没有验证,任何人都可以随意操作仓库是一件非常危险的事情;
目前Git服务器验证手段主要有两种:
因为本身HTTP协议是无状态的连接,所以每一个连接都需要用户名和密码:
下面有一些 Git Crediential 的选项:
使用下面命令查看是否安装
git config credential.helper
// 输出 manager-core,证明安装成功
- 例如其中一种方法是使用自动生成的公钥-私钥对来简单地加密网络连接,随后使用密码认证进行登录;
- 另一种方法是人工生成一对公钥和私钥,通过生成的密钥进行认证,这样就可以在不输入密码的情况下登录;
- 公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管;
// 生成方式一
ssh-keygen -t ed25519 -C “your email"
// 生成方式二
ssh-keygen -t rsa -b 2048 -C “your email"
注意:.pub后缀文件便是公钥,我们要把这个公钥里面的内容复制,然后添加到 github或其他第三方开源git平台上,然后我们就能使用ssh方式验证,而不再需要登录验证了。
查看远程地址:比如我们之前从GitHub上clone下来的代码,它就是有自己的远程仓库的:
git remote
git remote –v :-v是—verbose的缩写(冗长的)
添加远程地址:我们也可以继续添加远程服务器(让本地的仓库和远程服务器仓库建立连接):
git remote add
git remote add gitlab http://152.136.185.210:7888/coderwhy/gitremotedemo.git
重命名远程地址:git remote rename 旧名字 新名字
移除远程地址:git remote remove 名字
问题一:当前分支没有track的分支
原因:当前分支没有和远程的origin/master分支进行跟踪
在没有跟踪的情况下,我们直接执行pull操作的时候必须指定从哪一个远程仓库中的哪一个分支中获取内容;
git pull 配置的远程仓库名 远程分支名
如果我们想要直接执行git fetch是有一个前提的:必须给当前分支设置一个跟踪分支:
设置上游分支前,先使用 git fetch 命令,让本地仓库知道远程仓库有哪些分支
git branch --set-upstream-to=origin/master
问题二:合并远程分支时,拒绝合并不相干的历史
原因:我们将两个不相干的分支进行了合并:
Git refusing to merge unrelated histories on rebase - Stack Overflow
简单来说就是:过去git merge允许将两个没有共同基础的分支进行合并,这导致了一个后果:新创建的项目可能被一个毫不怀疑的维护者合并了很多没有必要的历史,到一个已经存在的项目中,目前这个命令已经被纠正,但是我们依然可以通过--allow-unrelated-histories选项来逃逸这个限制,来合并两个独立的项目;
git merge --allow-unrelated-histories
从远程仓库clone代码:将存储库克隆到新创建的目录中;
git clone http://152.136.185.210:7888/coderwhy/gitremotedemo.git
将代码push到远程仓库:将本地仓库的代码推送到远程仓库中;
默认情况下是将当前分支(比如master)push到origin远程仓库的;
git push (这个必须要保证当前分支名,与远程分支名相同,不然会报错。这就必须要指定详细的分支名)
git push origin master
从远程仓库fetch代码:从远程仓库获取最新的代码
- git fetch
- git fetch origin master
- git merge
- git merge origin/master
- 注意:这个时候,你会发现,为什么使用fetch与merge方式指定的远程仓库分支的方式不同,这是固定的牢记即可
从远程仓库pull代码:上面的两次操作有点繁琐,我们可以通过一个命令来操作
- git pull
- 上述命令相等于
- git fetch + git merge(rebase)
情况一: 你到公司之后公司已经有项目,并且有远程仓库了
- git add .
- git commit -m "提交"
- git pull -> git fetch/git merge
- git push
情况二:开发一个全新的项目(由你来搭建的)
创建一个远程仓库
- git clone 项目地址
- 在clone 下来文件夹中开始搭建整个项目
- git add .
- git commit -m ""
- git push
- 创建一个本地仓库和搭建本地项目
- git remote add origin 项目地址
- git branch --set-upstream-to=origin/master
- git fetch
- git merge --allow-unrelated-histories
- git push
当我们使用git push命令时(使用前必须先配置远程仓库地址),有时候我们本地仓库分支名与远程仓库分支名不同,这个时候我们我们即使设置了上游分支也会报错。我们必须使用git push命令必须要指定详细的本地分支名和远程分支名,让它们产生关系。
git push origin 本地分支名:远程分支名
注意:这个origin是我们在添加远程仓库地址所指定的名字,或者我们也可以新创建一个与远程仓库分支相同的名称进行推送
这个时候我们有一个问题,就是明明我们设置了上游分支,按道理我们可以直接使用 git push 命令,不需要跟参数呢?为什么,这种方式行不通呢?这个问题的产生就与git 的默认push方式参数的配置有关系了。
git config push.default 参数
当前这个里面的参数是simple,代表的是当我们直接使用 git push 而省略后面的参数,这个时候,git就会在远程仓库中查找是否有相同的分支名,如果没有则会报错。通过这个参数,我们可以知道,git在push操作时根本就没有使用到我们设置的上游分支,当然也就不会起作用了。如果我们想要使用到我们设置的上游分支,这个时候我们只需要将里面的simple参数改为upstream,这个我们就能使用 git push,后面不在需要跟上任何参数,不会发生报错问题了。
git config push.default upstream
注意:这种设置发生只对当前项目生效,如果想要全局生效后面要跟上 --global参数
关于push.default的更详细说明,请参见文档:Git - git-config Documentation, 也可以使用下列命令查看 git config --help
对于重大的版本我们常常会打上一个标签,以表示它的重要性:
创建标签:
git tag v1.0
git tag -a v1.1 -m "附注标签"
默认情况下,git push 命令并不会传送标签到远程仓库服务器上。
在创建完标签后你必须显式地推送标签到共享服务器上,当其他人从仓库中克隆或拉取,他们也能得到你的那些标签;
// 推送单个标签
git push origin v1.0
// 推送所有标签
git push origin --tags
删除本地tag:
要删除掉你本地仓库上的标签,可以使用命令 git tag -d
删除远程tag:
要删除远程的tag我们可以通过 git push
–delete
检出tag:
查看所有tag
git tag
几乎所有的版本控制系统都以某种形式支持分支。
使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。
在进行提交操作时,Git 会保存一个提交对象(commit object):
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。
Git 的 master 分支并不是一个特殊分支。
Git 是怎么创建新分支的呢?
很简单,它只是为你创建了一个可以移动的新的指针;
比如,创建一个 testing 分支, 你需要使用 git branch 命令:
git branch testing
那么,Git 又是怎么知道当前在哪一个分支上呢?
也很简单,它也是通过一个名为 HEAD 的特殊指针;
git checkout testing
如果我们指向某一个分支,并且在这个分支上提交:
你也可以切换回到master分支,继续开发:
创建新分支的同时切换过去
让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。
继续开发后续的新功能,正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补, 你将按照如下方式来处理:
切换到tag v1.0.0的版本,并且创建一个分支hotfix;
想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git checkout 命令:
git checkout –b hotfix
分支上开发、修复bug:
切换回master分支,但是这个时候master分支也需要修复刚刚的bug:
所以我们需要将master分支和hotfix分支进行合并;
git checkout master
git merge hotfix
如果我们希望查看当前所有的分支,可以通过以下命令:
git branch # 查看当前所有的分支
git branch –v # 同时查看最后一次提交
git branch --merged # 查看所有合并到当前分支的分支
git branch --no-merged # 查看所有没有合并到当前分支的分支
如果某些已经合并的分支我们不再需要了,那么可以将其移除掉:
git branch –d hotfix # 删除当前分支
git branch –D hotfix # 强制删除某一个分支
由于Git上分支的使用的便捷性,产生了很多Git的工作流:
比如以下的工作流:
比较常见的git flow
- 以
/ 的形式命名的;
- 你需要通过fetch来获取最新的远程分支提交信息;
操作一:推送分支到远程
- git push origin
操作二:跟踪远程分支
- git checkout --track
/
如果你尝试检出的分支 (a) 不存在且 (b) 刚好只有一个名字与之匹配的远程分支,那么 Git 就会为你创建一个跟踪分支;
- 方式一:git checkout --track
/ - 方式二:git checkout
操作三:删除远程分支
如果某一个远程分支不再使用,我们想要删除掉,可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。
git push origin --delete
(--delete也可以简写为 -d)
合并冲突(conflict)
我们通过pull从Git远程仓库获取到分支内容后会自动进行合并(merge),但是并非所有的情况都可以正常的合并,某些情况下合并会出现冲突:
不同的文件合并时不会发生冲突,但是相同的文件合并时可能会发生冲突,这个时候,我们手动修改冲突部分即可解决问题。
在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。
什么是rebase呢?
// 切换分支
git checkout experiment
// 合并master
git rebase master
rebase是如何工作的呢?
我们可以再次执行master上的合并操作:
// 切换到master分支
git checkout master
// 合并分支,这样head指针,就会指向experiment
git merge experiment
注意:删除远程分支或标签的命令写错了,正确的应该是
git push
–delete