从零开始学习Git

Git最初是Linux的创始者Linus为了应对Linux日益增大的版本管理需求,花了两个周开发出来的。相对于SVN集中式的版本管理而言,Git的优点从使用角度来考虑,可以理解为SVN为两层的,而Git为3层的,除了远程可以进行版本管理之外,在本机也可以进行版本管理,切换管理分支也更快速方便。

本文所有的操作都是基于Windows操作系统。

在学习过程中,查询了一些资料,下面几篇内容对于理解Git的理解非常有用,先列在此处,表示感谢!

https://zhuanlan.zhihu.com/p/45510461

https://www.jianshu.com/p/35b8749be736

https://tonybai.com/2020/04/07/illustrated-tale-of-git-internal-key-concepts/

1 安装

下载地址:Git官网

全程按照默认配置进行安装。

安装完成之后,需要做一下全局配置,标示一下你的身份。这个时候,可以打开一个命令行窗口,执行下面的命令:

git config --global user.name "Your Name"
git config --global user.email "email@example.com"

执行完成后,可以看到在windows系统盘的当前用户目录(比如C:\Users\Administrator\)下面,增加了一个名为.gitconfig的文件,打开可以看到,其中内容正是我们新增的配置信息。

几个有用的配置相关命令:

git config -l  # 列出当前的所有配置
git config -h  # 查看跟配置相关命令的帮助信息

2 核心原理

Git的核心其实是Git数据库,其数据库存储在.git/objects文件夹当中,其key为hash散列值(被拆分为两个部分前2位为objects下的文件夹名,剩余部分构成其下的文件名),其value为文件内容,value内容类型分3种blob, tree, commit

结构如下:

    .git
.git/HEAD
.git/config
.git/objects
.git/objects/info
.git/objects/pack
.git/refs
.git/refs/heads
.git/refs/tags
.git/hooks
.git/hooks/commit-msg.sample
.git/hooks/post-update.sample
.git/hooks/update.sample
.git/hooks/pre-rebase.sample
.git/hooks/pre-applypatch.sample
.git/hooks/fsmonitor-watchman.sample
.git/hooks/applypatch-msg.sample
.git/hooks/pre-receive.sample
.git/hooks/pre-push.sample
.git/hooks/prepare-commit-msg.sample
.git/hooks/pre-commit.sample
.git/info
.git/info/exclude
.git/branches
.git/description  
.git/index

要记录一个文件的版本变化,需要记录:

2.1 记录一个文件变化

  • 执行一次git add [file],git将在数据库中,将该文件内容的当前快照创建为一个blob类型的键值对,进行添加。执行多次git add操作,将会产生多个记录。同时,会将最新的记录信息刷新到index文件当中。

  • 而要记录文件名以及文件组织的变化,需要另外一种类型tree。树对象包含一条或多条记录,每条记录指向一个blob或者tree对象的SHA指针,以及相应的模式,类型,文件名。tree对象可以通过底层命令git write-tree产生。

2.2 提交记录的版本关系

为了记录什么人,什么时间,什么原因提交了哪些文件,就需要另外一个对象commit来进行存储。在一次git commit的操作当中,会生成一个tree来记录(index)当中的快照信息,并生成一个commit对象,来存储当前版本对应的tree对象,提交者等其他信息,以及commit对象的父对象。这个commit对象是快照入口,而通过commit的父对象之间的串联,又可以得到版本变化的整条线。这棵树叫做默克尔数,其底层的变化都会一直向上反馈到根节点上。

commit_object.png

2.3 index-索引

暂存区就是表面理解的索引的意思,对应着.git/index文件的内容。通过它,将工作目录中文件的最新状态与Head中的状态可以作对比。底层命令可以查看内容:

git ls-files -s

其中记录着文件的访问权限,文件对应在objects当中的hash值,以及文件的路径信息和计数信息(计数信息在merge时会有使用)。

一个文件如果想被git来跟踪,需要使用 git add 命令将文件加入到暂存区,命令执行后,会生成该文件对应的blob对象,存储到objects当中。对于已经存在于暂停区的文件,再次使用git add 命令,会再次生成一个新的快照,追加到objects当中,原来的文件对象并不删除。

下面介绍通过 git status 命令执行后,显示的文件状态:

  • untracked: 未跟踪的
  • unmodified: 未修改的,表示文件第一次被加入到index或者文件与head内容一致。
  • modified:修改过的。表示文件内容与head内容不一致。
  • staged: 暂存,等待被commit的,代表下次执行commit会将其写入到版本库当中 。

与索引相关的操作:

git add 将工作目录的变更写入到索引当中。
git add file_path   --添加某个文件到暂存区
git add . 把工作区内修改和新增的文件添加到暂存区
git add -u 仅把修改的文件内容添加到暂存区
git add -A 把工作区内所有的变化都添加到暂存区,包括新增、修改以及删除

git rm file_name 从索引中删除文件,同时该文件也会从工作目录中删除,如果只想从索引中删除,则需要参数--cache
git mv old_file_name new_name 变更索引中的名字
git ls-files 列出当前索引中的内容
git commit 以索引按图索骥,对比工作目录与HEAD中的内容,将差异内容形成tree对象,并添加其中信息,最终形成commit对象,添加到版本库当中。
git restore --staged <file> #丢弃修改

疑问:不同分支的索引文件,保存的位置没有发现在哪里,只确认了切换分支时,其index文件也跟着做了切换。

2.4 分支 branch

创建一个新的分析,会在.git/refs/heads/下创建一个同名文件,该文件的内容实际存储的是某个commit的Hash值,也就是一个指针。分支的原理并不复杂,相当于一次次的commit提交形成的一个链条。

2.4.1 建立分支

git branch <branch_name>

分支建立后,指针指向的位置与当前分支的HEAD位置一样,只是单纯创建了一个指针。

2.4.2 切换分支

git checkout <branch_name>
git checkout -b <branch_name> 创建新的分支并切换到新的分支

需要注意,在切换分支时,如果当前分支中的index上有未commit的变更,在切换分支后,index并不变化。这个时候进行切换并提交,会将之前索引中的未提交的内容提交到切换后的分支版本当中。

2.4.3 合并分支

git merge <branch_name>
git rebase <branch_name>                                                                                                         

将branch_name分支合并到当前分支,一般的操作是在主版本上,将分支版本合并,此时主分支的HEAD指向移动到目标分支处,向前合并fast-forward。

两种合并的区别如下:

     D---E test
    /
A---B---C---F master

merge效果:

     D--------E
    /          \
A---B---C---F---G    test , master

rebase效果:

 A---C---D---E---C `---F` test , master

在操作中。merge操作遇到冲突时候,当前merge不能继续下去。手动修改冲突内容后,add 修改,commit 就可以了
而rebase操作的话,会中断rebase,同时会提示去解决冲突。解决冲突后,将修改add后执行git rebase -continue继续操作,或者git rebase -skip忽略冲突。

2.4.4 删除分支

git branch -d <branch_name>

删除一个分支。如果强制删除,可使用-D参数。

2.5 标签 tag

与分支相同,tag也会创建一个指针,指向某个commit。信息存储在.git/refs/tags下。因为存储的都是指针,这也是git之所以建议频繁使用分支和tag进行版本管理的原因。tag相当于commit的一个贴纸,可以赋予版本更有意义的一个标识。我们可以在开发一个里程碑创建一个标签,来标识发行版本。

git log  # 查询提交历史
git tag <tag_name> <commit_hash> -a -m "tab message" #附注标签
git tag <tag_name> <commit_hash> #轻量标签
git tag -d <tag_name> #删除标签

3 操作本地仓库

  • 创建本地仓库
    执行下面命令,将在当前目录下,创建一个dir_name名称的目录,并将其目录初始化,也即是在dir_name目录,会生成一个.git隐藏目录,其中创建了git的版本数据库。
git init dir_name

现在我们有了自己的版本仓库了,根据前面的讲解,现在就可以自由地对我们代码进行管理了。有个最常用的命令我们需要首先介绍下:

git status  # 查看当前工作目录的状态

通过该命令我们可以清晰地看到当前工作目录所在的分支,工作目录中文件的修改状态,同时会给出可能有用的命令提示,告诉我们下一步如何操作。

下面按照常用的使用场景来进行说明。

3.1 保存修改

  • 将新增文件保存到索引。

    参考 git add 命令。

  • 将当前修改作一次提交。

    参考 git commit 命令。

3.2 回滚操作

  • 回滚未添加到索引的操作
git restore -W file_name
  • 回滚到已经添加到索引未提交版本的操作
git restore -S file_name
  • 回滚commit
git reset --hard HEAD^
git rebase -i HEAD~n

如果是回滚多次commit,使用rebase,需要注意的是,如果回滚3个版本,则这3个版本都没有了,而如果我们只想还原版本树中的某次提交,并不想将这次修改之后的修改历史一起回滚,那么使用revert。

git revert -n commit_hash

3.3 暂存与恢复

我们不能保证总是单线程来工作,当手头的代码撸到一半的时候,可能随时都会有一个bug来打扰我们,需要我们放弃手头的代码来转至其他战线。

git stash # 将当前未提交的所有变更都打包存储起来,将工作目录恢复至修改之前
git stash pop # 将之前的最后一次未完成的工作状态放回工作目录

在我们执行git stash 之后,可以发现在.git/refs目录下面会增加一个stash文件,这相当于一个临时变更的一个快照。我们可以多次暂存,也可以自己决定释放哪次暂存。更多的功能可以参考git stash -h

4 操作远程库

记住,你不是一个人在战斗!我们已经可以在本机通过Git轻松地管理我们的代码了,而要这种控制扩展到整个项目组,真至全世界,只需要在远端再架设一个版本库,将我们的版本可以再提交到远程库即可。

远程仓库,国外最著名的就是GitHub,而国内最早的是Gitee,而目前一线大厂也推出了自己的代码管理库,有兴趣的同学可以自行百度一下。

在这种模式下面,整个代码的管理结构如下:

本地工作目录-->本地版本库---->远程个人版本库----->远程项目版本库

与我们原本的预期相比,增加了一个远程个人版本库,之所以增加这样的一个中间环节,我们可以假设下面一种场景:我在公司的代码没有写完,需要回家或者去客户现场继续开发,总不可能带着公司的台式机再到处跑。这个时候,提交个版本到远程个人版本库,到了另外一个地方,再把代码拉取下来,继续进行操作即可。等我们负责的特性开发并测试完成,将其合入到远程个人版本库,再从这个地方发起一个合并请求,在项目组成员都Review了代码,各种测试做完之后,就可以将其并入到项目的远程版本库当中了。

先不探究怎么创建一个远程库,其实也没有啥神秘的,就是把本地的版本库上传一份就上去就行了,其他的角色管理,权限控制啥的,我们先不关心。先来看看怎样从远程库fork一份工程到本机吧,这是我们参与开源的第一步。

  • fork一个远程库到本机当前目录,该操作会将服务器上的顶层目录一起下载到当前目录。 我们下载下来的版本库与远端没有什么不同,同样包含了各个分支,各个版本的提交记录。

    git <clone RepositoryURL>
    

在远程仓库Fork到本地之后,再与远程仓库交互,就是一种增量的变更请求了。

  • 拉取远程的更新到本机。
git fetch <remote> <branch> #将远端的版本更新记录拉到到本机
git pull <remote> <branch> # 将远端的版本内容拉到到本机,并进行合并,相当于fetch+merge操作

所以,如果只是想确认一下远端版本库的内容,并不想真的合并,那就fetch一下。

  • 将本机的版本推送至远端
git push <remote> <branch_name>
  • 每次提到远端版本库,都使用RUL,太费劲了,那么我们来给他取个别名吧
git remote add <alias> <remote_url>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容