0%

分布式版本控制系统-Git

本文主要包括Git的用法加底层

使用

初始化相关

# --system 操作系统级别; --global 用户级别; 不加: 项目级别 
$ git config --global user.name "zouxxyy"
$ git config --global user.email "xxxxxxxxxxxxxx"

# 查看配置
$ git config --list

# 创建git对象,会生成 .git 目录
$ git init
$ ls
HEAD 指示当前分支的文件
config 配置文件
hooks. (目录) 存放钩子脚本,比如提交代码前或者后的自动执行操作
objects (目录) 存放所有历史记录的
branches 指示目前被检测出的分支
description 仓库描述性信息的文件
info (目录) 存放全局性的排除文件。比如 mac 进去查看就有.DS_Store
refs (目录) 存放分支与tags的提交对象的指针

文件相关

# 添加新文件(由未跟踪到跟踪状态,同时也是暂存状态)或者添加修改文件(由修改状态到暂存状态)(粗略这样说,其实底层做了更多事)
$ git add test.txt

# 提交文件(由暂存状态到提交状态);可加参数 -a 跳过 add 步骤(未跟踪的不能提交)
$ git commit -m "commit message"

# 查看文件的状态
$ git status

# 查看未被 add 的修改
$ git diff

# 查看已 add 但未被 commit 的修改
$ git diff --staged

# 删除文件 (同时让文件由跟踪变成未跟踪状态)(注意它 和手动删除文件再执行 git add 效果一样)
$ git rm test.txt

# 重命名
$ git mv test.txt new.txt

# 查看 commit log;可加 --oneline 参数简写
$ git log

# 完整 commit log(只要改动了HEAD)
$ git reflog

分支相关

# 创建分支
$ git branch branchname

# 切换分支 (切换前保证 status 干净)
$ git checkout branchname

# 创建并切换
$ git checkout -b branchname

# 显示所有分支 可加参数 -v 查看它们的最后一次提交
$ git branch

# 删除分支(需先切到另一个分支)可加参数 -D 强制删除
$ git branch -d branchname

# 创建分支,并指向指定的提交对象 (时光机)
$ git branch branchname commitHash

# 分支合并 (合并前切回主分支,再合并需要合并的分支)
# 合并可能会产生冲突,需要手动修改冲突的文件,再提交
$ git merge otherBranch

stash相关

# 有时想切分支,但又不想提交,可以用到 stash 功能,是一种暂存的栈。这个栈是针对分支的~

# 查看当前分支的 stash
$ git stash list

# 将未提交的改动 入栈(接着可以安全执行切分支操作)
$ git stash

# (切回分支后)弹出 改动
$ git pop

# 后面两个一般不用,我们栈里只放一个元素,git stash 入栈,git pop 出栈

# 只取指定改动,不删除
$ git stash apply stashName

# 删除指定的栈里的东西
$ git stash drop stashName

回退相关

# 撤回工作目录的修改(未add -> 未修改)
$ git checkout -- fileName

# 撤回暂存区的修改 (未commit -> 未add)
$ git reset [HEAD] fileName

# 可用于修改刚刚提交的message(相当于后退一步,再重新提交)
$ git commit --amend

# 注意下面3个命令都可以撤回到指定提交对象(把 HEAD~ 改为指定的 commitHash 即可)

# 撤回上一次提交 (commit -> 未commit)
$ git reset --soft HEAD~

# 撤回上一次提交同时撤回暂存区的修改 (commit -> 未add)
$ git reset [--mix] HEAD~

# 撤回上一次提交同时撤回暂存区的修改同时撤回工作目录的修改 (commit -> 未修改,危险!)
$ git reset --hard HEAD~

# 撤回少用 reset --hard,最好使用 branch
$ git branch recoverBranchName commitHash

标签相关

# 列出所有tag
$ git tag

# 打tag
$ git tag tagName commitHash

# 删除指定tag
$ git tag -d tagName

# 切到指定tag,然后创新分支,防止头部分离
$ git checkout tagName
$ git checkout -b branchName

远程相关

# 查看全部远程分支
$ git remote -v

# 添加远程分支别名(注意:orgin 只个别名)
$ git remote add orgin https://xxxx.git

# 克隆远程仓库到本地(此时自动创建全部远程跟踪分支,同时生成一个关联远程master的本地分支(注意))
$ git clone https://xxxx.git

# 由本地分支创建远程跟踪分支,再推到远程分支,但并不关联(注意)
# 推荐仅当创建一个新分支,并想把它推到远程时,才使用它;并且接着执行关联操作,也就下一行,之后仅用 git push 即可
$ git push orgin branchName

# 本地分支(已创建)关联远程跟踪分支
$ git branch -u orgin/branchName

# 拉取远程分支(全部分支)到远程跟踪分支中
# 推荐仅当远程有了新分支时,才使用它(注意)
$ git fetch orgin

# 创建一个本地分支,同时关联远程跟踪分支
$ git branch -b branchName orgin/branchName
$ git branch --track orgin/branchName (效果一样,快捷写法)

# 拉去远程分支到远程跟踪分支和本地分支(本地分支已经关联远程跟踪分支),相当于执行 git fetch + git merge
$ git pull

# 删除远程分支
$ git push orgin --delete branchName

# 列出没用远程跟踪分支,并删除
$ git remote prune orgin --dry-run
$ git remote orgin

看起来比较复杂,但只要记住一个逻辑链就好,也就是按以下3步走:

  1. 建立远程跟踪分支

    • git clone https://xxxx.git
    • git push orgin branchName新分支到远程
    • git fetch orgin 拉远程的新分支
  2. 本地分支关联远程跟踪分支

    • git checkout branchName 创建本地新分支(和远程分支同名) 并关联 (还是它简单)
    • git branch -u orgin/branchName 当前分支 关联
    • git branch --track orgin/branchName 创建本地新分支 关联
  3. 愉快使用

    • git pull 拉到本地
    • git push 推到远程(先 pull,解决冲突再 push)

上面是自己团队的项目(有权限),当想为开源项目做贡献(没权限)时,使用 pull request, 大致流程(用到再研究)为:

  1. folk 一份

  2. 在 folk 的库中,作出改动并提交,注意提交前先拉远程库,保持最新。

  3. 进网站(如github),点 pullRequest

底层

git 的底层数据结构是一种在 object 目录中存放的 k-v 类型的数据。key 是 hash值 (前两位是子文件夹名,后面是文件名),value 是 数据内容。

数据内容 分为 git 对象blob)、树对象tree)、提交对象commit

git 对象

代表文件,它是真正存数据的。它是一对一的(注意:一个文件的历史文件都算一个单独的文件)。

# 向数据库中写数据,并返回hash键值。存的是一个blob对象
$ git hash-object -w test.txt
915c628f360b2d8c3edbe1ac65cf575b69029b61

# 数据是经过压缩的,可以用cat-file查看该数据
# -p 显示内容; -t 查看类型
$ git cat-file -p 915c628f360b2d8c3edbe1ac65cf575b69029b61
hello git

树对象

代表版本,可以理解成一个树对象指向多个 git 对象,形成一个版本。

# 暂存区 写入 test.txt 的首个版本(指针信息)
# --add 文件首次添加;--cacheinfo 表示添加的文件位于git数据库;100644 文件类型; test.txt 文件名
$ git update-index --add --cacheinfo 100644 915c628f360b2d8c3edbe1ac65cf575b69029b61 test.txt

# 查看暂存区
$ git ls-files -s
100644 915c628f360b2d8c3edbe1ac65cf575b69029b61 0 test.txt

# 生成树对象。暂存区未清空
$ git write-tree
72203871fa4668ad777833634034dcd3426879db

# 查看树对象内容
$ git cat-file -p 72203871fa4668ad777833634034dcd3426879db
100644 blob 915c628f360b2d8c3edbe1ac65cf575b69029b61 test.txt

提交对象

相当于对版本添加描述信息。一个提交对象封装一个树对象,而且它是链式的,里面指向上一个提交对象。

# 生成提交对象,后接树对象hash值
# 这里是第一次提交。以后可以加 -p 指定父提交对象
$ echo 'first commit' | git commit-tree 72203871fa4668ad777833634034dcd3426879db
5402d3560f35d66f7f1e4e4665dd63bf3d786495

# 查看提交对象的内容
$ git cat-file -p 5402d3560f35d66f7f1e4e4665dd63bf3d786495
tree 72203871fa4668ad777833634034dcd3426879db
author zouxxyy <xxxxxxxxx@qq.com> 1575450654 +0800
committer zouxxyy <xxxxxxxxx@qq.com> 1575450654 +0800

first commit

文件相关

  • git add test.txt 相当于先将文件做成git对象存入object ,再将它(指针信息)放入暂存区,等待提交。该操作是绝对安全的,因为数据已经写入文件中。所以可以理解为啥只有 git add,而没有什么git change之类的,因为修改也会加git对象

  • git commit -m "xx" 由暂存区生成树对象,再生成提交对象。注意暂存区的东西不删除

  • git rm test.txt 相当于将该文件对应的(指针信息)从暂存区中删除,同时删除工作目录中的文件

分支相关

分支切换会切换工作目录,所以切换前,先把当前分支该提交的提交了,保证工作目录干净

  • 2个重要的东西:

    • refs 存放各个分支(和tags)的提交对象的指针(hashkey)
    • HEAD 指明当前分支的指针位置
  • git branch branchname 相当于在 refs/heads 目录中新建以分支名命名的当前分支的提交对象的指针

  • git checkout branchname 更改HEAD文件;切换工作目录影响暂存区

  • git commit -m "commit message" 更新 refs 中对应分支的提交对象

回退相关

git checkout branchNamegit reset --hard commitHash 的区别 :

  • checkout 只改HEAD ;reset HEAD 和 分支指针的一起改

  • checkout 对工作目录是安全的;reset --hard完全强制覆盖工作目录