title: Git 札记:基础 date: 2018-12-01 17:25:42 categories:
安装过程中选择User Git from Git Bash only,表示完全不修改PATH环境变量,仅在Git Bash中使用Git
$ git --version //查看git是否安装成功及版本号
设置签名用来区分不同开发人员的身份
$ git config --global user.name "tujidelv" //为该机器上所有的git仓库指定用户名
$ git config --global user.email "tujide.lv@foxmail.com" //为该机器上所有的git仓库指定email地址
适当地显示不同的颜色
$ git config --global color.ui true
Git存储对象(hashmap)
Git是一个内容寻址文件系统,其核心部分是一个简单的键值对数据库(key-value data store),你可以向数据库中插入任意内容,它会返回一个用于取回该值的hash键。
# git键值库中插入数据
# 写入版本1
$ echo 'version1' > README.MF; git hash-object -w README.MF;
# 写入版本2
$ echo 'version2' > README.MF; git hash-object -w README.MF;
# 写入版本3
$ echo 'version3' > README.MF; git hash-object -w README.MF;
79362d07cf264f8078b489a47132afbc73f87b9a
# 查找所有的git对像
$ find .git/objects/ -type f
# 基于键获取指定内容(git基于该功能把每个文件的版本中内容都保存在数据库中,当要进行版本回滚的时候就通过其中一个键将其取回并替换)
$ git cat-file -p 79362d07cf264f8078b489a47132afbc73f87b9a
# 回滚指定版本
$ git cat-file -p c11e96db44f7f3bc4c608aa7d7cd9ba4ab25066e > README.MF
所以我们平常用的git add其实就是把修改之后的内容插入到键值库中。当我们执行git add README.MF就包括了执行git hash-object -w README.MF把文件写到数据库中。
我们解决了存储的问题,但其只能存储内容并没有存储文件名,如果要进行回滚,怎么知道哪个内容对应哪个文件呢?接下要讲的就是树对象,它解决了文件名存储的问题。
Git树对象
树对象解决了文件名的问题,它的目的将多个文件名组织在一起,其内包含多个文件名称与其对应的Key和其它树对象的引用,可以理解成操作系统当中的文件夹,一个文件夹包含多个文件和多个其它文件夹。
每一个分支当中都关联了一个树对象,他存储了当前分支下所有的文件名及对应的 key。
#查看分支树对象或者其他树对象
$ git cat-file -p <master^{tree} | tree_id>
$ git cat-file -p a1183ea74b0cd7b5cec6b9bf8b758e03f8c5ba94
100644 blob 46ad03092238f6c2ee203ef8bb14dc88faa5203b .gitignore
100644 blob 6a96454c48ac077474c118f5ed26c02c0f05e762 README.md
100644 blob d8033580327bba5b2480e16fbbb1dc5fea61e2b4 TOC.md
100644 blob e1ec5e0c6b69f3371d730926d6d4d2b52faf44f5 _config.yml
100644 blob 36eaa9c9d2243211556e6e517e5545bbdb62677d hd.sh
100644 blob 6a8ed3965ab82e1b73bb7dcc94c575bc85f4317e hs.sh
100644 blob 911639e0e486bc709ac7813e2a52082ad9e115e9 package-lock.json
100644 blob d457fbae7bcb0ef328e13b7fc12a9aeba55597ea package.json
040000 tree a99672782f26a13f14589febab3fe407e64170ae scaffolds
040000 tree 2ff7dd52dc20edb978226c298967bf33800d26d3 source
Git提交对象
一次提交即为当前版本的一个快照,该快照就是通过提交对象保存,其存储的内容为:一个顶级树对象、上一次提交的对象啥希、提交者用户名及邮箱、提交时间戳、提交评论。
$ git cat-file -p a33ef4e
tree a1183ea74b0cd7b5cec6b9bf8b758e03f8c5ba94
parent 96bcdd27b47641809d42b2417985a44aa8d25960
author lvzhiqiang <zhiqiang.lv@riskraider.com> 1579428368 +0800
committer lvzhiqiang <zhiqiang.lv@riskraider.com> 1579428368 +0800
pushcode
通过上面的知识,我们可以推测出从修改一个文件到提交的过程总共生成了三个对象:
一个内容对象 ==> 存储了文件内容
一个树对象 ==> 存储了文件名及内容对象的key
一个提交对象 ==> 存储了树对象的key及提交评论。
Git引用
当我们执行git branch {branchName}时创建了一个分支,其本质就是在git基于指定提交创建了一个引用文件,保存在.git\refs\heads\下。
# 演示分支的创建
$ git branch dev
$ cat.git\refs\heads\dev
git总共有三种类型的引用:分支引用、远程分支引用、标签引用
保存二进制对象:$ git hash-object -w test.txt
- 把test.txt的当前内容压缩成二进制文件存入Git,压缩后的二进制文件称为一个Git对象,保存在.git/objects目录,同时会计算当前内容的SHA1哈希值(长度40的字符串)作为该对象的文件名。
- .git/objects下面的一个个子目录名是该哈希值的前2个字符,该子目录下面有一个文件,文件名是哈希值的后38个字符。
- 用cat命令查看该文件都是一些二进制字符(包括元数据),可用如下命令查看文件原始的文本内容:$ git cat-file -p e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
更新暂存区:$ git update-index --add --cacheinfo 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
- 用于在暂存区记录一个发生变动的文件
- 向暂存区(stage)写入文件名test.txt、二进制对象名(哈希值)和文件权限(100代表regular file[普通文件],644代表rw-r--r--)
Tips:Git提供了git add命令简化上面2步操作。
保存当前目录结构:$ git write-tree
- 将当前的目录结构生成一个Git对象,目录结构也是作为二进制对象保存的,也保存在.git/objects目录里面,对象名就是哈希值
将目录树对象写入版本历史:$ echo "first commit" | git commit-tree c3b8bb102afeca86037d5b5dd89ceeb0090eae9d
- 提交的时候需要有提交说明,echo "first commit"就是给出提交说明。然后git commit-tree命令将元数据和目录树,一起生成一个Git对象
- 用git cat-file -p命令能看到本次快照对应的目录树对象(tree),作者和提交人信息,以及提交说明
- 用如下命令也可以用来查看某个快照信息:$ git log --stat c9053865e9dff393fd2f7a92a18f9bd7f2caa7fa
更新HEAD的值向我们的commit对象:$ git update-ref refs/heads/master 4ad659c50196f780b19b1ab6c338205815a4d74a
如果当前不在master分支而在其他分支上,则需要指定为其他分支
Tips:Git提供了git commit命令简化上面3步操作。
常用命令
- git help <命令>
- git init [目录] //初始化版本库(把当前目录变成git可以管理的仓库[会在当前目录创建一个.git子目录,用来保存版本信息])
- git add <FILE...|DIR...> //告诉git把文件添加到仓库的暂存区,可反复多次使用,并且每次可添加多个文件
- 实际上就是把要提交的所有修改添加到暂存区(stage)
- git add --all/-A/. //对当前项目所有变动的文件,执行保存二进制对象和更新暂存区操作
- git commit [文件] -m "wrote a readme file" //告诉git把文件提交到仓库的分支(-m后面是本次提交的说明),一次可提交多个文件
- 实际上就是一次性把暂存区的所有修改提交到分支(master)
- git status //查看工作区/暂存区的状态
- git status -s //返回一个简单版的 git status。我设置这个命令为默认 git status 来减少噪音。
- git diff [FILE] //如果git status提示有文件被修改,可以用此命令查看指定文件修改了哪些内容,显示的格式是Unix通用的diff格式
- 比较的是版本库(暂存区和分支中的任意一个)与工作区同名文件相比是否相同,而git diff --cached比较的是暂存区有无内容
- git diff HEAD [FILE] //将工作区中的文件和本地库历史记录比较
- git log [branch-name] //显示从最近到最远的提交日志。其中commit(快照) id是用SHA1算出的16进制数字
- 后面加上--pretty=oneline可以精简信息
- 后面加上--graph可以看到分支合并情况
- 后面加上--abbrev-commit可以精简快照id
- 后面加上dev..master可以查看master分支还有多少修改没有同步到dev分支
- git show [branch-name] //显示最近一次提交的内容详细改变
- git reflog //查看命令历史,当找不到新版本的id时可以用此来确定要回到未来的哪个版本
- HEAD@{移动到当前版本需要多少步}
其他命令
- git checkout -- <FILE>... //撤销此文件在工作区的修改(包括删除),回到最近一次commit或add时的状态
- git checkout -- . //撤销工作区所有的修改
- git checkout -- *.java //撤销工作区所有java文件的修改
- git reset HEAD <FILE>... //撤销此文件在暂存区的修改,回退到工作区
- git reset HEAD * //撤销暂存区所有的修改
- git rm FILE //当想从版本库中删除文件时,可先在工作区删除,然后调用此命令,再commit
- git rm –cached FILE //只删除远程文件,但是不影响本地文件。这对删除已经推送过的忽略文件记录而且不影响本地文件是非常的方便的。
- git reset --hard HEAD~ //版本回退,HEAD代表当前版本,~代表回退到上个版本,如回到上100个版本可以用HEAD~100来表示
- git reset --hard 43ade3f //当想回到之前的新版本时(也能后退),可以找到它的commit id输入前几位即可(基于局部索引值操作,推荐)
- --hard //会让当前工作区和暂存区的修改也撤销
- --soft //不会让当前工作区和暂存区的修改也撤销,上次的提交由本地仓库回到暂存区
- --mix //不会让当前工作区的修改撤销,会让暂存区的修改撤销,上次的提交由本地仓库回到暂存区
- git stash //隐藏工作现场
- 当手头工作没有完成时(工作区/暂存区有变动时),先把工作现场git stash隐藏一下,这样git status查看工作区是干净的,然后去修复bug,修复后,再git stash pop,回到份工作现场
- git stash list //查看隐藏的工作现场
- git stash pop //恢复隐藏的工作现场,同时删除stash内容
- git shortlog -sn //显示提交记录的参与者列表。和GitHub的参与者列表相同。
$ git branch [-v]
>当前分支前面会标一个*号
查看所有分支(包括远程分支):$ git branch -avv$ git checkout -b name [branch-name]
>基于提交/指定分支(默认当前分支)创建新分支:$ git branch new-branch-name [branch-name | commit id]
基于远程分支(必须已存在)创建与之对应的新分支:$ git branch new-branch-name
切换分支:$ git checkout branch-name
$ git merge [-m "..."] <name>
>Fast-forward,代表此次合并是"快进模式",即把当前分支(master)指向指定分支(dev)的当前提交,所以合并速度非常快,这种模式下,删除分支后,会丢失掉分支信息
后面加上--no-ff 参数,表示禁用"Fast-forward",用普通模式合并,会创建一个新的 commit,合并后的历史能看到分支信息,能看出来曾经做过合并,而 fast forward 合并就看不出来曾经做过合并$ git branch -d <name>
>$ git branch -D 强行删除一个没有被合并过的分支(分支中只有空文件时可用普通方式-d删除分支)
当 Git 无法自动合并分支时,须手动编辑文件解决冲突,然后再添加到暂存区再提交,合并完成
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.
Rebase
创建标签
$ git tag <tagname> [branch-name] //新建一个标签,默认是打在当前分支下的最新commit上的
- $ git tag <tagname> [commitid] //打在某次指定(commit id)提交上
- $ git tag -a <tagname> -m "" [commitid] //创建带有说明的标签,-a指定标签名,-m指定说明信息
- $ git tag -a v0.1 -m "version 0.1 released" 3628164
- $ git tag -s <tagname> -m "" [commitid] //创建PGP签名的标签,不可伪造,-s用私钥签名一个标签
- 必须先安装gpg(GnuPG),如果没有找到gpg,或者没有gpg密钥对,就会报错;如果报错,请参考GnuPG帮助文档配置Key
操作标签
$ git tag //查看所有标签,按字母排序列出,而不是按时间顺序
$ git show <tagname> //查看指定标签信息
$ git tag -d <tagname> //删除一个本地标签
$ git push origin <tagname> //推送一个本地标签到远程
$ git push origin --tags //推送全部未推送过的本地标签到远程
$ git push origin :refs/tags/<tagname> //删除一个远程标签,需先删除对应的本地标签
有些时候添加一个文件到 Git,但发现添加不了,原因是这个文件被 .gitignore 忽略了,有如下2种处理方法
$ git add -f FILE //用-f强制添加到Git
$ git check-ignore -v FILE //检查.gitignore文件的哪个配置影响了该文件,好修改规则
Windows 下创建 .gitignore 文件的常用方法
通过命令配置
加上--global 是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch
$ git config --global alias.unstage 'reset HEAD'
$ git config --global alias.last 'log -1' //显示最近一次的提交
$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
通过文件配置
每个仓库的 Git 配置文件都放在 .git/config 文件中,而当前用户的 Git 配置文件放在用户主目录下的一个隐藏文件 .gitconfig 中,别名就在[alias]后面,要删除别名,直接把对应的行删掉即可
[alias]
co = checkout
ci = commit
br = branch
st = status
unstage = reset HEAD
last = log -1
lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
[user]
name = tujidelv
email = tujide.lv@foxmail.com
windows 版 git bash 设置其他命令别名