git 学习笔记

内容来源:

  • 书名: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这种东西,规定每个分支的功能