前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >git 实践总结

git 实践总结

原创
作者头像
JarvisChu
修改2019-12-06 10:38:33
1.6K0
修改2019-12-06 10:38:33
举报
文章被收录于专栏:Jarvis-TechJarvis-Tech

git 实践总结

toc

Git 官方中文文档

概念

远程库 Remote

本地库 Repository

缓存区 Index/Stage

工作区 workspace

我们在工作区中修改文件,

改动完成之后 git add 添加到缓存区

再通过git commit缓存区中改动递交到本地库

最后,通过git push本地库中的改动推送到远程库

git.png
git.png

最佳实践

删除分支

代码语言:txt
复制
git branch -D mylocalbranch #删除本地分支
git push origin --delete myremotebranch #删除远程分支

撤销本地分支的更改

撤销最近的修改

代码语言:txt
复制
git reset HEAD^        #相当于 unstage,等同于 --mixed
git reset --hard HEAD^ #改动彻底消失

--mixed: 相当于unstage, 缓存区中的递交会被撤销,但工作区中不会变 --hard: 缓存区和工作区中的修改都会被撤销

撤销中间某次的修改

代码语言:txt
复制
git revert commit-id

撤销远程分支的更改

情况1, 撤销最近的几次递交

代码语言:txt
复制
#先将本地分支reset
git reset --hard HEAD~3 

#再 -f 强制push到远程
git push -f

#这种方式,在git log中是完全看不到被撤销的递交已经撤销操作的

情况2,撤销中间某次的递交

代码语言:txt
复制
#checkout到本地,revert指定的commit-id,然后push到远端
git checkout remove-branch
git revert commit-id #如果是merge, 使用 -m 1
git push

合并多个commit

「Git」合并多个 Commit

代码语言:txt
复制
#比如有三次提交
$ git log --pretty=oneline
0151917b7f3584c982192973bef9fbe5cd7e87a3 (HEAD -> testing_tmp) modify-3
689ef007dd673300423f9d25a5fa18da54833f76 modify-2
10d748ec1965cfa1c79db9b86008e0a9863b18a0 modify-1

#现在想把第二次和第三次合并成一个递交
git rebase -i 10d748ec

或者 使用 git rebase -i HEAD~3 将最近三次的递交合并

执行上述命令后,会进入vi,将modify-3前的 pick 改成s即可。

代码语言:txt
复制
pick 689ef007 modify-2
pick 0151917b modify-3

# Rebase 10d748ec..0151917b onto 10d748ec (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# 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

git rebase 处理本地分支和远程分支的分叉

git rebase

本地分支和远程分支各自有提交,这时,如果直接pull,会要求将远程分支merge到本地,这样虽然可以,但是确实产生一个无用的merge 提交记录。

git rebase 可以解决这个问题。比如分支情况是:

代码语言:txt
复制
          A---B---C topic
         /
    D---E---F---G master

执行git rebase 之后,会将本地的改动连接到远程改动之后。

代码语言:txt
复制
                  A'--B'--C' topic
                 /
    D---E---F---G master

远程库要创建成裸仓库

git init --bare .,会将当前目录作为git目录(即把当前目录作为.git目录)。

说明:bare库只有git的记录,没有工作区。所以,只可以查看,却不能递交。

作用:如果不适用bare, 远程库本身工作在某个分支,如master,本地库向远程库push master分支时,可能会产生冲突。 所以默认情况下push会被拒绝。使用bare,就解决了这个问题,因为远程库上checkout出分支,本地库的push永远不会远程库产生冲突。

bare库的当前目录名约定是 xxx.git ,这也就是为什么我们clone的github的库都是.git结尾。

示例

代码语言:txt
复制
#创建四个空目录
mkdir repo repo.git repo_local repo.git_local

#创建远程仓库
git init repo  #repo中创建普通仓库,等同于 cd repo && git init .
git init --bare repo.git #repo.git中创建裸库

#--- 至此远程仓库创建完毕,接下来分别clone到本地进行操作 ---

#clone 仓库到本地
git clone repo repo_local
git clone repo.git repo.git_local

#在repo_local中进行操作 【会无法push】
cd repo_local
echo "HelloWorld" > test.txt
git add test.txt
git commit -m "add file"
git push   # !!!出错,信息如下 !!!

remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require 'git reset --hard' to match
remote: the work tree to HEAD.
remote:
remote: You can set the 'receive.denyCurrentBranch' configuration variable
remote: to 'ignore' or 'warn' in the remote repository to allow pushing into
remote: its current branch; however, this is not recommended unless you
remote: arranged to update its work tree to match what you pushed in some
remote: other way.
remote:
remote: To squelch this message and still keep the default behaviour, set
remote: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To /Users/jarvischu/code/Programming/test_git/repo
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to '/Users/jarvischu/code/Programming/test_git/repo'

#在repo.git_local中进行相同操作,则没有问题,成功push.

Git 本地仓库和裸仓库

本机创建远程库+本地库进行clone和push测试

代码语言:txt
复制
#创建远程库
mkdir repo.git 
git init --bare repo.git 

#clone到本地库
git clone repo.git repo

#本地库进行操作
cd repo && do_git_operation_like_git_status

Git workflow 最佳的Git分支策略

A successful Git branching model

Git 在团队中的最佳实践--如何正确使用Git Flow

GIT版本管理:Git Flow模型

branch.png
branch.png
  • master: 稳定代码,处于随时可发布的状态。只能从release/hotfix分支merge。每次merge都是一个稳定的版本,需打上tag,如v1, v2。
  • develop: 开发主分支,开发的最新的状态。只能merge,不能commit.
  • feature: 需求分支,从develop拉出,开发完成后,merge回develop.
  • release: 从develop拉出,发布完成后,merge到master。如果发布过程有更改(bugfix),直接改到release分支,发布完成后,merge回develop.
  • hotfix: 修复线上紧急bug,从master拉出,修复完成后,同时merge到master和develop

bugfix: 本质上也是feature分支。 如果是release分支的bugfix,直接从release拉分支,修改完merge回release。release分支发布后,要同时合回master和develop。

需求开发完成后,feature分支可以直接删除。

release发布完成后,release分支也可以直接删除

hotfix 完成后,也可以直接删除

整个flow中,只有master和develop是常驻,其他均是用完即删。

从库中删除文件,但不要删除本地文件

常用于:误提交了 libxx.a 文件到库中,需要从库中删除,但本地又需要。

如果使用git rm则会删除本地,所以正确方法是:

代码语言:txt
复制
git rm --cached libxx.a

同时checkout出多个分支

git worktree 可以将某个分支的代码 checkout到单独的文件夹中,这样,这个分支就可以单独维护。

代码语言:txt
复制
# 将branch-name 的分支,checkout到 new_dir中
# 在new_dir 中进行branch-name分支的操作。
# 在主库的能看到该分支的情况
# P.S.  branch要先存在
git worktree add /path/to/new_dir  branch-name

如果要删除拉出的分支

代码语言:txt
复制
# 查看当前拉出的分支情况
git worktree list

# 将拉出的 要删除的分支 目录整个删掉,然后执行
git worktree prune

# 然后执行 -D 删除即可
git branch -D branch-name

Git服务器搭建

runoob - git服务器搭建 廖雪峰 - git服务器搭建

代码语言:txt
复制
# add user and group
groupadd git
useradd git -g git

# 让git用户可以正常通过ssh使用git,但无法登录shell,因为我们为git用户指定的git-shell每次一登录就自动退出。
usermod -s /usr/bin/git-shell  git

# create bare repo
$ mkdir -p repo/sample.git && cd repo
$ git init --bare sample.git

# change owner to git
chown -R git:git repo

# clone 
git clone git@ip:/path/to/repo/sample.git

二分查找法定位引入错误的commit - git bisect

阮一峰 - git bisect 命令教程

git bisect start 开始二分查找,定位到 终点 和 起点 中间的commit

如果此次commit 没有错误,则 使用git bisect good 标记,如果有错误,则使用git bisect bad 标记,

依次,直到定位到第一次引入错误的commit

git bisect reset 退出

代码语言:txt
复制
# git bisect start [终点] [起点]
git bisect start HEAD 4d83cf

git bisect good
git bisect bad

git bisect reset

分支重命名 git branch -m

https://www.jianshu.com/p/cc740394faf5

代码语言:txt
复制
# 修改本地分支名称
git branch -m oldName newName

# 如果分支已经push 到 服务器,则可以 
# (1) 先删除远程分支
# (2) 再重命名本地分支
# (3) 再Push到服务器

保存账号和密码

git config --global credential.helper store

更改最近一次提交的注释 或者 补上漏提交的文件

代码语言:txt
复制
# 修改注释,方法1
git commit -m "新的commit注释" --amend

# 修改注释,方法2,会打开一个vi编辑器,让
git commit --amend

# 补上漏提交文件的方法
git add some-missing-file
git commit --amend 

git describe --tags 生成版本号

代码语言:txt
复制
$ git describe --tags --always --dirty

# 如果当前的commit上有tag,则直接输出当前tag
v0.0.1

# 如果当前的commit不是tag,则找到距离最近的tag,以 tag+当前commit-id的形式
# v0.0.1 是距离最近的tag,
# -1 表示当前的commit 距离tag的提交个数
# -g 表示代码管理工具是 git
# f787ed9 是当前的commit-id
v0.0.1-1-gf787ed9

# 如果当前commit不是tag,而且历史记录中也没有tag,则输出当前的commit-id
# --always 的作用即在此,不加--always,没有tag会报错
f787ed9
  • --always: 如果找不到tag,直接用commit-id (缩写版)
  • --dirty: 判断当前的库是否和HEAD完全一致,即有没有没提交的改动。如果有没提交的改动,会在输出后面加上 -dirty, 如 v0.0.1-dirty如果有本地未提交的改动,git stash 一下,就可以没有dirty了。

比较某个文件两个版本之间的差异

代码语言:txt
复制
git diff commit-id1 commit-id2 -- your-file
#git diff 

合并两个库

How to import existing Git repository into another

将库XXX,合并到YYY中,并作为子目录ZZZ 存在,操作方法如下:

代码语言:txt
复制
# 在YYY中执行如下的操作

git remote add other /path/to/XXX
git fetch other
git checkout -b ZZZ other/master
mkdir ZZZ
git mv stuff ZZZ/stuff                      # repeat as necessary for each file/dir
git commit -m "Moved stuff to ZZZ"
git checkout master                
git merge ZZZ --allow-unrelated-histories   # should add ZZZ/ to master
git commit
git remote rm other
git branch -d ZZZ                           # to get rid of the extra branch before pushing
git push                                    # if you have a remote, that is

通过这种方法,也可以从一个库中,移出部分目录到新的库,并且同时保留递交记录。

命令

reset和revert区别

revert是撤销某次提交,但是这次撤销也会作为一次提交进行保存.

Revert撤销一个提交的同时会创建一个新的提交。这是一个安全的方法,因为它不会重写提交历史。

相比git reset,它不会改变现在的提交历史。因此,git revert可以用在公共分支上,git reset应该用在私有分支上。

你也可以把git revert当作撤销已经提交的更改,而git reset HEAD用来撤销没有提交的更改。

就像git checkout 一样,git revert 也有可能会重写文件。所以,Git会在你执行revert之前要求你提交或者缓存你工作目录中的更改。

reset file 和 checkout file的区别

代码回滚:git reset、git checkout和git revert区别和联系

reset: 重置缓存区; checkout: 重置工作区

代码语言:txt
复制
git reset HEAD myfile #重置缓存区中的myfile

将本地库中HEAD版本的myfile文件取出,替换到缓存区中的myfile。即将缓冲区中的myfile还原。 不会影响到工作区的myfile

代码语言:txt
复制
git checkout HEAD~2 myfile #重置工作区中的myfile

将本地库中HEAD版本的myfile文件取出,替换到工作区中的myfile,不会影响到缓存区的myfile。

git revert 详解

代码语言:txt
复制
#回滚版本改动. 会生成一个新的commit
git revert commit-id|HEAD|HEAD^ 

#不会提示输入注释,使用默认的"Revert "HEAD版本的日志""
git revert --no-edit HEAD

#或者-n,表示不用自动commit. 可以revert之后再做些改动,再手动commit
git revert --no-commit HEAD  

#如果要回滚的递交(如 HEAD)是一个merge,则需要使用-m
#因为merge是两个分支合为一个,所以git不知道哪个分支作为mainline, -m|-mainline 指定主线。
git revert -m 1 commit-id 

-m parent-number

--mainline parent-number

Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline and allows revert to reverse the change relative to the specified parent.

Reverting a merge commit declares that you will never want the tree changes brought in by the merge. As a result, later merges will only bring in tree changes introduced by commits that are not ancestors of the previously reverted merge. This may or may not be what you want.

See the revert-a-faulty-merge How-To for more details.

本地分支推送到远程仓库

代码语言:txt
复制
git push origin mybranch

637cffed68cf5babe1ce88c7bbd20b5a522f99de (HEAD -> master) add second line

418912cae2093d285a031339f7bca6833a8a6483 Readme.txt

前面的 字符串,称为 commit id

撤销提交

代码语言:txt
复制
git reset --hard HEAD^ #方式1 使用HEAD
git reset --hard commit-id #方式2 使用commit id

将缓存区和工作区都撤销到版本 HEAD^

  • HEAD: 当前版本
  • HEAD^ : 上一个版本
  • HEAD^^: 上上个版本,
  • 类推...

使用commit-id撤销时,可以撤销到任意版本。

取消撤销

记住被撤销的commit的 commit-id,直接使用方式2即可

撤销修改

代码语言:txt
复制
git checkout -- <file> #撤销工作区的修改

git reset HEAD <file> #撤销缓存区的提交,即unstage

显示git命令历史 reglog

代码语言:txt
复制
git reflog

Git管理的是修改

正是因为stage的存在,使得Git可以跟踪和管理修改,而不是文件。

示例

代码语言:txt
复制
touch Readme.txt   
git add Readme.txt
echo "Read it Carefully" >> Readme.txt
git commit -m "tonly add empty file"

新建了一个文件,然后添加到stage,之后修改该文件。然后直接commit,这时,后续的修改并不会被commit。说明git管理的是修改,不是Readme.txt这个文件。

使用Github作为远程仓库

(1) 生成SSH Key

代码语言:txt
复制
ssh-keygen -t rsa -C "iJarvisChu@Gmail.com"

回车默认即可,会在~/.ssh/目录下生成两个文件

id_rsaid_rsa.pub

生成之前,如果文件夹已存在,先 rm -r ~/.ssh email为 Github的登录账号email

(2) Github上新增SSH Key配置

Github -> Settting -> SSH and GPG Keys -> New SSH key

Title:随意起

Key: 将上一步中得id_rsa.pub内容,拷贝至此

(3) 将github加入ssh know_hosts

代码语言:txt
复制
ssh-keyscan github.com >> ~/.ssh/known_hosts

否则会有:the authenticity of host 'github.com (192.30.255.113)' can't be established.错误。

(4) 测试连接

代码语言:txt
复制
ssh -T git@github.com

至此相关Github相关环境配置已经ok。 下面我们就在Github上创建库,作为我们的远程库

  1. 在Github上创建一个库,名为ForTest
  2. 比如在本地已有一个库,也叫 ForTest.
代码语言:txt
复制
mkdir ForTest
cd ForTest/
git init
touch Readme.txt
git add Readme.txt
git commit -m "Add Readme"
  1. 新建远程仓库,并将本地仓库push到远程
代码语言:txt
复制
git remote add origin git@github.com:JarvisChu/ForTest.git
git push -u origin master

第一行:新建了一个远程库,并命名为origin.

代码语言:txt
复制
#增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

第二行:将当前库,push到远程库origin的master分支;-u 表示同时建立追踪关系,相当于 -set-upstream

忽略文件

在git目录新建文件 .gitignore 将需要忽略的文件写进去即可

注意,要把 .gitignore 也写入文件

GitHub提供的gitignore模板:https://github.com/github/gitignore

git diff

格式

代码语言:txt
复制
git diff [--cached] [ filepath ...]

--cached : 表示比较 Index 和 Repository; 否则比较 Workspace 和 Index

filepath:指定比较的文件路径;如果不指定,则比较所有

默认是将Workspace与某个版本比较,指定 --cached 则是将Index 与某个版本比较

常见用法

代码语言:txt
复制
git diff            # 比较 工作区和Index的所有文件差异
git diff --cached   # 比较 Index和HEAD的所有文件差异
git diff [--cached] file1 [file2 ...]  #比较指定文件

git diff commit-id [file...] # 比较 工作区与commit-id版本的[所有|指定]文件
                             # commit-id 可以是 HEAD,如下:
git diff HEAD [file...]      # 比较 工作区与本地库

git diff --cached commit-id [file...] #比较 Index和commit-id的版本

git diff commit-id1 commit-id2 # 比较两个commit-id的版本之间的差异

补丁

代码语言:txt
复制
#生成补丁 
git diff [--cached] [file...] > patch # 将差异保存成patch文件 [patch名称随意]

#应用补丁之前,先检查补丁
git apply --stat patch_index # 检查补丁文件本身
git apply --check patch      # 检查补丁能否应用,无输出则表示可以应用

#应用补丁 [将补丁打到]
git apply patch
git apply --reject patch #将能打的补丁先打上,有冲突的会生成.rej文件,此时可以找到这些文件进行手动打补丁

#或者使用 git am 命令来 应用补丁
git am --signoff < patch

git difftool 调用自定义的diff工具

配置difftool

代码语言:txt
复制
git config [--global] diff.tool vimdiff # 配置difftool 为vimdiff

示例

代码语言:txt
复制
brew install diff-so-fancy
git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX"

git difftool –tool-help 命令来看系统支持哪些 Git Diff 插件

使用difftool

代码语言:txt
复制
git difftool   #使用方法同git diff

git config

代码语言:txt
复制
#优先级依次递增(高优先级的配置会覆盖低优先级的相同配置项)
git config --system # 系统配置,读写 /etc/gitconfig 文件
git config --global # 用户配置,读写 ~/.gitconfig 文件
git config          # 本库配置,读写 ./.git/config 文件
代码语言:txt
复制
git config [--system|global] --list    # 显示当前所有的配置信息(如果不指定system或global,则包括三个来源)
git config [--system|global] user.name # 查看具体配置项

git config [--system|global] user.name "jarvischu"
git config [--system|global] user.email "abc@example.com"

git config [--system|global] diff.tool vimdiff # 配置difftool

git config [--system|global] core.editor vim  # 配置编辑器,默认是系统默认编辑器

添加别名

代码语言:txt
复制
$ cat .gitconfig
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
[user]
    name = Your Name
    email = your@email.com

git log

日志显示
代码语言:txt
复制
git log -p -2  #-p 显示差异,-2 显示最近的两条
git log --stat #显示每次递交的更改概要(增删改的统计)
git log --pretty=oneline  #显示log,一行一条commit
git log --oneline         #等同于--pretty=oneline
git log --oneline --graph #显示简单图形

git log --pretty=short|full|fuller
git log --pretty=format:"%h - %an, %ar : %s" # 格式化输出
日志筛选
代码语言:txt
复制
#通过时间筛选
git log --until=1.minute.ago // 一分钟之前的所有 log
git log --since=1.day.ago //一天之内的log
git log --since=1.hour.ago //一个小时之内的 log
git log --since=`.month.ago --until=2.weeks.ago //一个月之前到半个月之前的log
git log --since="2018-8-8" --until="2018-8-9"   //2018-8-8 那天的日志

#作者筛选 (包括email)
git log --author="jarvis" #author或email中包含jarvis
git log --author="jarvis\|chu" #包含jarvis 或者 chu

#提交描述信息筛选
git log --grep="周期" #commit message中包含 “周期”两个字

筛选完,可以通过

git show commit-id 来显示递交详情

git tag

代码语言:txt
复制
git tag # 显示当前的所有tag
git show v0.0.1 # s
git tag v0.0.1 -m "framework done"  # 打tag
git push --tags # 推送到远程

深入研究

15分钟成为 GIT 专家

英文原文

Git的原理简介和常用命令

官方文档

Git 原理

Git 的两层命令

1. porcelain命令 - 高层命令

基于plumbing命令实现

日常用的Git命令都是procelain命令,如git checkout,git clone,git commit,git branch 等等

2. plumbing命令 - 底层命令

git hast-object: 创建Git对象

代码语言:txt
复制
#存储 -w表示存储, --stdin表示从标准输入读取数据
key1=$(echo 'value1' | git hash-object -w --stdin)
echo $key1  #输出 ef208405fde3517d1eb47f6d886490f7cc943b78

#或者从文件生成Git对象
git hash-object -w test.txt

git cat-file: 查看Git对象信息

代码语言:txt
复制
#读取hash对象的value,value是压缩的,不可阅读,-p,pretty, 可阅读形式显示
git cat-file -p ef208405fde3517d1eb47f6d886490f7cc943b78 #输出value1

#-t 显示对象的类型

git write-tree: 由index生成tree对象

git mktree:由blob生成tree对象

git update-index: 更新index

Git目录说明
代码语言:txt
复制
$ cd .git && tree
.
├── HEAD        #文件,存放当前的分支,实际为指向refs下分支文件的路径
├── branches    #目录
├── config      #文件,项目基本配置,如remote信息 
├── description #文件, 忽略,GitWeb专用的文件,
├── index       #文件, 存放缓存区的信息和数据
├── hooks       #目录,存放钩子脚本
├── info        #目录,和.gitignore互补,存放全局的要忽略的文件模式
├── logs        #日志, 存放递交记录的日志,用于Git log命令
├── objects     #目录:存放git对象 (blob,tree,commit三种)
│   ├── info
│   └── pack
└── refs        #存放分支和tag信息
    ├── heads   #本地分支head信息(每个分支一个文件,内容为最新commit的hash值)
    ├── tags    #所有tag的信息
    └── remotes #远程库的信息
Git 本质是一个key-value 数据库

key就是文件(头+内容)的哈希值(采用sha-1的方式,40位),

value就是经过压缩后的文件内容(zip压缩)

代码语言:txt
复制
git hash-object -w test.txt

#存储位置在 .git/objects目录下
cd .git/objects && ls
--> ef  info pack
ls ef
--> 208405fde3517d1eb47f6d886490f7cc943b78

.git/objects目录下会出现一个以hash值前2位命名的文件夹,里面有以hash后38位命名的文件。

存储的hash数据,我们称之为blob

Git 也可以将多个 blob 合并成一个文件生成一个 pack 文件,pack 文件存储在./git/object/pack 目录。

pack 对象相关的信息都存储在 ./git/object/pack 目录。

Git 基于 blob 的内容为每一个 blob 生成哈希值,所以存储在 Git 中的对象是不可修改的,因为修改内容就会改变哈希值。

git add 原理
  1. 使用 git hash-object -w 保存成blob对象
  2. 使用 git update-index --addblob对象信息加入到index(即缓存区)
代码语言:txt
复制
$ echo "HelloWorld" > test.txt
$ git hash-object -w test.txt
3da1ec26e9c8512eae062868a9ff9bae47e5625b

# --cacheinfo 用来指明对象的类型,100644 表示这是普通文件
# hash值本质就是该文件的某个版本,文件名用来指明是哪个文件
# 故功能即:将该文件的该版本,添加到index文件
$ git update-index --add --cacheinfo 100644 3da1ec26e9c8512eae062868a9ff9bae47e5625b test.txt

#查看当前index文件的结构
$ git ls-files -s
100644 blob d9b401251bb36c51ca5c56c2ffc8a24a78ff20ae	readme.txt
100644 blob 3da1ec26e9c8512eae062868a9ff9bae47e5625b	test.txt

此时,若使用git status可以看到test.txt已经放在了index

git status 原理 & index

运行 git status 时,git做了两个比较:

  • 将 index 文件和当前的工作目录比较 –变化是 “not staged for commit”,即待add
  • 将 index 文件和 HEAD 提交比较 –变化是 “to be committed”,即待commit

index索引库记录从项目初始化到目前为止,项目仓库中所有文件最后一次修改时刻的时间戳、大小、hash值,因此随着加入仓库中的文件不断增多,index文件也会不断增大。每次调用git add命令,都会把add的文件的索引信息(时间戳和大小)进行更新,而我们所使用的git status命令,则会把每一个文件的索引信息和上次提交的索引信息进行比较,如果发生了变化,就会显示出来。

git init 初始化库时,index文件为空(或者说就没有生产index文件),使用git add 等命令会修改index文件。

git clone 时,index文件也会clone到本地。

切换分支后,index文件中文件的最新信息也会切换

可通过 git ls-files -s查看index中当前的文件信息

GIT科普系列5:index in git

tree 对象

tree对象仅仅是用于生成commit对象

blob对象存储的是内容和hash,并没有对应的文件名, tree对象即是为了解决这个问题。

简单理解tree对象就是目录,blob对象就是文件 一个tree对象可以包含多个tree或者blob对象

方式1 使用mktree由blob 组成tree对象

git mktree: 从标准输入读取ls-tree格式的字符串,生成tree对象

代码语言:txt
复制
#ls-tree 格式
<filemode> SP <object-type> SP <object-hash> TAB <file-name>

#由blob生成tree对象
$ printf '%s %s %s\t%s\n' \
      100644 blob 3da1ec26e9c8512eae062868a9ff9bae47e5625b test.txt \
      100644 blob d9b401251bb36c51ca5c56c2ffc8a24a78ff20ae readme.txt |
    git mktree
4272d19b3555342190e5817d7facea6ea90fd2c4

#查看tree对象
$ git cat-file -t 4272d19b3555342190e5817d7facea6ea90fd2c4
tree

$ git cat-file -p 4272d19b3555342190e5817d7facea6ea90fd2c4
100644 blob d9b401251bb36c51ca5c56c2ffc8a24a78ff20ae	readme.txt
100644 blob 3da1ec26e9c8512eae062868a9ff9bae47e5625b	test.txt

方式2 使用write-tree 直接由index生成tree对象

代码语言:txt
复制
$ git write-tree
4272d19b3555342190e5817d7facea6ea90fd2c4 #可见生成的tree对象是相同的,这种方式更方便

在Git中,每个子目录都对应一个tree对象,每个文件对应一个BLOB对象,因此整个工作目录对应一棵Git对象树,根节点就是commit对象所引用的tree节点,而每个子文件夹又分别对应一棵子树。

生成tree时,会生成文件所在目录的tree对象,然后将文件放到其目录的tree对象下。

代码语言:txt
复制
$ mkdir dir1
$ echo "dir1file" > dir1/dir1file1.txt
$ git add dir1/dir1file1.txt
$ git write-tree
f2a0d84e6ba0cf4425c58a2c2a9797f221e48e7b

$ git cat-file -p f2a0d84e6ba0cf4425c58a2c2a9797f221e48e7b
040000 tree 4fb31530615c39d012e10dace2d6acd60e5c295f	dir1
100644 blob d9b401251bb36c51ca5c56c2ffc8a24a78ff20ae	readme.txt
100644 blob 3da1ec26e9c8512eae062868a9ff9bae47e5625b	test.txt

$ git cat-file -p 4fb31530615c39d012e10dace2d6acd60e5c295f
100644 blob 805d1de9419166ca0d5f0fab9e58e4f629cd6f1c	dir1file1.txt
commit对象

在Git中,每一次commit都对应一个commit对象,而一个commit对象对应一个tree对象。

commit中其实就是对tree的一层封装,附带上了提交信息时间作者等信息

代码语言:txt
复制
#创建commit对象
#treekey是用于生成commit的tree对象的hash值
#-p 指向其父commit对象的hash值。如果是第一次commit,则不用指定。
#提交信息 从stdin 读取
git commit-tree treekey –p parentcommitkey 

#示例
$ echo "first commit" | git commit-tree f2a0d84e6ba0cf4425c58a2c2a9797f221e48e7b
9c296ec5f33719973f4c393ed07df8cb707b3a7f

$ git cat-file -p 9c296ec5f33719973f4c393ed07df8cb707b3a7f
tree f2a0d84e6ba0cf4425c58a2c2a9797f221e48e7b
author jiangtaozhu <jiangtaozhu@tencent.com> 1533579509 +0800
committer jiangtaozhu <jiangtaozhu@tencent.com> 1533579509 +0800

first commit

通过指定父commit的方式,所有的commit也就形成了一个 commit树结构。

查看commit详情:git log --stat commit-hash

Git 记录文件快照,而不是文件差异

官网中文wiki

Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。

Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • git 实践总结
    • 概念
      • 最佳实践
        • 删除分支
        • 撤销本地分支的更改
        • 撤销远程分支的更改
        • 合并多个commit
        • git rebase 处理本地分支和远程分支的分叉
        • 远程库要创建成裸仓库
        • 本机创建远程库+本地库进行clone和push测试
        • Git workflow 最佳的Git分支策略
        • 从库中删除文件,但不要删除本地文件
        • 同时checkout出多个分支
        • Git服务器搭建
        • 二分查找法定位引入错误的commit - git bisect
        • 分支重命名 git branch -m
        • 保存账号和密码
        • 更改最近一次提交的注释 或者 补上漏提交的文件
        • git describe --tags 生成版本号
        • 比较某个文件两个版本之间的差异
        • 合并两个库
      • 命令
        • reset和revert区别
        • reset file 和 checkout file的区别
        • git revert 详解
        • 本地分支推送到远程仓库
        • 撤销提交
        • 撤销修改
        • 显示git命令历史 reglog
        • Git管理的是修改
        • 使用Github作为远程仓库
        • 忽略文件
        • git diff
        • git difftool 调用自定义的diff工具
        • git config
        • git log
        • git tag
      • 深入研究
        • Git 原理
    相关产品与服务
    对象存储
    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档