内容来源:
- 书名:Git从入门到精通
- 作者:高见龙
- 出版社:北京大学出版社
前言
设置用户信息
git config --global user.name "Auceptin"
git config --global user.email "[email protected]"
git config --list可以查看当前设置
如果想为某个项目设定单独的作者,把global参数换成local
设置别名(缩写)
git config --global alias.co checkout这样就可以让co等同于checkout
也可以直接去/.gitconfig文件里改
主要功能
初始化
git init在项目目录里执行,初始化git,生成一个隐藏的./git文件夹,里面有git生效的全部信息
获取状态
git status拿手头的项目试一下:
D:\steam\steamapps\common\Noita\mods\Noita_Kirous> git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: files/enemy/animal_maggot_tiny_mode/endless/level_endless/death.lua
no changes added to commit (use "git add" and/or "git commit -a")
当前所在分支,版本状态和未提交的修改内容
提交
一切修改都在工作目录里完成,而完成提交需要先转移到暂存区,再转移到存储库中。
注意:Git在计算、产生对象时,是根据“文件的内容”进行计算的,所以只是新增一个目录的话,Git是无法处理它的。
将修改的文件加入暂存区:
git add filename.md,支持通配符git add *.md,或者git add .把当前目录下的所有加进暂存区,或者git add --all,把整个项目文件都加进去
提交到存储库:
git commit -m "改动描述"改动描述默认是必须的,否则需要--allow-empty参数
合并add和commit可以git commit -a -m "update content",增加一个-a参数,但是只适用于存储库中已有文件的修改,对新加入的文件无效
查看历史
git会保留一个项目的历史信息,从github上pull下来后也默认有这些信息
git log查看每一次提交的信息,这一环节是推荐用图形化界面看的,因为清晰。大部分IDE都有集成git图形化的功能
筛选某个作者的提交git log --oneline --author="Sherly"可以使用|或符号,但是需要加上转义,像这样git log --oneline --author="Sherly\|Eddie"
筛选提交描述词git log --oneline --grep="LOL"
按时间筛选git log --oneline --since="9am" --until="12am" --after="2017-01"
查看单独一个文件git log welcome.html
删除文件
方案一是删除文件后把该文件提交到暂存区,两个步骤,也可以用git rm filename
以上工作目录里对应文件也会一并删掉,如果只想从暂存区里删掉,不动工作目录git rm welcome --cached用–cached参数
改文件名
直观的方式仍然是在工作目录直接改名再提交,本质是删一个文件再加一个文件,也可以用git mv hello.html world.html把hello.html改成world.html
文件的名称不重要,Git是根据文件的“内容”来计算SHA-1的值,所以文件的名称不重要,重要的是文件的内容。当更改文件名时,Git并不会为此做出一个新的Blob对象,而只是指向原来的那个Blob对象。但因为文件名变了,所以Git会为此做出一个新的Tree对象
修改commit描述内容
git commit --amend -m "Something_overwrite"
追加一点修改到上一次commit
如果不想为了一点微小的改动增加一次commit,比如修改了一下a.c文件,就可以先把它加到暂存区,再git commit --amend --no-edit
gitignore
某些文件不想提交上去,用此文件标记:
# 忽略secret.yml文件
secret.yml
# 忽略config目录下的database.yml文件
config/database.yml
# 忽略db目录下所有后缀是 .sqlite3的文件
/db/*.sqlite3
# 忽略所有后缀是 .tmp的文件
*.tmp
# 如果想要忽略.gitignore这个文件也可以,只是通常不会这么做
# .gitignore
git add -f 文件名称中,f应该是force,要高过gitignore文件的效果
对创建前已经存在于暂存区的文件无效,需要用git rm --cached清出去
出bug后的清算时间
git blame filename可以看到每行代码最后修改于谁、哪次提交
挽回
git checkout cinderella.html将指定文件恢复回来,同样可以使用通配符:git checkout . 一定要提防AI用这个!喜欢自己checkout的模型不是好模型
git reset XXX回退到XXX次提交,这个细节比较多
首先需要理解HEAD,它像一个指向分支的指针,一个提交历史可能像这样:
A->B->CHEAD默认就指向C,表示接下来的修改接续在C后面
依据切换时是否舍弃工作目录、暂存区,reset分为三种模式,git reset --soft保留工作目录和暂存区的修改git reset --mixed,舍弃暂存区,保留工作目录(默认)和git reset --hard,丢弃所有未提交的改动
git reset HEAD^中^表示前一次,这条指令的含义就是reset到前一次提交,这是相对定位,或者git reset 85e7e30
commit不会因为reset消失,但会无法通过log查看,可以通过reflog获取一些信息git reflog展示HEAD移动信息
SHA-1
SHA-1(Secure Hash Algorithm 1)是一种哈希算法,计算之后的结果通常会以40个十六进制的数字形式呈现。该算法的特点之一,就是只要输入一样的值,就会有一样的输出值;反之,如果输入不同的值,就会有不同的输出值。Git中所有对象的编号都是靠这种算法产生的。
在Git中,不同种类对象的SHA-1值的计算方法会稍微有些不同。例如,Blob对象的SHA-1组成模式如下:
(1)“blob”字样。
(2)一个空白字节。
(3)输入内容的长度。
(4)Null结束符号。
(5)输入内容。
一个重要信息是与时间、设备等都无关,只与文件内容有关
运行原理
四种对象
在Git中,有4种很重要的对象,分别是Blob对象、Tree对象、Commit对象以及Tag对象。
Blob(Binary large object),对一个文件进行SHA-1后就可以存到git目录,得到的40个字符中,前两个作为目录,后38个作为文件名,文件内容是压缩过的,可以用git cat-file -p 30ab28d3acb37f96ad61ad8be82c8da46d0a7307查看,把-p换成-t可用来查询对象类型
重复:git只对“内容”有兴趣,察觉不到空目录
Tree,目录及文件的名称将以Tree对象的形式存放,Blob只存放文件内容。会形成一个有向无环图
Commit,记录了根目录tree对象,提交信息
总结:
(1)把文件加入Git之后,文件的内容会被转成Blob对象存储。
(2)目录及文件名会存放在Tree对象内,Tree对象会指向Blob对象或者其他的Tree对象。
(3)Commit对象会指向某个Tree对象。除了第一个Commit,其他的Commit都会指向前一次的Commit对象。
(4)Tag对象(Annotated Tag)会指向某个Commit对象。
(5)分支虽然不属于这4种对象之一,但它会指向某个Commit对象。
(6)往Git服务器上推送之后,在.git/refs下就会多出一个remote目录,里面放的是远端的分支,基本上与本地的分支类似,同样也会指向某个Commit对象。
(7)HEAD也不属于这4种对象之一,它会指向某个分支。
在checkout时,git会根据当前commit对象,把所有对象抽出来
其它细节
git不做差异备份,会浪费空间,有垃圾回收机制但空间不是git考虑的主要问题
分支
git branch查看所有分支
git branch newBranch新增一个分支(不切换)
给分支改名git branch -m newBranch Auceptin用-m参数
删除分支git branch -d Auceptin用-d参数,如果没合并会提示,换成-D强制执行。当前所在分支不能删,此外均可
切换分支git checkout master如果分支不存在,加上-b参数默认新建分支
分支从名字容易理解为项目的分叉,从实际实现上应当理解为一个贴纸,贴在commit上,被HEAD指向并移动。而其本质是40个字节(某个Commit的SHA-1值),所以新建分支开销极小,删掉分支也不会影响commit
合并分支git merge Branch1,效果是把Branch1合并到当前所在分支。如果有两个从master分出的分支a和b要合并,他们不存在谁是谁的子集,git就会新建一个commit再整合到一起(如果没有冲突)。有子集关系直接换贴纸就好了,或者加上--no-ff参数也会新增一个commit
git reset HEAD^ --hard撤回
rebase合并git rebase Branch,把分叉以后得提交嫁接到Branch分支上,但是原本的分支并没有消失,只是不可达了
rebase的撤回不同,可以用reflog找回不可达的commit
回溯时间,回到以前的commit重新分叉,可以checkout后再commit,也可以git branch bird 657fce7,含义是在657fce7这个commit上新增一条名为bird的分支
冲突
大致git呈现出的是这样的:
<<<<<<< HEAD
<div> 我是Cat</div>
=======
<div> 我是Dog</div>
>>>>>>> dog
先指明HEAD当前分支,===分割线,后面是另一个分支
把不想要的内容和标记删掉即可,然后重新加到暂存区并commit,如果是rebase就是git rebase --continue
如果是非文本文件,比如图片文件冲突,git checkout --ours a.jpg或者git checkout --theirs a.jpg
修改历史
–amend参数可以修改最后一次commit,如果想回溯更久的历史,需要用git rebase
git rebase -i bb0c9c2其中-i参数表示进入互动模式,范围是从bb0c9c2到当前提交。在互动模式的记录中,由上而下是从最旧到最新,与git log指令所呈现的结果是相反的。修改内容限于commit的描述信息,SHA-1值会全部改变
git reset ORIG_HEAD --hard撤回
用图形化的界面要方便很多
合并多次提交:进入交互模式后,把pick改成squash就可以和上一个commit合并,commit信息重写
拆分,同上,把pick改成edit,git reset HEAD^然后重新加进暂存区、提交
revert reset 和 rebase
revert是新增一个commit,内容是还原到上一个commit,一种过于礼貌的修改,但是适用于已经push之后的修改
reset把当前状态设置为指定commit
rebase如上,功能丰富
标签
标签和branch一样也是指向commit的一个指向标,一般用于记录版本号等,做里程碑用
分为轻量标签和有附注的标签,添加轻量标签git tag 1.0.0 51d54ff把1.0.0标签贴到51d54ff这个commit上,默认加到当前commit。增加-a参数变为有附注的标签-m "something"标签增加内容,类似于commit
有附注的标签主要用作软件版本号等,而轻量标签则是用于个人使用或暂时标记。前者会记录谁在什么时候贴了这张标签,后者不会
git tag -d tag_name用-d(delete)参数清理
标签与分支的区别是,分支会随着Commit而移动,但标签不会。
其他功能
手边的工作做到一半,临时要切换到别的任务
先提交已经完成的修改,切换分支,修改后$ git reset HEAD^回来
也可以用stash功能,暂存当前修改git stash然后用git stash list查看已经存的,用git stash pop stash@{2}这样一条指令恢复,默认0号stash,pop之后就没了
补充:这里建议使用更现代的git switch
提交了私密信息
掀桌子,删git目录
Rebase到提交前再逐个commit编辑,或者 git filter-branch批量编辑,比如git filter-branch --tree-filter "rm -f config/database.yml"
撤回git reset refs/original/refs/heads/master --hard
如果已经push上去了就只能 filter-branch后git push-f
资源回收
文件进了git想走就困难了,首先要让对应commit不可达,即没有标签打在顶上或其根其它commit,然后还要让reflog也忘掉,默认三十天,或者git reflog expire --all --expire=now
git fsck --unreachable列出真正不可达的commit,git gc喊来垃圾车,但是依然不会立刻运走,而是等到过期git gc --prune=now才可以
Github
设置远端节点
git remote add origin [email protected]:kaochenlong/practice-git.git其中origin是个代名词,换成随便别的什么都可以
推送git push -u origin master,完成的工作是:
(1)把master分支的内容推向origin位置。
(2)在origin远端服务器上,如果master不存在,就创建一个名为master的分支。
(3)如果服务器上存在master分支,就会移动服务器上master分支的位置,使它指到当前最新的进度上。
-u参数,表示upstream上游,。在Git中,每个分支可以设置一个上游(最多也只有一个)下次执行git push指令时,就会用它来当默认值,可以使用git push没有origin main了。
git fetch只拉不合并
git pull拉下来自动合并 = git fetch + git merge
冲突也是常有的,先拉再推是好习惯,git push -f强制推上去的话……至少代码的冲突解决了
git clone addressClone指令通常只在第一次下载时使用,而之后的更新就只能使用Pull/Fetch指令了
PullRequest(PR)想参与一个项目但没有权限也要不到权限,可以:
(1)先复制(Fork)一份原作者的项目到自己的GitHub账号下。
(2)因为复制的项目已经在自己的GitHub账号下,所以就有了完整的权限,可以随意更改。
(3)改完后,将自己账号下的项目推送(Push)上去。
(4)发个通知,让原作者知道你帮忙做了一些事情,请他看一下。
(5)原作者看完后如果觉得可以,就会把你做的这些修改合并(Merge)到他的项目中。
其中,步骤(4)中的那个“通知”,就是发送一个请原作者拉回去(Pull)的请求(Request),称为PR(Pull Request)。
此外,更礼貌的做法是修改前发issue和作者做好沟通
但是遇到进度落后会比较麻烦,重新fork有效好用但是比较笨,正确做法是把原作者的项目设置成上游项目,Fetch回来后再手动合并。
git push origin master:cat把本地的master分支推上去之后,在服务器上创建cat分支,去掉master,git push origin :cat用空白区覆盖cat分支,实现了删除效果
在大型项目多人合作,分支如果不加以约定会变得十分混乱,于是有了git flow这种东西,规定每个分支的功能