身为一名程序员,我们除了自己单人开发项目,更多的情况需要多人协同开发。如果你使用硬盘拷贝或者发送压缩包之类的方式(虽然我们公司常用),那肯定是不如 版本控制系统 来的安全与高效。不考虑硬盘丢失或文件损坏等意外情况,拷贝的代码合并就很令人头疼,本文就来带大家快速上手 分布式版本控制系统Git 。
1 版本控制系统的分类
1.1 集中式
集中式版本控制系统的版本库是集中存放在“中央服务器”的,所有操作都需要联网,局域网或互联网。比如你修改了某个bug,但是你的电脑无法联网或者“中央服务器”宕机了,此时你就无法提交到“中央服务器”,就会影响到你 版本管理 。代表工具:SVN 。
1.2 分布式
分布式版本控制是没有“中央服务器”的,每一台电脑都有完整的版本库,你可以在本地分支上提交代码、合并分支、解决冲突等操作。多人协作时只需要把各自的修改推送给对方,就能看到对方的操作记录了。代表工具:Git 。
2 Git的介绍
Git是一个 开源的分布式版本控制系统 ,可以有效、高速地处理从很小到非常大的项目版本管理。也是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。先看下面的示意图:  Git有本地仓库和远程仓库,它们俩都有完整的版本库,远程仓库的作用是方便开发人员之间代码的推送与拉取。
3 Git工作流程图
 Git命令:
- clone(克隆):从远程仓库中克隆代码到本地仓库
- checkout(检出):从本地仓库中检出一个仓库分支然后进行修订
- add(添加):在提交前先将代码保存到暂存区
- commit(提交):提交到本地仓库
- fetch(抓取):从远程仓库更新版本库到本地仓库,不进行合并
- pull(拉取):从远程仓库更新版本库到本地仓库,并自动进行合并
- push(推送):将本地仓库的修改内容推送到远程仓库
4 Git的安装与配置
4.1 Git下载与安装
官网:Git官网  当前最新版是2.37.3,下载后运行程序,全部默认即可。安装完成后,在桌面或任何文件夹内右键,出现下面两个则表示成功: 
Git GUI:Git提供的图形界面工具

Git Bash:Git提供的命令行工具

本文只介绍 Git Bash 中的命令,了解了命令后界面工具也很容易上手。
4.2 Git基础配置
安装完成后首先 必须 要设置用户信息,因为Git每次提交版本时都会使用该用户
- 打开Git Bash
- 设置用户信息(邮箱可以不是真实邮箱)
git config --global user.name '你的用户名'
git config --global user.email '你的邮箱'
- 查看配置信息
git config --global user.name
git config --global user.email
5 Git本地仓库常用命令
5.1 基础命令
5.1.1 初始化本地仓库
命令形式:
git init
新建一个文件夹 git_test ,进入目录后打开 Git Bash ,输入上述命令,出现以下内容则表示初始化成功  此时在目录中会生成一个隐藏的 .git 文件夹,里面就是你的版本库信息,含 .git 文件夹的目录称为 工作目录 。 
5.1.2 查看仓库状态
在工作目录中所有文件都有状态,新建文件是未跟踪(untracked),修改已有文件是未暂存(unstaged),添加到暂存区是已暂存(staged),提交到仓库是已提交(committed)。
命令形式:
git status
输入以上命令,可以查看仓库当前状态  命令行提示说:在master分支上没有任何内容可以提交(创建或复制文件,然后使用“git add”命令来跟踪)
5.1.3 添加到暂存区
命令形式:
git add 文件名|通配符
上述命令可以将工作区一个或多个文件添加到暂存区
添加单个文件:git add 文件名或目录名 添加所有修改的文件:git add .
举例说明: 在我们的工作目录 git_test 中新建一个txt文件叫file01(可以鼠标右键新建,也可以使用命令 touch file01.txt ),此时再执行 git status 命令会得到另一种提示:  命令行提示说:file01.txt是未被跟踪的文件,使用“git add 文件名 ”来包含要提交的内容 。 那我们这里就执行 git add file01.txt 命令,然后再执行 git status 命令,可以发现它的状态发生了改变:  命令行提示:file01.txt文件等待提交,使用“git rm --cached ” 可以退回未暂存状态。
上面的操作是新增一个文件,如果修改已提交的文件,则会更改状态为未暂存(unstaged)。假设你已完成 5.1.4 提交到仓库 的操作,然后修改 file01.txt 内容,添加 count=1 ,然后再执行 git status 命令:  命令行提示:你的修改还没有暂存,不能提交!可以使用"git add "来更新要提交的内容,或者使用“git restore ”来丢弃工作目录中的更改。 那我们这里可以执行 git add file01.txt 或者 git add . 命令,然后再执行 git status 命令,可以发现它的状态发生了改变:  可以看到修改后的 file01.txt 文件被成功添加
5.1.4 提交到本地仓库
命令形式:
git commit[ -m "注释内容"]
通过该命令可以将已暂存的文件提交到本地仓库,[] 中的部分表示可选,推荐必填 。因为 注释内容 本身是必须的,如果在此处不填,下一步会打开其他编辑器(比如vim编辑器或其他)让你填写。  此时再执行 git status 查看状态  可以看到已经成功提交了,之前的 No commits yet 也消失了。注意:提交步骤需要先添加到暂存区再提交 ,下文说的提交到仓库指的这连续两步。
5.1.5 查看日志
命令形式:
git log[option]
选项:
- –all 显示所有分支
- –oneline 将提交信息显示为一行
- –graph 以图的形式显示
通过该命令可以查看仓库的所以操作记录  此处 commit 后面的一长串字符串是版本号(commitID),后面括号内的 HEAD 表示 当前所在分支 ,这里的意思是当前分支是master分支。Author表示作者,Date则表示提交时间,add file01就是我们填写的 提交信息 。
我们可以把上面几个常用选项全部加上,同时输出不加选项的日志进行比较: 
commitID缩短了,用户信息和日期被省略了,只留下了关键的分支和注释,这样我们看日志会更加清楚明了。同时,缩略的commitID使用时效果是一样的,因为它是唯一的。
5.1.6 版本重置
命令形式:
git reset --hard commitID
比如上面已经提交过两次,第一次新建file01文件,第二次在file01文件修改内容,所以有两次提交记录,执行上述命令后当前版本就会重置到第一次提交记录上  此时你在日志中已经查看不到第二次提交的记录了,那我们是否可以通过本条命令重置当前版本到第二次提交记录上?答案是可以的,我们可以通过上面第二次提交的commitID来执行命令。  可以看到日志中又出现了第二次提交的记录,但是如果我们清除了命令行工具的内容拿不到commitID,那还能重置回去吗?答案是可以的,这里需要使用另一条命令: git reflog 。这条命令可以理解为 显示所有引用日志 ,它可以查看所有的版本操作记录,包含提交与重置,而 git log 只显示当前版本(HEAD指针 )及其之前的版本信息。  这里我们对比一下两条命令的输出,在 git log 下面只有两次的提交记录,而在 git reflog 下面有四条记录,从上到下是最新到最旧的记录,分别是:1. 重置到第二次提交,2. 重置到第一次提交,3. 提交更新文件file01.txt,4. 提交新建文件file01.txt。所以我们可以在这拿到想要的commitID。
5.1.7 忽略列表
我们新增了一个 file02.txt 文件,但是我们不想提交它,同时又希望使用 git add . 来一键全部提交,此时可以新建一个 .gitignore 文件,在里面写上 file02.txt 保存,下面我在保存前后分别执行 git status 查看了文件的状态  可以看到保存前未跟踪的文件里包含 file02.txt 文件,保存后则被忽略了。
5.1.8 为常用命令配置别名
有些命令加上选项会很长,但是又经常使用,比如 git log --oneline --graph ,此时我们可以给它配置别名。
- 打开用户目录,创建
.bashrc 文件 打开【C盘】-【用户】,我这里用户目录是【Administrator】,打开目录后新建.bashrc 文件。部分windows系统不支持创建点开头的文件,可以打开 Git Bash 执行 touch ~/.bashrc 。 - 在
.bashrc 文件中输入以下内容
alias git-log='git log --all --oneline --graph'
这样别名就设置成功了,我们可以执行 git-log 试试效果  发现确实和执行 git log --oneline --graph 是一样的,注意,.bashrc 的内容修改后 Git Bash 需要重启才会生效。
5.2 分支命令
顾名思义,分支就是从主体上分出去的一部分。分支可以把开发环境和生产环境分离开来,避免开发影响到生产环境。最常用的分支有两个:master(生产)分支 和dev(开发)分支 。其他更细致的分支可以自行百度,这里举个例子, 比如leader让你开发一个新功能,应该在dev分支 上创建一个新的分支feature/xxx ,等到功能完成并且测试通过后先合并到dev分支 上,然后leader审核通过后再合并到master分支 上,这样可以在开发新功能的同时保证生产环境的稳定与安全。
5.2.1 查看本地分支
命令形式:
git branch
 初始化仓库时默认创建 master 分支,可以看到当前只有 master分支 ,并且 当前所在分支 就是 master分支 (颜色标绿且前面带有星号)
5.2.2 创建本地分支
命令形式:
git branch 分支名
 可以看到已经成功创建了 dev分支 ,但是 当前所在分支 还是 master分支 。
5.2.3 切换分支
命令形式:
git checkout[ -b] 分支名
不加参数 -b 只能切换已创建的分支,添加参数 -b 可以在分支不存在时创建并切换。 
5.2.4 合并分支
一个分支上的提交可以合并到另一个分支。比如前面提到的,master作为 生产分支 ,我们新增功能模块是不该在上面开发的,可以新建一个dev分支,开发完成后再合并到master分支。 命令形式:
git merge 分支名
举例说明:继续使用前面的例子,我们切换到 dev分支 然后创建一个file03.txt文件,按顺序执行 git add . ,git commit -m 'add file03' ,git-log 。  此时可以看到最新的版本记录,分支是dev(HEAD指针指向的就是当前分支),注释内容是 add file03 ,正是我们刚刚提交的。然后切换回master分支  可以看到master分支的版本记录只到 update count=1 这一步,这时我们想把 dev分支 的改动合并到 master分支 上来,就可以执行 git merge dev 。注意,我们要合并到master分支那就要切换到master分支,不能在dev分支上执行,那就没有效果了。  可以看到,dev分支 上的改动已经成功合并到master分支 上。
5.2.5 删除分支
命令形式:
git branch -d 分支名 // 通常情况使用 git branch -D 分支名 // 强制删除,在被删除分支比当前分支的版本更新时使用,比如dev比master多了一次提交,想要删除dev就必须使用该命令
5.2.6 解决冲突
当我们在两个分支上改动同一个文件,就会产生冲突。比如上面的例子,master 和dev 都有file01.txt 文件,内容都是 count=1 ,我们切换到dev分支 ,把内容改为 count=2 然后提交,再切换到master分支 ,把内容改为 count=3 然后提交,然后我们把dev合并到master上,  此时命令行提示:自动合并file01.txt失败,需要解决冲突然后提交结果。我们可以使用记事本打开file01.txt:  两条分支都修改了同一个文件的同一行,合并时Git不知如何取舍,所以干脆交给执行合并命令的人处理。从HEAD到虚线的是当前分支的内容,从虚线到dev的就是dev分支的内容。解决冲突很简单也很暴力,那就是留下你需要提交的内容 ,比如这里我要留下 count=3 那就删掉其他内容,  然后再重新提交一遍就行了
6 Git远程仓库
前面我们讲的都是本地仓库的操作命令,只有将远程仓库关联进来才是完整的Git工作流程 。那么我们如何搭建Git远程仓库呢?其实可以通过互联网上提供的一些代码托管服务来实现,比如Github,码云(Gitee)等等。Github因为某些原因需要科学上网,我们这边就选择码云。
6.1 注册码云账号
码云是国内的一个代码托管平台,想要使用它的服务自然要先注册账号,注册流程直接略过。
6.2 创建远程仓库
在码云右上角有个加号,展开后点击【新建仓库】  然后填写仓库名称,系统会自动填充路径  其他都可以不用改动,然后立即创建  创建后的样子如上图所示
6.3 配置SSH公钥
现在远程仓库已经创建好了,我们想要把本地仓库推送到远程仓库,远程仓库需要验证我们的身份,有两种方式,一种是输入Git的账号和密码,一种是SSH公钥,推荐使用第二种。
首先打开 Git Bash 执行以下命令来生成公钥:
ssh-keygen -t rsa
然后一直回车即可,如果公钥已经存在,则会自动覆盖。已经生成过的公钥,可以执行以下命令来查看:
cat ~/.ssh/id_rsa.pub
 这样我们就拿到需要的公钥了,然后要把它填到哪呢?打开码云,展开右上角头像菜单  根据上图点击【设置】,然后左侧菜单栏点击【SSH公钥】,把拿到的公钥填到公钥一栏里  标题一栏有时会自动填充,你也可以随便改,点击确定就添加成功了。那么如何验证公钥生效了呢?打开 Git Bash 执行 ssh -T git@gitee.com ,出现以下内容则表示成功  注意:如果你是第一次访问它,会询问你是否继续访问,这里输入 yes 然后回车,然后就能看到上图的内容了。
6.4 操作远程仓库
6.4.1 添加远程仓库
命令形式:
git remote add 远端名称 仓库路径
- 远端名称,默认是origin
- 仓库路径,从远端服务器获取
我们打开码云上新建的仓库,从上面获取仓库的SSH地址  然后就能在 Git Bash 中执行命令 
6.4.2 查看远程仓库
命令形式:
git remote

6.4.3 推送到远程仓库
命令形式:
git push[ -u][ <远程主机名> <本地分支名>[:<远程分支名>]]
如果直接执行 git push ,会提示当前分支和远程分支没有关联,需要我们推送当前分支并绑定关联。 
方法有两种,第一种是:
git push origin master
origin是通用的远程主机名,本地分支名和远程分支名相同时可省略,所以可以只有一个master。这种写法有一个弊端,就是每次都要写全。第二种方法是:
git push -u origin master
参数 -u 是 --set-upstream 的缩写,作用是将当前本地分支(即master)和远程分支(也叫master)建立关联,之后的推送可以直接执行 git push 。  此时就成功推送到了远程仓库,并且创建了master分支,origin/master 就是远程分支。我们打开码云上的仓库,可以看到文件全都推送成功了,历史提交的版本也都能查看。  我们也可以执行 git branch -vv 来查看本地分支和远程分支的关联关系,下图看到 master 后面跟着 origin/master 就说明关联成功了。 
其他:git push 命令除了 -u 还有其他参数,此处不做展开,有兴趣的可以执行 git push -h 查看自行研究。 
6.4.4 从远程仓库克隆
我们已经成功将本地仓库推送到远程仓库,假如我们的同事要协同开发,他就可以从远程仓库进行克隆。 命令形式:
git clone[ 远程仓库路径][ 本地目录名称]
远程仓库路径 就是仓库的url,可以使SSH或其他,如下图   这样就成功克隆了仓库。 本地目录名称 作用是修改本地项目名称 ,如果不加,本地项目的目录名称就是远程仓库的名称。比如上面,远程仓库名是 git_test ,Cloning into 'git_test' 就表示在当前目录下创建了一个 git_test 目录,把远程仓库的内容克隆到该目录,我们可以加上 git_testf_01 ,那么目录名就自动变了。  这里我们可以执行 ls 命令,它可以查看当前目录下所有文件  可以看到,确实是创建了 git_test_01 目录
6.4.5 抓取和拉取
抓取 命令形式:
git fetch[ <远程仓库名> [ <分支名>]]
提示:如果不指定远程仓库名和分支名,则抓取对应的分支。
继续使用上面的例子,在 git_test 目录下新增 file04.txt 文件并提交到仓库,然后推送到远程仓库。打开 git_test_01 目录,执行抓取命令  执行 git-log 可以看到最新的版本库是 origin/master 远程分支,但是本地 master 分支并不是最新版本库,因此需要执行 git merge origin/master 合并分支  此时本地 master 分支也是最新版本库了
拉取命令形式:
git pull[ <远程仓库名> [ <分支名>]]
提示:如果不指定远程仓库名和分支名,则抓取对应的分支。
抓取和拉取的区别就在于,抓取不会自动合并,而拉取会在抓取后执行合并。我们可以在 git_test 目录下新增 file05.txt 文件并提交到仓库,然后推送到远程仓库。打开 git_test_01 目录,执行拉取命令  可以看到,拉取成功后本地 master 分支就是最新版本库了。
6.4.6 解决冲突
**本地仓库更改后,在推送到远程仓库前,应该先执行抓取,再执行推送。**假如用户A修改了file01文件并且推送到了远程仓库,用户B也在本地修改了file01文件,此时他想推送到远程仓库就会失败。  提示说远程仓库有你本地没有的工作,建议你先集成远程仓库的更改。这就是先执行抓取的原因。我们执行抓取后(通常执行拉取,此处抓取是为了),输出日志查看。  远程更改了count为8,本地更改了count为9,此时执行合并分支 就会产生冲突。  如果觉得 抓取+合并 太麻烦,你也可以直接执行拉取(pull),效果是一样的。  图中HEAD表示本地,最后面的版本号表示远程。
出现冲突不用怕,解决冲突和前面本地仓库是一样的,就是把 需要提交的内容留下 ,其余删除,然后提交到本地,再推送到远程。这里我们就把这段内容删除,留下count=10,然后提交。  这样我们就解决了冲突,最后一步就是把解决冲突后的文件推送到远程仓库。  到这里,Git的入门知识就讲完了。这是本人在学习后,思考和实践后整理出的内容,毕竟看得多不如写得多,写得多不如说(讲给别人)得多。希望这篇文章能对入门的伙伴有一点帮助。
|