从不同使用场景来介绍git的基础命令
从不同使用场景来介绍git的基础命令
自从上了高级软件工程课,我对于Git在多人协作和版本控制方面的作用,产生了浓厚的兴趣。之前本人也做过一些的小的项目,在开发者只有一人的情况下,自然不用考虑太多,尤其是涉及到多个分支,多人协同开发的情况。但现在的软件行业,已经不是孤身一人就可以作战,每个人或多或少的都会在某个项目中与他人互动。这就产生了以下几个问题:如果几个人同时对某个文件进行了改动,且改动不一,该怎么处理?如果当前的项目进展的不是很顺利,想要使用之前写过的版本,但过去的代码并未备份,这时候又该怎么办?
不用担心,Git作为一个伟大的版本控制工具,为我们做了解答,下面就让我们一起来看一下Git的基本使用方法。
鉴于Vscode支持Git的使用,接下来的演示,我会使用Windows上安装的Vscode和Git工具。对于如何在Windows上安装这两款工具,请见链接:
https://blog.csdn.net/qq_32786873/article/details/80570783(Windows10下安装git)
https://blog.csdn.net/x15011238662/article/details/85094006(Windows下安装Vscode,并使用,以及中文配置)
以下我会分五个场景介绍Git的基础使用,但在此之前,让我们先来看一下,Git中涉及的一些概念。
以上就是Git的基本架构和操作逻辑。
Git的工作区域可以简单分为两类——本地(local)和远程(remote)。
Git的本地工作区包括3个部分:
工作区(work directory):就是我们项目所在的根目录,也是我们直接改动代码的地方;
暂存区(stage):与工作区的文件进行直接交互,工作区文件的提交或者回滚,首选都是通过暂存区
本地仓库(repository):我们将项目设置为本地版本库后,在项目的根目录下,就会生成一个隐藏的“.git”文件夹,该目录即为本地仓库
看完了上述的几个名词,有些疑惑是吗?没关系,下面就让我们来一个个解释。
场景一.Git本地版本库的基础用法
我先在桌面新建一个名为”gitdisplay“的文件夹,右键点击,选择”Git Bash Here“,便会打开如下图所示的窗口
当我们敲下如下命令:git init
我们会发现,在gitdisplay目录下多了一个.git隐藏文件夹,也就是说我们通过命令初始化了一个本地仓库。
此时如果我们通过Vscode打开gitdisplay文件夹,并且新增一个"readme.md"文件,Vscode的侧边栏,便会出现以下情况:
如果我们返回Git bash界面,输入命令:git status
便可以查看工作区的当前状态
以上信息说明,当前工作区的文件“readme.md”未被跟踪,因此我们需要把该文件提交到暂存区
输入命令 git add [FILES]
此时查看工作区状态,显示文件已被added,位于暂存区中,等待提交
命令git commit -m "added info"
,可以将暂存区中的文件提交到本地仓库
以上我们就完成了本地对源代码的基本的版本控制,简单来说,每当我们的工作区有了变动,我们就通过 git add
将改动添加到暂存区,然后通过 git commit
将其提交到本地仓库。
如果此时我们试着向readme.md中写入一些东西,然后重复上述操作,将其提交,通过 git log
指令,我们可以看到git bash界面显示了两次提交记录。
由图片可以看出,“HEAD”对准的是我们最新的提交。
当然,并不是我们的每一次提交都是有意义的提交,有的时候,我们需要的可能是之前的提交,而命令
git reset --hard HEAD^^/HEAD~100/commit-id/commit-id的头几个字符
可以让我们回退到过去的某次提交,并且新的commit记录也会消失。当我们输入 git reset --hard 02bc9
,然后用 git log
查看提交记录
我们惊讶地发现,只剩下了第一次的提交记录,并且HEAD指针指向了该次提交记录。同时,此时打开readme.md,我们会发现,添加的语句已经没有了。
然而,并不是所有回滚都是有意义的,可能某些同学回滚完了之后,又开始后悔了,可是通过git log
指令,我们已经无法查看 第一次提交之后的记录了。别担心,指令 git reflog
可以查看当前HEAD之后的提交记录
看懂了吗,HEAD指向我们工作区所依赖的版本,同时也是未来与过去的分界线。
当我们再次输入 git reset --hard 7081a
恭喜,我们又回到了最新的提交版本。借助HEAD,我们可以在过去和未来之间来回切换。
有关git reset 的其他模式,请参照 https://www.jianshu.com/p/c2ec5f06cf1a(git reset 三种模式)
场景二.git远程版本库的基本用法
还记得我们是怎么来初始化一个本地版本库的吗?没错,我们使用的是git init
。同样的,我们也可以通过
git clone [链接地址]
的方式初始化。该命令的意思是从远程仓库克隆一个项目到本地仓库。
那么问题来了,这个所谓的远程仓库是在哪?一般来说,远程仓库位于github这个网站上。git与github的关系,经常有人混淆,其实git是个版本控制工具,而github就是个远程仓库。
首先,我们要做的就是新建一个远程仓库,关于此部分我就不再赘述,有兴趣的同学可以参考一下链接:
https://www.jianshu.com/p/750527980651(git添加远程仓库)
当我们成功创建一个远程仓库后,就会获得一个你的远程仓库的链接地址。接下我们要做的就是将远程仓库,和我们的本地仓库联系起来。
需要输入的命令为:
git remote add origin [your repository link]
git push -u origin master
(仅第一次需要添加“-u”)
我们发现,远程仓库的内容,与本地仓库已经一摸一样了。
此后每当我们在本地工作区产生改动,当提交到本地仓库后,再使用指令 git push origin master
使远程仓库与本地仓库保持一致。
如果我们之后的开发只要按照这种流程,那么也就不会有后续的烦恼了。但问题在于,我们不光可以在本地进行修改,也可以在远程仓库进行修改。当我们在远程仓库加入新的改动时,由于我们本地并未改动过,如果我们想以远程仓库的最新版本为基准,那我们需要使本地仓库与远程仓库保持一致。这时,使用git fetch
和 git merge
,便可以使本地仓库与远程仓库保持一致。
当我们输入git fetch
git bash中有如上显示,但是本地文件中,还是没变。
同时我们查看status时,
显示我们的本地仓库,已经落后了远程仓库1次提交。也就是说git fetch
指令拉取了远程仓库的最新信息,但并没有立即合并到当前分支中。此时,如果我们输入 git merge
现在我们可以看到本地仓库与远程仓库,都保持一致了。
当然,如果我们总是以远程仓库的版本为基准,我们可以使用git pull
指令,可以近似看做以上两条指令的组合。
另外当git merge
发生冲突时,我们需要处理冲突,之后再次提交。(假如本地和远程修改了同一文件的同一处的地方)
一个比较保险的做法,就是不管实在本地仓库还是远程仓库,在对代码修改之前,都应该进行代码同步,以防止产生冲突和分叉。
场景三.团队项目中的分叉与合并
之前介绍的情况,都是基于一点,那就是在master主分支上开发。master分支一般只用来发布重大版本,如果一个项目有多人协作,如果大家都在master分支上开发,那势必会引起版本混乱。正确的做法应该是在其他分支上做开发,调试好了再合并到主分支。
如果我们想创建一个分支并切换到该分支,可以使用命令 git checkout -b dev1
,这样我们就创建并切换到了分支dev1。
当我们用git branch
命令查看分支的情况时,发现共有两个分支,且当前所在的分支为dev1。
如果我们在readme.md中添加了一行新的语句,并提交。然后再重复一遍该操作
我们发现在dev1分支下多了两条提交记录。
假如我们已经在dev1分支上完成了开发,希望将该分支上的变动合并到主分支master上,我们又该做什么呢?
首先,我们先要用git checkout master
返回主分支。
然后,我们使用git pull
命令使主分支与远程仓库同步。
之后我们使用命令git merge --no-ff dev1
将dev1分支上的变动,合并到master分支。
命令中的--no-ff,作用是禁止快进式合并,这样可以使分支的提交历史,变得更加清晰。
接下来我们要做的就是将本地的变动,push到远程仓库。好了,我们成功创建了一个分支,完成了一个功能修改,然后将主分支与其合并,然后使远程仓库与主分支保持一致。以上就是开发某个模块或者功能的基本流程了。
场景四.Git Rebase
有人可能对git rebase
命令的作用产生疑惑。一般来说,作用是精简提交记录。比如你自己在某个分支上 提交了多个记录,但是并不是每一次提交都是有意义的提交。通过git rebase
可以使某些提交记录合并为一个,从而变得简洁明了。
假如我们在dev1分支上进行三次改动,每次都提交记录,结果如下:
接着我们回到master主分支,并在该分支上进行三次操作,同样每次都要提交记录,结果如下:
图中红线圈起来的部分,就是在master和dev1分支上分别提交的3次记录。如果我们嫌dev1分支上的3次提交过于繁琐,想把它整合为一次,我们可以先切换为dev1分支,然后使用命令
git rebase -i [startpoint] [endpoint]
,
后续的endpoint如果不指定,则要合并的区间的终点,默认是当前分支的HEAD。
我们希望仅希望保留第三次提交记录
保存退出文件,这时我们发现git bash显示报错,并打开冲突文件,因为前两次提交相当于第三次提交的基础,所以我们需要对冲突文件进行修改。我们可以直接修改文件,保留三次修改的内容,同时去掉提示符号,并保存。
这时我们通过git status
查看
此时,我们需要通过git add
命令将修改好的文件存入暂存区 ,然后输入命令git rebase --continue
此时会出现以下画面
提示我们需要重新修改记录提交信息。
这时我们发现,在dev1上的提交记录,已经合并为一次了
接下来的操作就是,先切换为master分支,使用git pull
指令使本地与远程保持一致,然后合并dev1分支到master分支,并用git push origin master
向远程仓库推送。
最后,我们看到,虽然dev1有多次提交记录,但最终只有一个提交记录有显示。
以上就是关于Git的一些基础命令,感谢您的观看。
参考文章:五⼤场景玩转 Git,只要这一篇就够了!
出处:https://www.cnblogs.com/chuanguo/p/13795951.html
=======================================================================================
五⼤场景玩转 Git,只要这一篇就够了!
推荐阅读:
本文作者孟宁,未经许可禁止转载!
Git 分布式版本控制系统
关于版本控制,其实我们应该都比较熟悉,甚至经常用到,比如我们写一个文档的过程中,经常会另存为一个独立文件作为备份,来管理我们在写文档的过程中产生的不同版本,这就是版本管理,只是这是用人工的方式来做版本控制。当不同版本的数量庞大的时候,人工进行版本管理就比较容易出差错了,这时就出现了用软件系统作为工具来进行版本控制,这就是版本控制系统。
版本控制的策略比较常见的有两种:一种是独立文件或说整体备份的方式,比如使用另存为将整个项目或者整个文档整体备份一个版本;另一种就是补丁包的方式,比如使用 diff 命令将当前版本与上一个版本对比得出两者之间的差异从而形成一个补丁包,这样上一个版本加上这个补丁包就是当前版本。显然前者会产生大量重复数据,消耗比较多的存储资源,但是每一个版本都是独立且完整的;后者几乎没有重复数据,存储效率更高,但是每一个版本的补丁包无法独立使用,因为它们都需要递归地依赖上一个版本才能合并出一个完整的版本。
版本控制系统大致分为两大类,一类是中心版本控制系统,比如 Concurrent Versions System(简称 CVS)和 Subversion(简称 SVN);另一类就是分布式版本控制系统,比如我们即将重点介绍的 Git,是目前世界上最先进的分布式版本控制系统(没有之一)。Git 的诞生的经过大致是这样的。
Git诞生的历史
在 2002 年以前,Linux 内核源代码文件是通过 diff 的方式生成补丁包发给 Linus,然后由 Linus 本人通过手工方式合并代码!Linus 坚定地反对 CVS 和 SVN,这些中心版本控制系统不但速度慢,而且必须联网才能使用。有一些商用的版本控制系统,虽然比 CVS 和 SVN 好用,但那是要付费购买的,这和 Linux 的开源精神不符。
2002 年 Linus 选择了一个商业的版本控制系统 BitKeeper,BitKeeper 的东家 BitMover 公司出于人道主义精神,授权 Linux 社区免费使用BitKeeper版本控制系统。其实应该是 Linus 与 BitMover 公司的创始人之间的私人关系促成了免费授权 Linux 社区使用 BitKeeper。
2005 年 BitMover 公司要收回 Linux 社区的免费使用权。为什么 BitMover 公司要收回 Linux 社区的免费使用权?根据一些资料显示,大概是因为 Linux 社区里有人违反授权协议将 BitKeeper 用于其他项目从而侵害了 BitMover 公司的商业利益。
为了替代BitKeeper版本控制系统,结果Linus 花了两周时间自己用 C 写了一个分布式版本控制系统,这就是 Git!一个月之内,Linux 内核源代码已经用 Git 管理了!大神 Linus 除了 Linux 内核之外又产出了一个神作——Git分布式版本控制系统。
大约经过了3 年时间的发展迅猛,到 2008 年,专门提供 Git 存储的 GitHub 网站上线了,它为开源项目免费提供 Git 存储,如今 GitHub 已经成为全球最大的程序员社交网站,在码农中戏称为全球最大的单身男性社交网站。
2016 年,也就是 Git 诞生 11 年之后,可能是由于 Git 太流行了,以致 BitKeeper 版本控制系统在商业上无法维系运营,BitMover 公司只得将 BitKeeper 的源代码贡献给开源社区续命。
2018年,Github被微软以75亿美元收购。
Git 的基本操作
大概了解了Git的历史,接下来我们看看Git 的基本操作大致如下图。
对于本地 Repo如上图中间存储库Repository,实际上本地 Repo都存在项目根目录下.git文件夹中,内部可能有多个 branch,但至少有一个叫 master的branch。
本地Repo中的某个branch被checkout到当前workspace,就能在当前源代码目录中看到一份完整的源代码,这份完整的源代码就是workspace。
在workspace中新增文件或修改文件,只有完成add和commit两步操作才能将新增或修改的文件纳入本地Repo的存储库中进行版本管理。add和commit两步操作中间Index索引数据应该也是和本地 Repo一样存在项目根目录下.git目录中。
到这里Git与 CVS、SVN操作逻辑大致一致,只是实现了本地的中心化版本控制。接下来看Git是怎么做到分布式的
本地 Repo 中的 branch 与一个或多个远程 Repo(上图中的Remote) 中的 branch 存在跟踪关系,这样就构成了Git分布式版本控制的网状结构。
显然Git的分布式网状结构比中心化 CVS 和 SVN在理解和使用上更为复杂一些。比如增加了本地与远程Repo之间数据同步操作clone、fetch、push、pull。
通过git --help命令可以查看Git命令用法及常用的Git命令列表。
$ git --help
usage: git [--version] [--help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
[-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
<command> [<args>]
These are common Git commands used in various situations:
start a working area (see also: git help tutorial)
clone Clone a repository into a new directory
init Create an empty Git repository or reinitialize an existing one
work on the current change (see also: git help everyday)
add Add file contents to the index
mv Move or rename a file, a directory, or a symlink
reset Reset current HEAD to the specified state
rm Remove files from the working tree and from the index
examine the history and state (see also: git help revisions)
bisect Use binary search to find the commit that introduced a bug
grep Print lines matching a pattern
log Show commit logs
show Show various types of objects
status Show the working tree status
grow, mark and tweak your common history
branch List, create, or delete branches
checkout Switch branches or restore working tree files
commit Record changes to the repository
diff Show changes between commits, commit and working tree, etc
merge Join two or more development histories together
rebase Reapply commits on top of another base tip
tag Create, list, delete or verify a tag object signed with GPG
collaborate (see also: git help workflows)
fetch Download objects and refs from another repository
pull Fetch from and integrate with another repository or a local branch
push Update remote refs along with associated objects
'git help -a' and 'git help -g' list available subcommands and some
concept guides. See 'git help <command>' or 'git help <concept>'
to read about a specific subcommand or co
接下来我们用五大场景来介绍Git的基本用法,基本遵循着从简单到复杂,最终再回归到简单的过程,同时这五大场景也基本能够覆盖大多数实际使用的应用场景。
场景一:Git 本地版本库的基本用法
如果您使用了VS Code,那么恭喜您,安装VS Code时已经附带安装了Git,建议Windows用户通过安装VS Code完成Git软件包的安装。
如果您使用的Linux或者其他Unix类的操作系统,以Ubuntu为例大致可以通过类似如下命令安装Git软件包:
sudo apt install git
当然Linux和MacOS也可以通过安装VS Code附带安装Git,但是在没有图形界面的服务器端单独安装Git软件包比较合适。
初始化一个本地版本库
在VS Code中打开文件夹( Ctrl/⌘+O),实际上就是打开一个项目工作区(workspace),VS Code的工作区概念与Git的工作区概念基本一致,都是指当前项目文件夹里的整套源代码。
如果项目文件夹里没有Git存储库,这时打开源代码管理(Ctrl+Shift+G)大致如下图,可以直接点击【初始化存储库】按钮,初始化一个Git本地版本库。
习惯于使用命令行的话,只需在项目根目录下执行git init命令即可以完成初始化一个Git本地版本库。
如果您已经在Gitee.com或者Github.com网站上创建了版本库,可以通过git clone命令,将版本库克隆到本地完成本地版本库的初始化。git clone命令的用法如下:
git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git
不管使用何种方式初始化一个Git本地版本库,实际上都是在项目根目录下创建了一个.git文件夹,感兴趣的话可以进入.git文件夹进一步了解Git版本库内部数据的存储结构。
查看当前 workspace 的状态
在VS Code中打开源代码管理(Ctrl+Shift+G)可以看到与上一个版本比项目的所有更改,即当前 workspace 的状态,比如如下图中源代码管理中以绿色U标记的文件为没有添加到版本库进行跟踪的文件(Untracked files)、以橙色M标记的文件为已修改(Modified)未提交的文件(Changes not staged for commit)。
习惯于使用命令行的话,只需在项目目录下执行git status命令即可查看Git本地版本库当前 workspace 的状态。
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: lab7/README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
"lab1/mybot/\357\202\267\343\200\212\345\267\245\347\250\213\345\214\226\347\274\226\347\250\213\345\256\236\346\210\230\343\200\213\350\242\253\346\225\231\350\202\262\351\203\250\350\256\244\345\256\232\344\270\272\345\233\275\345\256\266\347\262\276\345\223\201\345\234\250\347\272\277\345\274\200\346\224\276\350\257\276\347\250\213 (2018).txt"
no changes added to commit (use "git add" and/or "git commit -
暂存更改的文件
在VS Code中打开源代码管理(Ctrl+Shift+G)可以看到与上一个版本比项目的所有更改,即当前 workspace 的状态,如下图所示只要在【更改】列表里的文件上点击加号“+”即可暂存更改(git add FILES),是将更改的文件加入到【暂存的更改】列表里,点击撤销符号即可放弃更改(git checkout -- FILES / git checkout .),是将该文件中的更改清除掉。
如下图所示只要在【暂存的更改】列表里的文件上点击减号“-”即可取消暂存更改(git reset HEAD FILES),是将暂存更改的文件从【暂存的更改】列表里取消,重新回到【更改】列表里。
习惯于使用命令行的话,需要清楚对应的几个命令用法如下:
git add FILES # 指定文件或文件列表
git add . # 用“.”表示当前目录
如上两行命令是将特定文件(FILES)或者当前目录下所有文件添加到暂存区(Index);
git reset HEAD FILES # 指定文件或文件列表
git reset HEAD
如上两行命令是取消将特定文件(FILES)或者所有文件添加到暂存区(Index),即从暂存区(Index)中删除;只有在暂存区登记的文件才会在提交代码时存入版本库。
git checkout -- FILES # 不要忘记“--” ,不写就把FILES当分支名了
git checkout .
如上两行命令的效果是放弃特定文件(FILES)或者所有文件的修改,实际上重新检出特定文件(FILES)或者所有文件到工作区(workspace),注意会覆盖掉已修改未暂存的内容,不希望被覆盖的文件可以先使用git add将其添加到暂存区。
把暂存区里的文件提交到仓库
在VS Code中打开源代码管理(Ctrl+Shift+G),只要【暂存的更改】列表里有文件就可以直接点击对号“√”(Ctrl+Enter)将暂存的文件提交到仓库中,只是在提交之前会强制要求输入提交日志消息。
注意一旦提交到仓库,尽管也可以撤销上次提交,但是依然会在仓库里留下记录,可以通过git reflog查看当前 HEAD 之后的提交记录,稍后我们具体通过命令行方式详细解释。
习惯于使用命令行的话,把暂存区里的文件提交到仓库主要使用git commit命令,但是还会涉及撤销提交和查看日志的命令。
git commit -m "wrote a commit log infro"
如上命令可以把暂存区里的文件提交到仓库。git log
如上命令可以查看提交日志,可以看到当前 HEAD 之前的所有提交记录。git reset —hard HEAD^
git reset —hard HEAD^^
git reset —hard HEAD~100
git reset —hard 128个字符的commit-id
git reset —hard 简写为commit-id的头几个字符
如上命令可以让HEAD回退到任意一个版本,比如HEAD^表示HEAD的前一个版本、HEAD^^表示HEAD的前两个版本、HEAD~100表示HEAD的前100个版本,也可以用版本号字符串来指定任意一个版本。
值得注意的是HEAD只是一个指向特定版本的指针,通过git reset —hard 回退之后,HEAD指向的不是最新的版本,而git log只能查看HEAD及其之前(时间更早)的提交记录,这就会产生一个问题,我们可以通过git reset —hard回到过去,那怎么回到未来?那就要有办法查到HEAD指向的版本之后(时间更晚)的提交记录。
git reflog
如上命令可以查看当前 HEAD 之后(时间更晚)的 提交记录,从而可以通过git reset —hard回到未来。
场景一:Git 本地版本库用法参考
场景一主要是在本地对源代码进行基本的版本控制,主要通过git add和git commit -m提交版本,有了提交记录之后可以灵活地将当前工作区里的源代码回退到过去的某个版本,也就是回到过去。回到过去之后,也有可能发现之前撤销的某个版本是有价值的,希望找回来,这就需要回到未来。过去和未来之间的分界点就是HEAD,即当前工作区所依赖的版本。
git init # 初始化一个本地版本库
git status # 查看当前工作区(workspace)的状态
git add [FILES] # 把文件添加到暂存区(Index)
git commit -m "wrote a commit log infro” # 把暂存区里的文件提交到仓库
git log # 查看当前HEAD之前的提交记录,便于回到过去
git reset —hard HEAD^^/HEAD~100/commit-id/commit-id的头几个字符 # 回退
git reflog # 可以查看当前HEAD之后的提交记录,便于回到未来
git reset —hard commit-id/commit-id的头几个字符 # 回退
场景二:Git 远程版本库的基本用法
如果您已经在Gitee.com或者Github.com等网站上创建了Git版本库,可以通过git clone命令,将版本库克隆到本地完成本地版本库的初始化。git clone命令的用法如下:
git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git
也可以VS Code中打开源代码管理(Ctrl+Shift+G),如果当前没有打开的项目文件夹,可以看到源代码管理(Ctrl+Shift+G)界面上有【打开文件夹】和【克隆存储库】两个按钮,这时点击【克隆存储库】如下界面,即可输入类似https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git 的存储库URL,按Enter键选择保存的目录位置,即可完成将远程的版本库克隆(git clone)到本地的任务。
这时通过 git clone 远程版本库从而在本地创建了一个版本库,这时就可以参照上面场景一的本地版本库基本用法,查看工作区状态(git status)、暂存更改的文件(git add)、把暂存区提交到仓库(git commit)、以及回到过去、回到未来(git reset —hard / git log / git reflog)等本地本地版本控制的基本操作。
远程版本库的基本命令简介
场景二Git 远程版本库的基本用法我们假定远程版本库作为远程备份或者公开源代码的目的,还是像场景一一样是单人的版本控制,为了循序渐进,暂时还是不涉及多人项目的协作。
这里使用git clone之后默认的分支,即远程为 origin/master 和本地 master,没有创建其他分支。管理本地版本库跟踪的远程存储库的命令为git remote。
$ git remote
origin
直接输入git remote可以看到git clone之后默认的远程存储库名称为orgin。
$ git remote -v
origin https://github.com/mengning/menu.git (fetch)
origin https://github.com/mengning/menu.git (push)
使用git remote -v 可以查看更详细的远程存储库信息,包括fetch(抓取)的远程存储库URL和push(推送)的远程存储库URL。
git fetch、git push加上git clone是三个对远程存储库的基本操作,而git pull(拉取)是实际上是 git fetch + git merge(合并)的组合。
-
git clone命令官方的解释是“Clone a repository into a new directory”,即克隆一个存储库到一个新的目录下。
-
git fetch命令官方的解释是“Download objects and refs from another repository”,即下载一个远程存储库数据对象等信息到本地存储库。
-
git push命令官方的解释是“Update remote refs along with associated objects”,即将本地存储库的相关数据对象更新到远程存储库。
-
git merge命令官方的解释是“Join two or more development histories together”,即合并两个或多个开发历史记录。
-
git pull命令官方的解释是“Fetch from and integrate with another repository or a local branch”,即从其他存储库或分支抓取并合并到当前存储库的当前分支。
如果不理解Git 背后的设计理念,或者说不理解其设计原理的话,记住这些命令及其实际作用还是有一些难度,而且在相对复杂的项目环境下也容易出错。但是在深入到Git 背后的设计理念之前,还是可以有一些在默认条件下的基本用法,从而快速上手用起来。
场景二:Git 远程版本库用法参考
我们假定使用场景二所述的工作是串行的并且及时将本地与远程同步,也就是对于一个单人项目,要么在本地提交代码到仓库,要么通过Web页面更新远程仓库,而且这两种方式不会同时发生。不管是在本地仓库还是远程仓库,对代码修改之前都首先进行代码同步操作,防止产生分叉和冲突。
在VS Code中版本库同步操作已经简化为了一个菜单命令,如下图:
在我们假定的使用场景中,此同步操作会将提交项(commits)推送(push)到远程仓库origin/master,并从远程仓库origin/master拉取(pull)提交项到本地master。
但是在我们假定的使用场景中,实际上只会有提交项被推送或拉取,不会同时有提交项被推送并被拉取,因此也不会产生冲突。
在我们假定的使用场景中,在命令环境下同步操作大致相当于如下两条命令:
git pull
git push
同步完成后,不管是在本地仓库还是在远程仓库提交代码,都能再次执行同步操作而不会产生分叉或冲突。
实际操作中难免会产生无法同步的情况,这时候需要在本地解决冲突,情形会稍微复杂一点。
首先我们通过git pull拉取远程仓库里的提交项到本地仓库并合并到当前分支,即将origin/master中的提交项fetch到本地仓库并merge到本地master分支。在VS Code中版本库有一个菜单命令可以完成git pull的功能,如下图:
拉取过程中有可能会有冲突的情况,无法完成merge合并动作,这时需要对冲突的代码进行修改并提交到本地仓库,即利用git add、git commit -m等本地版本库的操作。
这时本地仓库的提交项是领先于远程仓库的,只需要通过git push将本地仓库中的提交项推送到远程仓库,即可完成本地仓库和远程仓库的同步。在VS Code中版本库有一个菜单命令可以完成git push的功能,如下图:
注意推送过程一般需要用户名和密码验证身份。
至此,在本地版本库基本用法的基础上,只需要用推送(git push)和拉取(git pull)就完成本地仓库和远程仓库的同步。但是其中涉及了抓取(fetch)、合并(merge),以及分支(branch)、提交项(commits)的概念,要进一步深入学习Git、灵活使用Git,就需要理解Git 背后的设计理念,乃至Git仓库的存储方式作为基础。
Git 背后的设计理念
使用场景二所述的单人项目工作时间线上是串行的,如下图中分叉之前的部分,只要及时将本地与远程同步就不会出现分叉的情况。
但在实际项目中往往是多人团队开发项目,多人团队的工作时间线常常是并行的,团队中的每个人都向远程版本库 push,难免会发生如下图所示的分叉现象,这种情况该如何处理呢?只有理解了 Git 背后的设计理念,才能比较准确地把握分叉合并的处理方法。
首先要清楚Git版本管理的基础,是按行对比(line diff)将差异的部分作为一个增量补丁,通过git add添加到暂存区里的每一个文件都会由line diff得到该文件的增量补丁,而git commit将暂存区里的所有文件的增量补丁合并起来存入仓库就是一个commit。
通常在提交(commit)时,会生成一个SHA-1 Hash值作为commit ID。每个commit ID有40个十六进制数字(如3d80c612175ce9126cd348446d20d96874e2eba6),就是对那次commit在Git仓库中存储的内容和头信息(Header)的一个校验和(checksum)。Git使用了SHA-1并非是为了安全性,而是为了数据的完整性,即可以保证,在很多年后,重新checkout某个commit时,一定是它多年前的当时的状态,完全一摸一样,完全值得信任。
按时间线依次排列的一组提交记录(commit)形成一个branch,比如默认master分支即为一个branch,也可以根据某种需要创建分支(branch)。
tag是某个关键commit的标签,比如发布1.0版本时的那次提交(commit)被专门打了个标签v1.0,tag标签就是一个别名,便于记忆和使用。
我们简要总结一下以上几个关键概念:
-
line diff是形成增量补丁的技术方法,即一个文件按行对比(line diff)将差异的部分制作成一个增量补丁。
-
commit是存储到仓库里的一个版本,是整个项目范围内的一个或多个文件的增量补丁合并起来,形成项目的增量补丁,是一次提交记录。每个提交(commit)都生成一个唯一的commit ID。
-
branch是按时间线依次排列的一组提交记录(commit),理论上可以通过当前branch上最初的提交(commit)依次打补丁直到HEAD得到当前工作区里的源代码。
-
tag标签就是某次提交(commit)的commit ID的别名。
有了以上几个概念再来理解合并(merge)的概念就有了一个基础,合并操作比如远程origin/master分支合并到本地master分支,或者某个分支合并到master分支。以下图为例,项目在A版本处开始分叉,形成了两个分支,分别提交了B、D、F和C、E、G几个commit,这时希望将两个分支合并,只要将F与A的差异部分,以及G和A的差异部分,都放入工作区,如果有冲突解决冲突后就可以提交一个版本H,即完成了两个分支的合并。
简要总结一下,合并操作可以用一个公式来表示:H = (F - A)+ (G - A),即F版本与A版本的差异,以及G版本与A版本的差异,合并起来形成一个新的版本H。
具体技术实现上,因为每一个版本都是上一个版本的增量补丁,只要将要合并的分支里的B、D、F几个增量补丁,合并到当前工作区G版本里,解决冲突后提交为H版本。上图即为Github远程origin/master分支合并到本地master分支的示意图。
场景三:团队项目中的分叉合并
团队项目的一个参考工作流程
有了前面的基础知识和技能之后,我们可以考虑更复杂一些的团队项目合作的工作流程。
如果团队项目像场景二的方法一样多人同时向远程origin/master分支频繁提交代码,一来可能会有诸多冲突合并的情况发生;二来整个git log提交记录中多个开发者或多个代码模块的commit是交错排列在同一条时间线上,不利于回顾查看和回退代码,让跟踪代码的成长轨迹变得异常困难。
我们需要考虑新的方式来能够独立维护不同的开发者或者不同的功能模块的代码,让一段连续的工作在commit日志的时间线上呈现为一段独立的分支线段,只在关键节点处进行分支合并。
基于以上想法我们建议团队项目的每一个开发者都采用的工作流程大致如下:
-
克隆或同步最新的代码到本地存储库;
-
为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
-
在该分支上完成某单一功能模块或代码模块的开发工作;
-
最后,将该分支合并到主分支。
特别注意的是默认的合并方式为"快进式合并"(fast-farward merge),会将分支里commit合并到主分支里,合并成一条时间线,与我们期望的呈现为一段独立的分支线段不符,因此合并时需要使用--no-ff参数关闭"快进式合并"(fast-farward merge)。接下来我们会具体了解分支合并的具体方法。
分支的基本用法
在VS Code中创建分支方法如下图所示。
如果使用Git命令的创建分支为如下命令:
git checkout -b mybranch
实际上不管是菜单方式还是如上的命令方式创建分支,都是将当前分支分叉出一个分支(如上命令分支名称为mybranch),并签出(checkout)到工作区。这时使用git branch查看分支列表,如下所示mybranch前面有一个*代表当前工作区处于mybranch分支。
$ git branch
master
* mybranch
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git branch
* master
mybranch
这时要将当前工作区切换到master分支只需要如上所示使用git checkout master命令即可,在VS Code中可以使用如下图【签出到...】菜单,然后选择要签出的master分支即可完成分支切换。
假如要将mybranch合并到master,那么首先确保当期工作区处于master分支,可以使用git checkout master命令切换到master分支,或者使用git branch查看确认一下,或者在VS Code中源代码管理(Ctrl+Shift+G)的消息输入框里也能看到。然后可以使用如下图【合并分支...】菜单选择mybranch分支。
这样就可以将自己的工作合并到 master分支。如果创建mybranch分支之后master分支有更新过,合并过程可能会因为有冲突(都修改了同一位置)而失败,这时mybranch分支的代码已经合并到了当前工作区,只要在当前工作区里先解决冲突,然后提交到仓库(git add和git commit -m)即可完成合并。
上述【合并分支...】菜单合并mybranch分支到当前的master分支,可以也使用如下Git命令:
git merge mybranch
不管是如上命令还是【合并分支...】菜单都是使用默认的合并方式,即"快进式合并"(fast-farward merge)。合并前后大致如下示意图,也就是mybranch分支与master分支会合并到一条时间线中。
如果要保留mybranch分支为一段独立的分支线段,则需要使用--no-ff参数关闭"快进式合并"(fast-farward merge),Git命令如下:
git merge --no-ff mybranch
使用--no-ff参数后,会执行正常合并,在Master分支上生成一个新节点。合并后大致如下示意图。
为了保证版本演进路径清晰,我们希望采用这种合并方法。不过这种方法在VS Code中好像没有对应的菜单选项,您可以在VS Code中可以通过 Ctrl+`(ESC 下面,1 左边的键)组合键调出集成终端,使用Git命令来执行这种合并方法。
场景三:团队项目工作流程参考
我们建议团队项目的每一个开发者都采用的基本工作流程有如下四大步,以Git命令方式为例,其中每一步对应的VS Code菜单操作一般都可以在本文前述内容中查找到
。
1、克隆或同步最新的代码到本地存储库;
git clone https://DOMAIN_NAME/YOUR_NAME/REPO_NAME.git
git pull
2、为自己的工作创建一个分支,该分支应该只负责单一功能模块或代码模块的版本控制;
git checkout -b mybranch
git branch
3、在该分支上完成某单一功能模块或代码模块的开发工作;多次进行如下操作:
git add FILES
git commit -m "commit log"
4、最后,先切换回master分支,将远程origin/master同步最新到本地存储库,再合并mybranch到master分支,推送到远程origin/master之后即完成了一项开发工作。
git checkout master
git pull
git merge --no-ff mybranch
git push
这样在Git分支网络图中该工作有一段明确的分叉合并路径,如果整个团队每一项工作都参照这个工作流程,那么最终Git分支网络图中就会留下清晰的项目演进成长路径,比如如下Github上的网络图(Network graph),分支上有两次合并到master。
场景四:Git Rebase
一般我们在软件开发的流程中,有一个朴素的版本管理哲学:开发者的提交要尽量干净、简单。开发者要把自己的代码修改按照功能拆分成一个个相对独立的提交,一个提交对应一个功能点,而且要在对应的 commit log message 里面描述清楚。因此在合并和 push 之前检查修改一下 commit 记录时常需要。
场景四实际就是在场景三团队项目工作流程中增加一步Git Rebase,即在mybranch分支上完成自己的工作之后,为了让 log 记录将来更容易回顾参考,用 git rebase 重新整理一下提交记录。注意不要通过rebase对任何已经提交到远程仓库中的commit进行修改。
git rebase命令格式大致如下:
git rebase -i [startpoint] [endpoint]
其中-i的意思是--interactive,即弹出交互式的界面让用户编辑完成合并操作,[startpoint] [endpoint]则指定了一个编辑区间,如果不指定[endpoint],则该区间的终点默认是当前分支的HEAD。
一般只指定[startpoint] ,即指定从某一个commit节点开始,可以使用HEAD^^、HEAD~100、commit ID或者commit ID的头几个字符来指定一个commit节点,比如下面的代码指定重新整理HEAD之前的三个commit节点。
git rebase -i HEAD^^^
这时会打开命令行文本编辑器大致如下:
pick c5fe513 A
pick ec777a8 B
pick 52c5ac5 C
# Rebase 902d019..52c5ac5 onto 902d019 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
#
# Note that empty commits are commented out
该文本编辑器的用法与Vim大致相同(应该就是内置了一个vi文本编辑器),按i键进入插入编辑模式,可以删除某个版本,也可以修改提交日志消息;按ESC键退出编辑模式回到一般命令模式(Normal Mode),这时按:键进入底线命令模式,输入:wq保存退出、输入:q退出、输入:q!强制退出。
不管做了怎样的编辑操作,退出文本编辑器后,想撤销git rebase操作的话,可以执行如下命令:
git rebase --abort
如果我们编辑删除了B版本,即删除“pick ec777a8 B”一行,然后:wq保存退出,可以看到如下提示:
$ git rebase -i HEAD^^^
Auto-merging git2.md
CONFLICT (content): Merge conflict in git2.md
error: could not apply 52c5ac5... C
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 52c5ac5...
这时用VS Code打开冲突文件大致如下:
可以根据提示选择保留哪个更改,也可以直接编辑文件去掉提示信息。
解决冲突后需要将修改后的文件存入暂存区(git add),最后执行如下命令完成git rebase。
git rebase --continue
删除的B版本的内容很可能会合并到C版本,这时往往需要重新修改C版本的提交日志消息,因此在完成之前会进入文本编辑器修改C版本的提交日志,同样按i键进入插入编辑模式,可以修改C版本的提交日志消息;按ESC键退出编辑模式回到一般命令模式(Normal Mode),这时按:键进入底线命令模式,输入:wq保存退出。保存退出后即完成了git rebase操作。
continue git rebase --
[detached HEAD adcb434] C
1 file changed, 6 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/mybranch.
这时查看提交日志可以发现B版本已经不存在了。
git log
commit adcb434396ca664b11f19ed518f7901a27c81e1f (HEAD -> mybranch)
Author: mengning <mengning@ustc.edu.cn>
Date: Sun Sep 20 11:19:57 2020 +0800
C
commit c5fe51360f8bc010ae8de3cabe0f550240ae78f8
Author: mengning <mengning@ustc.edu.cn>
Date: Sun Sep 20 11:19:07 2020 +0800
最后,和场景三的第4步一样,先切换回master分支,将远程origin/master同步最新到本地存储库,再合并mybranch到master分支,推送到远程origin/master之后即完成了一项开发工作。
Git Rebase的操作较为复杂一些,这里给出一道练习题如下。
在Github.com或Gitee.com上新建一个版本库,并实现如下 commit 网络结点示意图,要求 A 和 B 在本地存在过,但并不出现在远程网络图中。
场景五:Fork + Pull request
前面我们讨论的场景三和场景四都是在紧密合作的开发团队中使用的,这样的开发团队具有良好的信任关系,具有共同遵守的、规范的项目开发流程。但是开源社区开发活动往往是松散的团队,团队成员的技术水平或开发流程往往参差不齐、千差万别。这时如果采用场景三和场景四中推荐的参考工作流程,项目仓库的网络图就会一团糟。
为了解决开源社区松散团队的协作问题,Github提供了Fork+ Pull request的协作开发工作流程。
当你想更正别人仓库里的Bug或者向别人仓库里贡献代码时,要走Fork+ Pull request的协作开发工作流程:
-
先 fork(分叉) 别人的仓库,相当于拷贝一份;
-
做一些 bug fix或其他的代码贡献;
-
发起 Pull request 给原仓库;
-
原仓库的所有者 review Pull request,如果没有问题的话,就会 merge Pull request 到原仓库中。
接下来按步骤简要看一下整个 Pull request 的过程。
第一步,在某个项目页面的右上角点击Fork,如下图右上角的Fork按钮。
系统会以该项目仓库为蓝本为你Fork一个版本库,然后就直接进入新建的版本库,如下图。
注意上图Fork的版本库页面中Pull request按钮。
第二步,可以参考前面场景一、场景二、场景三和场景四的做法,在Fork的版本库中独立工作,最终将bug fix或其他的代码贡献同步到远程的Fork的版本库中。
第三步,创建Pull request。即在上图Fork的版本库页面中找到Pull request按钮,点击Pull request按钮,进入Comparing changes页面,如下图:
在Comparing changes页面可以看到Fork的版本库与原仓库之间所有变更信息,页面上有绿色的Create pull request按钮,点击Create pull request按钮即跳转到原仓库,如下图,即可审核变更信息提交Pull request。
第四步,处理Pull request。即原仓库的所有者 review Pull request,大致如下图可以看到所有的代码变更,如果没有问题的话,就会 merge Pull request 到原仓库中。
Fork + Pull request的具体操作,可以两人一组互相Fork对方的仓库,然后互相提交Pull request演练一下。
至此,我们由简单到复杂、从实际操作到背后的基本原理,并通过VS Code和命令行两种方式相互对照,在五大场景下给出了Git的参考用法。不管您是用到时快速参考,还是希望系统地掌握Git版本管理的技能,相信本文都能为您提供必要帮助。
本文作者孟宁,未经许可禁止转载!
出处:https://mp.weixin.qq.com/s/Km5KuXPETvG0wCGHrvj9Vg
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/14989298.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!