Git学习

    xiaoxiao2022-07-13  157

    Git

    本文是读《progit_v2.1.16》(提取码: vdyx )一书的学习笔记。

    发展

    本地版本控制系统

    用复制整个项目目录的方式来保存不同版本,例如写论文;

    坏处:有时会混淆工作目录,可能写错或覆盖意想外的文件。

    集中式版本控制系统

    有一个单一的集中管理的服务器,保存所有文件的修订版本。协同工作的人(客户端)取出最新的文件或提交更新。

    好处:相对于本地版本控制,每个人可以一定程度上看到其他人在做什么,管理员也能轻松掌握开发者的权限。

    坏处:中央服务器的单点故障。如果宕机一小时,这一小时内都无法提交更新,也无法协同工作;如果磁盘损坏,又没有备份,那将丢失所有数据,只剩各机器上的单独快照。

    分布式版本控制系统

    如Git。客户端不止提取最新文件快照,而是把代码仓库完整地镜像下来。也是一次完整备份。还可以指定和若干不同的代码仓库进行交互。

    Git基础

    直接记录快照,而非差异比较

    其他系统:以文件变更列表的方式存储信息,存储每个文件与初始版本的差异。

    Git:把数据看作是对小型文件系统的一组快照,并保存快照的索引。

    近乎所有操作都是本地执行

    因为在本地磁盘就有项目的完整历史,所以大部分操作看起来瞬间完成,少了集中式版本控制的网络延时开销。所以离线也能工作、提交,等到有网络时再上传。

    Git保证完整性

    Git 中所有数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内 容或目录内容。 这个功能建构在 Git 底层,是构成 Git 哲学不可或缺的部分。 若你在传送过程中丢失信息或损坏 文件,Git 就能发现。

    Git 用以计算校验和的机制叫做 SHA-1 散列(hash,哈希)。 这是一个由 40 个十六进制字符(0-9 和 a-f)组 成字符串,基于 Git 中文件的内容或目录结构计算出来。 SHA-1 哈希看起来是这样:

    24b9da6552252987aa493b52f8696cd6d3b00373

    实际上,Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。

    Git一般只添加数据

    你执行的 Git 操作,几乎只往 Git 数据库中增加数据。 很难让 Git 执行任何不可逆操作,或者让它以任何方式清 除数据。 同别的 VCS 一样,未提交更新时有可能丢失或弄乱修改的内容;但是一旦你提交快照到 Git 中,就难 以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。

    三种状态

    Git 有三种状态,你的文件可能处 于其中之一:已提交(committed)、已修改(modified)和已暂存(staged)。 已提交表示数据已经安全的 保存在本地数据库中。 已修改表示修改了文件,但还没保存到数据库中。 已暂存表示对一个已修改文件的当前 版本做了标记,使之包含在下次提交的快照中。

    由此引入 Git 项目的三个工作区域的概念:Git 仓库、工作目录以及暂存区域。

    指令

    > git clone [url] // 克隆仓库 > git init // 创建.git子目录,包含初始化Git仓库的所有必须文件 > git add . // 跟踪文件,把工作区的所有变化提交到暂存区 > git commit -m "" // 提交 > git status // 检查当前文件状态

    > git diff // 比较工作目录中当前文件和暂存区快照之间的差异 > git commit -a // 跳过暂存区直接将已跟踪文件提交,不推荐 > git rm xx.xx // 从暂存区移除,且从工作目录中删除 > git mv file_from file_to // 文件移除/改名

    查看提交历史

    > git log -p // 显示每次提交的差异 > git log --stat // 显示提交的简略统计信息 > git log --pretty=oneline // 一行显示 ...... //还可以按照时间显示,控制显示格式等等

    撤销操作

    > git commit --amend // 重新提交 > git reset --soft [commit-id] // 回退到某个版本,只回退commit信息 > git reset HEAD <file> // 回退提交和暂存,相当于 git reset --mixed > git reset --hard [commit-id] // 回退提交、暂存和工作目录 > git checkout -- <file> // 撤销对某个文件的修改,很危险!做的任何修改都会消失 ...... //删除的分支和用--amend覆盖的提交也可以恢复

    远程仓库

    > git remote // 列出你指定的每一个远程服务器的简写。如果已经克隆了自己的仓库,那么至少应该能看到origin- 这是Git克隆的仓库服务器的默认名字 > git remote -v // 显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL > git remote add hbl [url] // 添加新的远程Git仓库,可以在命令行中使用hbl字符串代替url > git fetch [remote-name] // 从远程仓库拉取数据到本地仓库,并不会自动合并,需要手动合并 > git push [remote-name] [branch-name] // 如将 master 分支推送到 origin 服务器 > git remote show origin // 查看某一个远程仓库的更多信息 > git remote rename hbl hbl2 //修改一个远程分支的简写名 > git remote remove hbl2 // 移除一个远程仓库

    打标签

    以示某次提交的重要,比如标记发布节点(v1.0)

    Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated)。

    轻量标签:很像一个不会改变的分支 - 它只是一个特定提交的引用。附注标签:是存储在 Git 数据库中的一个完整对象。

    通常建议创建附注标签。

    > git tag // 列出标签 > git tag -l 'v1.8.5*' //查找特定标签 > git tag -a v1.4 -m 'my version 1.4' // 创建一个附注标签。-m 选项指定了一条将会存储在标签中的信息。如果没有为附注标签指定一条信息,Git会运行编辑器要求你输入信息。 > git show v1.4 // 显示标签信息与对应的提交信息 > git tag -a v1.2 9fceb02 -m "" // 为某次提交打标签 > git push origin v1.5 // 默认情况下,git push 命令并不会传送标签到远程仓库服务器上。 在创建完标签后你必须显式地推送标签到 共享服务器上 > git push origin --tags // 这将会把所有不在远程仓库 服务器上的标签全部传送到origin > git checkout -b version2 v2.0.0 // 在特定的标签上创建一个新分支,当然,如果在这之后又进行了一次提交,version2 分支会因为改动向前移动了,那么 version2 分支就会和 v2.0.0 标签稍微有些不同,这时就应该当心了。

    Git 别名

    > 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分支

    为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会 为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中(Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:

    $ git add README test.rb LICENSE $ git commit -m 'The initial commit of my project'

    当使用git commit进行提交操作时,Git会先计算每一个子目录(本例中只有项目根目录)的校验和,然后在 Git 仓库中这些校验和保存为树对象。 随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外, 还包含指向这个树对象(项目根目录)的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照。

    现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象 索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)。

    做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。

    Git 的分支,其实本质上仅仅是指向提交对象的可变指针。

    由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁 都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单 能不快吗?

    > git branch hbl3 // 创建一个分支hbl3 > git checkout hbl3 // 切换到hbl3分支 > git checkout -b hbl3 // 上面两个操作的合并操作 > git log --oneline --decorate --graph --all // 查看项目分叉历史 > git checkout master > git merge hotfix // 合并hotfix分支到master分支。若有冲突需手动解决,然后git add .暂存后Git就会将它们标记为冲突已解决 > git pull // 相当于执行上面两个指令 > git branch -d hotfix // 删除本地分支 > git push origin --delete serverfix // 删除远程分支。这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所 以如果不小心删除掉了,通常是很容易恢复的。

    长期分支

    特性分支

    是一种短期分支,它被用来实现单一特性或其相关工作。

    远程分支

    origin 是执行git clone 时默认的远程仓库名字

    master 是执行git init 时默认的起始分支名字

    > git fetch origin // 抓取远程仓库有而本地没有的数据 > git push (remote) (branch) // 推送到远程仓库

    跟踪分支

    当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。

    > git checkout -b hbl2 origin/hbl2 // 创建一个本地分支跟踪远程分支 > git checkout --track origin/serverfix // 与上述写法等效 > git branch --set-upstream-to origin/serverfix // 已有本地分支,跟踪远程分支或修改上游分支 > git branch -vv // 查看设置的所有跟踪分支

    实际项目操作步骤

    // 新建一个空目录 > git init // 生成一个隐藏目录.git,里面有很多文件,就是控制和管理版本库的 > git remote add origin [url] // 添加远程仓库 > git fetch [remote-name] // 获取数据 [remote-name] = origin > git checkout -b hbl2 origin/hbl2 // 跟踪远程分支,参考上述3种写法

    变基

    原理是首先找到这两个分支的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。

    > git checkout hbl2 > git rebase master // 当前分支 experiment、变基操作的目标基底分支 master > git checkout master > git merge experiment // 合并

    压缩提交

    方式一:变基
    方式二:压缩扩展命令
    > git commit-squash --fetch --message "" > git push -f

    服务器上的Git

    本地协议

    使用共享文件系统,团队每一个成员都对其(例如一个挂载的 NFS)拥有访问权。

    优点:

    简单、只需把裸版本库对副本放在大家都可以访问对路径,设置好读写权限,就可以了。

    缺点:

    共享文件系统比较难配置,并且比起基本的网络连接访问,这不方便从多个位置访问。这个协议并不保护仓库避免意外的损坏。 每一个用户都有“远程”目录的完整 shell 权限,没有方法可以 阻止他们修改或删除 Git 内部文件和损坏仓库。

    HTTP协议

    智能(Smart) HTTP 协议

    智能” HTTP 协议的运行方式和 SSH 及 Git 协议类似,只是运行在标准的 HTTP/S 端口上并且可以使用各种 HTTP 验证机制,这意味着使用起来会比 SSH 协议简单的多,比如可以使用 HTTP 协议的用户名/密码的基础 授权,免去设置 SSH 公钥。

    优点:

    不同的访问方式只需要一个 URL 以及服务器只在需要授权时提示输入授权信息,这两个简便性让终端用户使用 Git 变得非常简单。 相比 SSH 协议,可以使用用户名/密码授权是一个很大的优势,这样用户就不必须在使用 Git 之前先在本地生成 SSH 密钥对再把公钥上传到服务器。 对非资深的使用者,或者系统上缺少 SSH 相关程序 的使用者,HTTP 协议的可用性是主要的优势。 与 SSH 协议类似,HTTP 协议也非常快和高效。

    缺点:

    在一些服务器上,架设 HTTP/S 协议的服务端会比 SSH 协议的棘手一些。

    SSH协议

    架设 Git 服务器时常用 SSH 协议作为传输协议。SSH 协议也是一个验证授权的网络协议;并且,因为其普遍性,架设和使用都很容易。

    > git clone ssh://user@server/project.git // 通过SSH协议克隆版本库,可以指定一个ssh://的URL > git clone user@server:project.git // 另一种简单的写法。也可以不指定用户,Git 会使用当前登录的用户名。

    优点:

    SSH 架设相对简单。通过 SSH 访问是安全的, 所有传输数据都要经过授权和加密。SSH 协议很高效,在传输前也会尽量压缩数据。

    缺点:

    不能通过他实现匿名访问。

    Git协议

    这是包含在 Git 里的一个特殊的守护进程;它监听在一个特定的端口(9418),类似于SSH 服务,但是访问无需任何授权。

    优点:

    Git 协议是 Git 使用的网络传输协议里最快的。它使用与 SSH 相同的数据传输机制,但是省去了加密和授权的开销。

    缺点:

    缺乏授权机制。最难架设。 它要求有自己的守护进程,还要求防火墙开放 9418 端,但是企业防火墙一般不会开放这个非标准端口。 而大型的企业防火墙通常会封锁这个端口。

    在服务器上搭建Git

    把现有仓库导出为裸仓库

    //创建一个空目录 > git init hbl-repositry // 目录下会生成一个仓库文件夹hbl-repositry,其中包含隐藏目录.git > git clone --bare hbl-repositry hbl-repositry.git // 把现有仓库导出为裸仓库,里面的内容是 .git里的内容。即取出Git仓库自身,不要工作目录,然后特别为它单独创建一个目录。

    把裸仓库放到服务器上

    //本地执行 > scp -r hbl-repositry.git root@192.168.8.112:~/hbl/git // 假设服务器上存在 /hbl/git/ 目录,复制裸仓库来创建一个新仓库 > git clone root@192.168.8.112:~/hbl/git/hbl-project.git // 其他通过 SSH 连接这台服务器并对 /opt/git 目录拥有可读权限的使用者,通过该命令就可以克隆你的仓库。 //服务器上执行 > git init --bare --shared//如果到该项目目录中运行git init命令,并加上--shared选项,那么Git会自动修改该仓库目录的组权限为可写。

    注意:裸仓库不包含工作区,所以并不会存在在裸仓库上直接显示提交变更的情况。但实际上已经push了的,可以通过在另一个目录下克隆检验。

    分布式Git

    集中式工作流

    若干个开发者则作为节点——也就是中心仓库的消费者——并且与其进行同步。

    集成管理者工作流

    项目维护者推送到主仓库。贡献者克隆此仓库,做出修改。贡献者将数据推送到自己的公开仓库。贡献者给维护者发送邮件,请求拉取自己的更新。维护者在自己本地的仓库中,将贡献者的仓库加为远程仓库并合并修改。维护者将合并后的修改推送到主仓库。

    司令与副官工作流

    普通开发者在自己的特性分支上工作,并根据 master 分支进行变基。 这里是司令官的master分支。副官将普通开发者的特性分支合并到自己的 master 分支中。司令官将所有副官的 master 分支并入自己的 master 分支中。司令官将集成后的 master 分支推送到参考仓库中,以便所有其他开发者以此为基础进行变基。

    在这里插入图片描述

    这种工作流程并不常用,只有当项目极为庞杂,或者需要多级别管理时,才会体现出优势。

    为发布打标签

    当你决定进行一次发布时,你可能想要留下一个标签,这样在之后的任何一个提交点都可以重新创建该发布。

    Git工具

    选择修订版本

    > git reflog // 引用日志,保存在本地,记录了最近几个月的HEAD和分支引用所指向的历史 > git show HEAD^ // 祖先引用,查看HEAD的父提交 > git show HEAD~ // 与上述引用等价 > git show d921970^2 // 代表 “d921970 的第二父提交” 这个语法只适用于合并 (merge)的提交,因为合并提交会有多个父提交。 第一父提交是你合并时所在分支,而第二父提交是你所合并的分支。 > git show d921970~2 // 表示第一父提交的第一父提交 等价于 git show d921970^^ 双点: > git log master..experiment // 显示在 experiment 分支中而不在 master 分支中的提交 > git log origin/master..HEAD // 查看即将推送到远端的内容。这个命令会输出在你当前分支中而不在远程 origin 中的提交。 如果你执行了 git push 并且你的当前分支正在跟踪 origin/master,git log origin/master..HEAD 所输出的提交将会被传输到远端服务器。如果你留空了其中的一边,Git会默认为HEAD。例如,git log origin/master..将会输出与之前例子相同的结果。 多点: > git log refA refB --not refC // 查看所有 被 refA 或 refB 包含的但是不被 refC 包含的提交 三点: > git log master...experiment // 查看 master 或者 experiment 中包含的但不是两者共有的提交

    储藏与清理

    > git stash // 项目中改动了几个文件,其中一些放在了储藏区,然后想切换分支但又不想提交之前的工作,就需要储藏修改,将储藏推送到栈上,不会储藏未跟踪文件 > git stash --include-untracked // 储藏包括未跟踪文件 > git stash list // 查看储藏列表 > git stash apply // 应用储藏,但是之前暂存到文件不会重新暂存 > git stash apply stash@{2} // 应用某个储藏 > git stash apply --index // 重新应用暂存的修改 > git stash drop // 移除储藏 > git clean // 清理工作目录,移除未被跟踪的文件,慎用!!!! > git stash --all // 移除每一样东西并存放在栈中

    搜索

    > git grep -n gmtime_r // 默认情况下 Git 会查找你工作目录的文件。可以传入 -n 参数来输出 Git 所找到的匹配行行号 > git grep -p gmtime_r *.c // 想看匹配的行是属于哪一个方法或者函数 > git log -SZLIB_BUF_MAX --oneline //Git 日志搜索,想找到 ZLIB_BUF_MAX 常量是什么时候引入

    重写历史

    > git commit --amend // 重新提交 > git filter-branch --tree-filter 'rm -f passwords.txt' HEAD // 从每一个提交移除一个文件

    重置揭密

    下面的速查表列出了命令对树的影响。 “HEAD” 一列中的 “REF” 表示该命令移动了 HEAD 指向的分支引 用,而`‘HEAD’’ 则表示只移动了 HEAD 自身。 特别注意 WD Safe? 一列 - 如果它标记为 NO,那么运行该命 令之前请考虑一下。

    高级合并

    > git merge -Xignore-space-change whitespace // 忽略所有空白修改 > git reset --hard HEAD~ // 撤销合并

    子模块

    > git submodule add [url] // 添加一个子模块,会产生一个新的 .gitmodules 文件,该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射 > git submodule update // 从子模块仓库中抓取修改时,Git将会获得这些改动并更新 子目录中的文件,但是会将子仓库留在一个称作 “游离的 HEAD” 的状态。 这意味着没有本地工作分支(例如 “master”)跟踪改动。 所以你做的任何改动都不会被跟踪。

    打包

    > git bundle create repo.bundle HEAD master // 就会有一个名为 repo.bundle 的文件,该文件包含了所有重建该仓库 master 分支所需的数据。bundle命令会将git push命令所传输的所有内容打包成一个二进制 文件,你可以将这个文件通过邮件或者闪存传给其他人,然后解包到其他的仓库中。如果你在打包时没有包含HEAD引用,你还需要在命令后指定一个-b master或者其他被引入的分支,否则 Git 不知道应该检出哪一个分支。 > git clone repo.bundle repo // 使用

    自定义Git

    Git 使用一系列配置文件来保存你自定义的行为。 它首先会查找 /etc/gitconfig 文件, 该文件含有系统里每位用户及他们所拥有的仓库的配置值。 如果你传递 --system 选项给 git config,它就会读写该文件。

    接下来 Git 会查找每个用户的 ~/.gitconfig 文件(或者 ~/.config/git/config 文件)。 你可以传递 --global 选项让 Git 读写该文件。

    最后 Git 会查找你正在操作的版本库所对应的 Git 目录下的配置文件(.git/config)。 这个文件中的值只对该版本库有效。

    以上三个层次中每层的配置(系统、全局、本地)都会覆盖掉上一层次的配置,所以 .git/config 中的值会覆盖掉 /etc/gitconfig 中所对应的值。

    Git内部原理

    执行 git init 时,Git 会创建一个 .git 目录。 这个目录包含了几乎所有 Git 存储和操作的对象。 如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。

    目录的结构:(重要的4个条目)

    HEAD :指向目前被检出的分支 config* :包含项目特有的配置选项 ~~description :~~仅供GitWeb程序使用,我们无需关心 hooks/ :包含客户端或服务端端钩子脚本 info/ :包含一个全局性排除(global exclude)文件 ,用以放置那些不希望被记录在 .gitignore 文件中的忽略模式 objects/ :存储所有数据内容 refs/ :指向数据(分支)的提交对象的指针 index/ :保存暂存区信息
    最新回复(0)