Git学习

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

集中式工作流

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

在这里插入图片描述

集成管理者工作流

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

在这里插入图片描述

司令与副官工作流

  1. 普通开发者在自己的特性分支上工作,并根据 master 分支进行变基。 这里是司令官的master分支。
  2. 副官将普通开发者的特性分支合并到自己的 master 分支中。
  3. 司令官将所有副官的 master 分支并入自己的 master 分支中。
  4. 司令官将集成后的 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/ :保存暂存区信息
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值