❝本文主要记录学习 Git 的过程。由于是学习笔记,可能有描述不全面或是不许确的地方,欢迎在评论区指正。前端
❞
本文的思惟导图以下:git
安装应该不用多说了,主要讲一下升级。Linux 和 Mac 都有包管理器,升级是很方便的,关键是 windows 并无这类东西,那么怎么升级呢?github
这里首先要经过 git version
查看你当前的 Git 版本是多少,若是版本:web
<= 2.14.1
:不要多想,老老实实卸载旧版本,安装新版本吧。。。
2.14.2 ~ 2.16.1
:直接
git update
升级
>= 2.16.1(2)
:直接
git update-git-for-windows
升级
经过命令升级的时候可能半天没反应,最后提示你 using proxy as per lookup
,如图所示:json
那么不要犹豫了,你须要科学工具,不然速度绝对让你崩溃。若是没有的话,那也只能卸载再安装了,不过官网下载的速度也是至关之慢......windows
如下操做基于 win10。bash
这个问题有点奇怪,直接使用 Git Bash
是不会出现乱码的,可是使用 windows terminal
后,在 git log
的时候中文会显示为八进制。你的问题可能和我不同,也许是 git commit
或者 git status
的时候乱码。总之,咱们能够统一设置:app
git config --global gui.encoding utf-8
git config --global core.quotepath false git config --global i18n.commitencoding utf-8 git config --global i18n.logoutputencoding utf-8 复制代码
最后执行:ssh
export LESSCHARSET=utf-8
复制代码
而后新建一个环境变量,键名 LESSCHARSET
,键值 utf-8
。(这一步很关键,不然你新开窗口仍是会乱码的)编辑器
注意:具体的设置可能由于系统(我是 win10)或默认编码不一样而不一样。我在解决这个问题的时候查看了不少博客,有的地方会说 git config --global i18n.logoutputencoding utf-8
这一步应该是设置为 gbk
,不过我这样设置以后连 Git bash
也乱码了......(尽管 win10 的默认编码应该是 gbk
没错),因此在设置上仍是得看你本身系统的状况,可能得多试几回。
❝2020-05-27 更
❞
这个 windows terminal
果真不让人省心,今天发现 ls
指令也会有乱码的状况,并且目录不会高亮。最后在这个 issue 下找到了答案,两种解决方法:
① 编辑 git/etc/bash/bashrc
文件,末尾添加:
export LANG="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8" 复制代码
② 编辑 windows terminal
的 setting.json
文件:
"commandline": "D:\\Git\\bin\\bash.exe"
// 改成 "commandline": "D:\\Git\\bin\\bash.exe -li" 复制代码
-li
参数可让 git bash
以正确的配置启动。估计前面全部问题都和这个有关,由于个人 git
没有安装在默认路径,可能致使某些配置读取不了,出现各类奇葩的问题。
显示配置:
git config -l/--list git config --global -l/--list 复制代码
进行配置:
git config --global username 'xxx'
git config --global useremail 'xxx' 复制代码
这里的参数能够是 local
(对本仓库生效),global
(对登陆用户的全部仓库生效) 或 system
(对全部用户的全部仓库生效),在优先级上依次递减。
生成项目文件夹(本地仓库),同时生成记录重要信息的隐藏文件夹 .git
:
git init project
复制代码
查看项目文件状态,红色表示文件还在工做区,绿色表示文件还在暂存区,不显示表示都到了历史区:
git status
复制代码
撤销本地文件的修改,使之回退到最近一次 git add
的状态:
git checkout -- <file>
复制代码
❝上面这个命令和切换分支很是像(多了
❞--
),容易混淆。为此,git 2.23
以后引入了专门的回退命令和切换分支命令,用来分离git checkout
的职责。
咱们能够用下面命令进行工做区文件的回退:
git restore <file>
复制代码
有时候咱们在工做区作一些修改的时候,可能临时接手了其它更紧急的任务,那么这时候就得把当前所作的更改作一个状态保存:
git stash
复制代码
在任务完成后,想要恢复以前保存的状态,则能够:
git stash apply // 或者 git stash pop 复制代码
git add <file> git add . git add -A 复制代码
git rm --cached <file> git rm --cached -r . git rm <file> // 删除工做区和暂存区的文件 复制代码
git mv <file1> <file2> git commit -m'Change the file name' 复制代码
当咱们修改文件并进行了 git add
以后,就不能再经过 git restore <file>
直接撤销本地文件的修改了。但能够先经过 git reset HEAD <file>
撤销对暂存区的文件提交(某个文件不提交了),再 git restore <file>
。
git reset HEAD <file>
复制代码
一样的,咱们能够用新命令代替:
git restore --staged <file>
复制代码
这两个命令借助于 HEAD
进行恢复,所以运行在“至少提交了一次”的前提下(若没有提交则不存在 HEAD
,使用命令是会报错的)。不过忘了也不要紧,Git 会根据操做给咱们相关提示的。
当咱们不加 <file>
参数的时候,会撤销暂存区全部的文件提交(全部文件都不提交了),其实至关于恢复到了最近一次 commit
的状态:
git diff HEAD
复制代码
在急着将工做区的文件提交到暂存区以前,能够先比对两个区的差别:
git diff
复制代码
若是目标是具体文件,也能够加上参数:
git diff --<file>
复制代码
一样,在急着将暂存区的文件提交到历史区以前,可能须要先将暂存区与最近一次 commit
进行比对,看看修改了什么东西(--cached
表示暂存区):
git diff --cached
复制代码
git commit -m'xxxxx' git commit 复制代码
commit
信息git log git log --oneline // 查看简略的提交信息 git log --graph // 查看图示提交信息 git log --n4 // 查看最近四次的提交信息 git log --all // 查看全部分支上的提交信息 git reflog // 查看包含回退在内的提交信息 复制代码
commit
信息git commit --amend
复制代码
commit
信息若是要修改之前的 commit
信息,就须要变基(rebase
)了。找到要修改的 commit
的前一次 commit id
:
git rebase -i <commit id>
复制代码
咱们要修改的是 3291b88
这个 commit
的信息,将前面的 pick
指令改成 reword
指令,并保存:
来到另外一个界面,在这里进行修改,以后保存退出便可:
commit
:假设想要合并中间的多个连续 commit
(2.txt 到 6.txt):
基于这几个 commit
的前一次 commit id
进行变基:
git rebase -i ec7eeb3
复制代码
在这里会列出第一个 commit
日后的全部 commit
,将想要合并的 commit
前面的指令改成 squash
指令,并保存:
编辑 commit
信息,这个信息会成为合并后的总 commit
信息,保存退出:
能够看到,commit
合并成功了:
commit
:针对上面合并以后的结果,若是咱们想要合并第一个和最后一个 commit
,那么能够基于第一个 commit
进行变基:
git rebase -i ec7eeb3
复制代码
在 rebase
的交互式界面中,只会显示第一个 commit
日后的 commit
,可是咱们这里须要用到第一个 commit
,因此手动写入一个 pick ec7eeb3
,并把第三个 commit
调整放到它后面,squash 有压入、塞入的意思,这里能够理解为把第三个 commit
塞入第一个中:
保存后会弹出一些提示,经过 git rebase --continue
再次回到交互式界面中。后面的步骤就和以前同样了:
再来打印看看,发现合并成功了:
git reset --hard HEAD^ / HEAD~n git reset --hard <commit id> 复制代码
git diff <commit id1> <commit id2>
复制代码
在介绍具体的指令以前,首先要搞清楚 HEAD
,master
和 dev
三个指针的做用(不考虑版本回退的状况):
master
分支(主分支),
master
指针始终指向主分支的最近一次
commit
,并在每次出现新的
commit
时向前推动;
dev
指针,它始终指向子分支的最近一次
commit
,并在每次出现新的
commit
时向前推动;
HEAD
指针都指向该分支的最近一次
commit
假设最初只有一个主分支,第一次 commit
新建文本文件,第二次 commit
修改文件内容。下面结合例子和示意图介绍指令。
git branch -av
复制代码
此时只有一个主分支,HEAD
和 master
指针都指向最近一次 commit
:
PS:这里必定要注意,判断当前处于哪一个分支看右边括号的内容便可,HEAD -> master
表示的并非 HEAD
指针指向主分支,而是像示意图那样。
参数 -a
表明查看本地和远程全部分支,-v
表明查看分支的同时显示最后一次 commit
的相关信息。若是只想查看本地分支,能够:
git branch -v
复制代码
commit
建立并切换分支git branch new_branch git checkout new_branch /* 或者 */ git checkout -b new_branch 复制代码
此时有了子分支,因为它是基于主分支的最近一次 commit
建立的,因此三个指针指向同一个东西,即 HEAD -> new_branch,master
commit
建立并切换分支git branch new_branch <commit id> git checkout new_branch /* 或者 */ git checkout -b new_branch <commit id> 复制代码
若是咱们是基于第一次 commit
建立分支的,则指针的变更以下:
git checkout -b new_branch old_branch
复制代码
git switch new_branch
复制代码
在 git 2.23
以后有了专门的切换分支命令。
git merge new_branch
复制代码
直接删除指定分支:
git branch -D new_branch
复制代码
删除前会先让用户肯定分支是否已经合并:
git branch -d new_branch
复制代码
假设当前提交状况以下:
要比较两个分支的差别,天然而然想到:
git diff master new_branch
复制代码
不过,前面咱们知道,master
指针指向主分支最近一次 commit
,dev
指向子分支最近一次 commit
,正是这两个指针不一样才得以将分支区别开来,而指针又是指向 commit
,所以其实能够经过比对两个分支最近一次 commit
的差别,进而比对两个分支的差别:
能够看到,虽然两次 diff
的参数不一样,可是结果是同样,这是由于「比对分支的差别,在本质上就是比对 commit
的差别」。
commit
切换到“分离头指针”状态git checkout <commit id>
复制代码
此时,咱们会到达某次 commit
以后的状态,咱们能够今后次 commit
的状态出发,进行一些试探性的修改。因为处在 'detached HEAD' state
(“分离头指针”状态),因此修改不是基于任何具体分支的,一旦切换到某个具体分支,全部的修改都会消失。这适用于实验性的修改,能够避免影响其它任何分支。
其实,Git 给出的文字提示也很清楚。咱们还能够注意括号里的内容,一开始是 master
分支,后来直接变成了某一次 的 commit id
。同时能够看到,HEAD
再也不像此前同样指向某个指针,而是「分离了」:
对应的示意图以下:
固然,咱们能够执行下面指令:
git checkout -b new_branch
复制代码
此时就会真正建立一个分支,咱们的修改得以保留。实际上就和以前的 git checkout -b new_branch <commit id>
效果同样。
① 每一次提交会造成一个 commit
对象,根据 id 查看该 commit
对象的类型和内容:
git cat-file -t 'xxxxxx' git cat-file -p 'xxxxxx' 复制代码
② 该 commit
对象的 tree
对象是一棵文件结构树,它记录了提交时的文件结构快照(有哪些文件夹、哪些文件),根据 id 查看该 tree
对象的类型和内容:
git cat-file -t 'xxxxxx' // tree git cat-file -p 'xxxxxx' 复制代码
③tree
对象包含了 tree
对象和 blob
对象,分别指代文件夹和文件,咱们能够进一步查看其类型和内容:
git cat-file -t 'xxxxxx' // tree or blob git cat-file -p 'xxxxxx' 复制代码
「注意最外层、最初始的 tree
是文件结构树,此后的 tree
是文件夹树」。比方拿下面这张图来看,第一棵树是文件结构树,内容是各个文件夹和文件,文件直接指代 blob
对象,而文件夹则指代另外一棵树/ tree
对象。
查看本地公私钥配置状况:
ls -al ~/.ssh
复制代码
生成公私钥:
ssh-keygen -t rsa -b 4096 -C 'your_email@example.com'
复制代码
以后本地保存私钥,并在 GitHub 我的设置里配置公钥。
第一步,先到 GitHub 建立一个远程仓库,拿到仓库对应的 ssh
或者 http
地址
第二步,建立本地仓库:
git init // 初始化当前文件夹为本地仓库 git remote add origin <Address> // 本地仓库与远程仓库创建关联 git remote -v // 查看与本地仓库关联的远程仓库 复制代码
最后,本地提交一些修改,并推送到远程:
git add -A git commit -m'xxxx' git push origin master 复制代码
PS:注意,若是以前创建远程仓库的时候勾选新建 README.md
文件,而且后来采用的不是 git clone
的方法,那么在这里是不能直接推送到远程的,由于本地与远程内容不同(本地没有 README.md
文件),直接推送的话会报错。必须得先 git pull
把远程的东西拉下来同步,以后再 git push
。
在前面的第二步,能够直接 git clone
,这样就不须要手动初始化本地仓库、创建远程链接以及同步等:
git clone <Address>
复制代码
不过,这只会克隆远程仓库的 master 分支。「之后」若是咱们想要克隆远程的其它分支,能够:
git branch remote_branch_name origin/remote_branch_name
复制代码
或者是直接切换到其它分支,Git 默认会帮咱们创建一个追踪(track)远程仓库对应分支的分支:
git switch remote_branch_name
复制代码
若是一开始就只想要克隆某个分支,能够:
git clone -b remote_branch_name <Address>
复制代码
还有一种状况是,咱们直接在本地创建了一个分支,忘记将它和对应的远程分支创建关联,这时候怎么办呢?能够这样:
git branch --set-upstream-to=origin/remote_branch remote_branch
复制代码
假设老大(项目负责人)新建远程仓库 X,并添加 AB 两人做为仓库的 contributor,那么 AB 就能够各自 clone 造成一个本地仓库,在作一些修改以后直接 push 到 X 仓库。
A 和 B 在同一个分支 remote_branch
上进行开发,A 修改 a 文件,B 修改 b 文件。在 A push
以后,B 是不能直接 push
的,由于本地仓库与远程仓库对应分支的内容还没进行同步。必须先执行下面的操做进行同步:
git pull origin/remote_branch
复制代码
这个指令其实作了两件事,第一件事是把远程已被 A 更新的 remote_branch
分支拉到本地:
git fetch origin/remote_branch
复制代码
第二件事是将这个目前最新的分支与本地 B 本身的分支进行合并:
git merge remote_branch origin/remote_branch
复制代码
最后再进行 push
就不会再报错了。
还有一种状况是修改同一文件的不一样区域,由于修改的地方不同,Git 仍是能够帮咱们进行 merge
的。
由于上面的修改都是针对不一样区域的修改,因此不存在冲突,Git 能够帮咱们进行 merge
。可是当修改同一文件的同一地方时,状况就不同了:好比说 A 修改了 a 文件的某个地方,以后 push;B 同时也修改了 a 文件的这个地方,进行 push
,以后报错,因此 B 选择先 fetch --> merge
再 push
,可是在 merge
的时候还会报错(Auto merged failed)。
这是由于 A 和 B 都修改了同一个地方,Git 并不知道以谁的修改成准,所以 Git 没法帮咱们作合并的工做。此时,就须要 B 手动去处理冲突,以后再 add --> commit --> push
才行。
假设如今的场景是:A 修改 a 文件的文件名为 a1,以后 push
;B 修改 a 文件的文件名为 a2,也想要 push
,由于本地和远程不一样步,根据以前解释的,到这里天然会报错,因此 B 决定先 pull
再 push
,但实际上,在 pull
这一步的时候会再报一个错,提示没法进行 merge
。
这和 6.2 实际上是同样的状况,A 和 B 都想要修改同一个东西,Git 不知道应该以谁为准。假设 AB 通过协商,最终决定修改成 a2,那么对于 B 来讲,他能够把本地的 a
和 a1
删除,再对 a2
进行 add --> commit --> push
的操做。(为何会有 a1
,是由于 pull
操做的 fetch
这一步是没问题的,远程拉下来以后本地会多出此前 A 更新的 a1
文件)
在前面,咱们是单人开发或者团队开发,可能只涉及到本地仓库和远程仓库,即 local 和 origin。假设 A 发现了某个第三方开源项目 ThirdParty/project
存在一些 bug,他想要进行修复,那么他就须要 fork 别人的仓库,造成本身的远程仓库 A/project
,再克隆这个仓库造成本地仓库。那么,对于本地仓库来讲,A/project
就是 origin,而 ThirdParty/project
就是 upstream。
这个就是以前讲的情形,origin 是 A 本身的远程仓库,A 具备自由读写的权限,能够直接 pull
和 push
。
若是本地仓库想要与原始仓库同步,第一步是先设置上游:
git remote add upstream <remoteAddress>
复制代码
查看当前配置的远程仓库(将包括 origin 和 upstream):
git remote -v
复制代码
以后就能够与原始仓库进行同步了:
git pull upstream master
复制代码
不过这里要注意,A 不是项目参与者,对原始仓库是没有写入权限的,所以没法直接进行 git push
操做。那么 A 怎么作呢?他能够先在本地把 bug 修复好,以后先 push 到 orgin 上去,再对原始仓库发起 pull request,等待项目负责人进行 merge。
设置上游以后,本地能够与原始仓库保持同步,那么 origin 怎么与原始仓库同步呢?咱们须要反向 pull request。这里能够以「掘金翻译计划」这个项目为例。在咱们本身的仓库点击 pull request 来到这个界面:
注意这里的 head 和 base,一开始是 origin 指向 upstream,表示我这边更新了东西,想要原始仓库去合并;不过如今的状况是反过来的,咱们能够看做是原始仓库更新了东西,想要咱们去合并。因此将 head 和 base 改成从 upstream 指向 origin:
能够看到,由于有很长一段时间没有同步更新了,因此多出了整整 800 多个 commit。到了这个界面,就能够正式建立 pr,而后咱们本身来 merge 了。这样,origin 和 upstream 就保持了同步。
参考:
最后是我的的公众号,最近在学习操做系统和编译原理,关于这方面的笔记会比较多。之后的话则基本和前端相关。我还在持续学习的路上,文章会尽可能以原创和翻译为主,若是感兴趣的话能够关注一下~
本文使用 mdnice 排版