Git 基础学习系列
git init
创建 Git 本地仓库
远端无仓库,本地无仓库,本地新建一个仓库
1 | git init git_learning |
远端有仓库,本地无仓库,拉取远端仓库到本地
1 | git clone git@github.com:michaelxoxo/michael-git.git |
远端有空仓库,本地已有项目文件,关联远端仓库
查看我们当前项目有哪些远程仓库可以执行如下命令:1
git remote -v
如果发现没有关联远端仓库,可以这么做:1
2
3
4
5cd micahel-git
git init # 如果本地已经是一个 Git 仓库,这行就跳过
git remote add origin git@github.com:michaelxoxo/michael-git.git # 添加一个远端主机,并命名为 origin
git push -u origin --all # --all 表示 push all branches,-u 选项指定了一个默认主机
git push -u origin --tags # --tags All refs under refs/tags are pushed
将本地的master
分支推送到origin
主机,同时指定origin
为默认主机,后面就可以不要再指定远端主机名 origin
了,直接使用git push
。
远端主机名可以定义为其他,比如 github
。通过 git remote add
命令,一个仓库其实可以与多个远端仓库发生关联的,这时候只要远端主机名取不一样的即可区别。为什么要给远程仓库取名字?因为我们可能一个项目有多个远程仓库,比如,Github一个,比如公司一个,这样的话,提交的时候可以提交到不同的远程仓库就需要指定不同的仓库名字了。
参考:
- git push 的 -u 参数具体适合含义?
- Git远程操作详解
git clone
下载一个远程仓库:1
git clone [-b br_name] <git@github.com:michaelxoxo/michael-git.git> [本地仓库名]
克隆的时候,可以指定下载远端的分支、自定义本地仓库的名字。如果不加分支名参数,git clone
命令会默认自动设置本地 master
分支跟踪克隆的远程仓库的 master
分支(其实是仓库的默认分支,大部分仓库默认分支是 master)。同时,默认远端主机设置别名为 origin
。
git mv
文件重命名:1
git mv <old filename> <new filename>
git branch
git branch -r
只显示远端分支,git branch -a
显示本地分支和远程分支
新建分支
新建 develop 分支,并切换到 develop 分支:1
2
3
4git branch develop
git checkout develop
# 新建并切换分支
git checkout -b develop
本地分支推送到远端仓库
本地分支推送到远程服务器时,远程分支自动创建,推送本地分支到远程:1
git push --set-upstream <remote_host_name> <local_branch_name>:<remote_branch_name>
<remote_host_name>
:远程 Git 服务器名称,一般为origin
<local_branch_name>
:本地分支名称<remote_branch_name>
:远程分支名称--set-upstream
参数用来关联本地分支和远程分支
一般情况下,本地分支和远程分支名称相同,所以可简化为:
1 | git push --set-upstream <remote_host_name> <branch_name> |
参考:
- Git创建远程分支
- 阮一峰–Git远程操作详解
查看本地分支
1 | git branch # 查看本地分支 |
删除分支
删除本地分支:1
2git branch -d develop
git branch -D develop # 强制删除
删除远程分支:1
2
3git push origin :<remote_branch_name>
# 和如下命令等同
git push origin --delete <remote_branch_name>
如果待删除的分支中的提交已经存在于其他分支,那么,需要检出到那个分支,然后才可以安全的删除该分支。当然,也可以将待删除的分支先 merge 到当前分支之后,再安全删除分支。这样做,其实是为了避免提交丢失。
意外删除分支或者其他引用后,可以使用 git reflog 命令恢复它。
分支命名
可以使用层次命名的方式,方便明白分支用途。比如在 bug 分支下建立不同的分支,如 bug/pr-1023
、bug/pr-17
。这种斜杠语法给你的分支名引进某种结构。
通过如下的简写,可以一次查看所有分支信息:
1 | git show-branch bug/* |
分支名命名的注意事项:
- 不能包含空格或其他特殊空白字符
- 不能包含一些特殊含义字符,比如
~`
:?`等
git add
多个场景会用到这个命令:
- 可以用它开始跟踪新文件
- 把已跟踪的文件放到暂存区
- 还能用于合并时把有冲突的文件标记为已解决状态,这个是在解决冲突时会用到的功能
常用命令:
git add -u
:将文件的修改、文件的删除,添加到暂存区,用-u
有个好处,避免把工作区没准备好的新文件直接加到暂存区了,用的较多;git add .
:将文件的修改,文件的新建,添加到暂存区,慎用;git add -A/--all
:将文件的修改,文件的删除,文件的新建,添加到暂存区,慎用;
git add -A
相对于git add -u
命令的优点 : 可以提交所有被删除、被替换、被修改和新增的文件到数据暂存区,而git add -u
只能操作跟踪过的文件。
在发出
git add
命令时,每个文件的全部内容将被复制到对象库中,并且按照文件的 SHA1 名来索引。暂存一个文件也叫缓存一个文件或是把文件放进索引。与其把git add
看成添加这个文件,不如看做添加这个内容
git diff
比较工作区和暂存区的差异
将工作区和暂存区所有文件进行比较:
1 | git diff |
只对某些文件和暂存区进行比较:1
git diff -- README.md [filename ...]
比较暂存区和 HEAD 之间的差异
1 | git diff --cached |
比较的是工作区和HEAD之间的差异
1 | git diff HEAD |
比较两个分支的差异
1 | git diff master temp |
只关心这两个分支中某个文件的差异:1
git diff master temp -- index.html
其实,分支名就是一个指针,就是一种引用,可以直接使用 commit id 比较:1
$ git diff 622a8 7e7a -- index.html
注意了:git diff A B
比较的结果可以看做是 B-A
的差集,调换 A B
顺序,正负号会有变化的。
git reset
暂存区文件的恢复
暂存区全部文件恢复成和 HEAD 一样:1
git reset HEAD
- reset 命令不加 –hard,则暂存区的内容恢复成HEAD对应的内容,工作区的变更继续保留;
- 如果加了 –hard,则不管工作区还是暂存区,内容都变回HEAD对应的内容,危险的命令,会让你在工作区的修改丢失;
git reset
有三个参数:
--soft
这个只是把 HEAD 指向的 commit 恢复到你指定的 commit,暂存区 工作区不变--hard
这个是 把 HEAD, 暂存区, 工作区 都修改为 你指定的 commit 的时候的文件状态--mixed
这个是不加时候的默认参数,把 HEAD,暂存区 修改为 你指定的 commit 的时候的文件状态,工作区保持不变
怎样取消暂存区部分文件的修改?1
git reset HEAD style.css
将工作区和暂存区保持一致
有时候修改了文件,已经保存到暂存区,之后又在工作区进行了修改,此时,发现工作的效果不如暂存区的效果好,想要将工作区和暂存区保持一致。
1 | git checkout -- <file>... |
其实,git status
都有友好的提示的:
- 如果想要变更工作区的内容,那么要想到和
checkout
命令相关; - 如果想要变更暂存区的内容,那么要想到和
reset
命令相关;
任何时候都可以通过 git status 查询索引的状态。
消除最近的几次提交
丢弃一些 commit,直接HEAD 指向了你指定的某个 commit,同时,暂存区和工作区也恢复到哪个 commit 时的状态:1
git reset --hard <commitid>
有些 commit 会丢失,是条危险的命令,要慎用。但是当你明确了你的需求,需要将工作区暂存区提交记录明确恢复到某个 commit 状态时,可以执行这个命令。
git commit
「提交」操作。当你前面采用 add
命令将文件添加到暂存区跟踪后,需要通过commit
将暂存区的内容提交到当前分支:1
git commit -m "test"
当一些已被追踪的文件修改后,常常需要git add file
,然后再git commit -m "xxxx"
,其实这两个步骤可以合二为一:1
git commit -am "test"
这么写个人觉得挺好,可以有效避免有些懒人git add .
的方式,将一切文件都添加到了暂存区,导致最后多余文件提交入库。
amend
修改最近一次 commit 的 message:1
git commit --amend
修改完 message 信息之后,保存退出即可
git commit --amend
命令本质上是用新的 commit 应该是替代了上一次的提交,不只是修改 message。比如上一次提交时有几个文件没有 add 以及 commit,可以重新进行 add 之后再 commit --amend
提交。但这次提交之后,在分支的 git log 中,不会增加一次新的 commit(因为被替换了嘛),看着效果相当于在父 commit 的基础上进行的修改。
修改历史提交的 message 信息:
可以使用 git rebase
命令:1
git rebase -i 8580
-i
会进入交互模式,有一系列指令操作对应的 commit,不要用pick
命令,而是使用reword
命令操作add ref
那次 commit,然后保存,就进入修改 message 的窗口,修改完再保存,最后就会 OK 了。
1 | $ git rebase -i 8580 |
git log -3
查看最近的3次提交,变为这样了,会发现,倒数第二次的 message 信息修改 OK 了,最新一次的 message 虽然没变,但其实,commit id
都发生了变化,「替换」的概念要记得。
git rebase 工作的过程中,就是用了「分离头指针」。rebase 意味着基于新 base 的 commit 来变更部分 commits。它处理的时候,把 HEAD 指向base 的 commit,此时如果该 commit 没有对应branch,就处于分离头指针的状态,然后重新一个一个生成新的 commit,当rebase 创建完最后一个 commit 后,结束分离头状态,Git 让变完基的分支名指向 HEAD
PS:对于团队中公用的分支,例如发布分支等,禁用 rebase,因为这样会破坏历史的 commit 信息的,将来要溯源、基于构建历史拉取补丁分支等就会带来极大不便。
连续多个 commit 合并
目前 commit 还在本地,没有 push 到团队分支上,想要将网页相关的 commit 合并成一个,就是从图中 55a9
开始的6个 commit 合成一个:1
git rebase -i 7e7a
1 | $ git rebase -i 7e7a |
变基过程中有可能会遇到冲突的,只要解决冲突即可,解决冲突的时候,只需要先修改有冲突的文件的内容,然后执行 git add <file_with_conflict>
即可,不要再 git commit
,否则多出来 commit 的,然后接着 git base --continue
即可,参考简书-git rebase解决合并冲突
历史中不连续的 commit 合并
将历史中和 readme 相关的 commit 合并,就是下图中52af 7e7a 03be 这三个 commit。
1 | git rebase -i 52af # 因为52af 是首个,没有父亲,因此需要补充一下 |
1 | $ git status |
最终整理成了两个 commit:
有意思的发现,有两个 commit 是没有祖先的:
如果将 temp 分支、js01 tag 删掉,Git 会清理掉下面那个树。
git pull
git pull命令的作用是,取回远程主机某个分支的更新,再与本地的指定分支合并。它的完整格式稍稍有点复杂:1
git pull <远程主机名> <远程分支名>:<本地分支名>
比如,取回 origin 主机的 next 分支,与本地的master分支合并:1
git pull origin next:master
如果远程分支是与当前分支合并,则冒号后面的部分可以省略:1
2
3
4git pull origin next
# 等价于下面两个命令
git fetch origin
git merge origin/next
参考:
- 阮一峰-Git远程操作详解
git push
git push
命令用于将本地分支的更新,推送到远程主机:1
$ git push <远程主机名> <本地分支名>:<远程分支名>
如果省略远程分支名,则表示将本地分支推送与之存在”追踪关系”的远程分支(通常两者同名),如果该远程分支不存在,则会被新建。1
$ git push origin master
上面命令表示,将本地的 master 分支推送到 origin 主机的 master 分支。如果后者不存在,则会被新建。
不管是否存在对应的远程分支,将本地的所有分支都推送到远程主机,这时需要使用 --all
选项:1
$ git push --all origin
git push
不会推送标签(tag
),除非使用 --tags
选项:1
$ git push origin --tags
可能会遇到 rejected
的 error,因为远端包含了一些本地是没有的变更,比如,创建远端仓库时,在远端仓库的 master 分支上新建了文件,比如 License,而本地是没有这次提交的:
把远端拉取下来:1
git fetch github master
语法:1
git fetch <远端主机名> <远端分支名>
non-fast-forward
表示,你本地 master 分支的演进不是基于远端 master 分支进行的,二者是割裂的,经过 fetch 之后,通过 gitk --all
可以看到:
解决这个问题,可以通过 rebase 或者 merge 的方式解决,现在先采用 merge 的方式:
1 | # 切到本地 master 分支 |
上面 fetch 和 merge 方式,和如下 pull 命令等效:1
$ git pull github master --allow-unrelated-histories
现在合并之后,分支演进如下:
可以看到 merge 这种方式新生成的 commit 有两个父亲。
现在重新启动将本地的 master 分支 push 到远端:1
git push github master
git rm
从 Git 中移除某个文件,就必须从已跟踪的文件清单中删除(从暂存区域移除文件),然后提交。可以使用 git rm
命令完成此项工作,并连带从工作目录中删除指定的文件,以后这个文件就不会出现在 Git 库中了。
当我们先把某文件从 Git 库中删除(亦即从暂存区移除),但仍然希望保留在当前工作目录中。比如当你忘记在.gitignore
文件中将一些文件忽略,但是却不小心把大的日志文件添加到暂存区域时,这一做法很有用:
1 | # --cached 将 README 文件从暂存区移除,但是工作区目录仍然保留 |
git checkout
基于某分支创建新分支:1
git checkout -b <new_branch_name> <base_branch_name>
这里的 base_branch_name
是指创建分支时的「基」。
- 当省略时,就是基于当前分支创建;
1 | git checkout -b <new_branch_name> |
- 当这个「基」是远端分支名时,就实现了在本地基于远端分支创建分支。
1 | git branch -av # 查看本地+远程分支列表 |
还可以可以在checkout
命令中使用 Hash 值作为起点创建分支:1
git checkout -b <name_of_branch> <commit id>
除了有“切换”的意思,checkout
还有一个撤销的作用。
举个例子,假设我们在一个分支开发一个小功能,刚写完一半,这时候需求变了,而且是大变化,之前写的代码完全用不了,好在你刚写,甚至都没有 git add
进暂存区,这个时候很简单的一个操作就直接把原文件还原:
1 | git checkout <filename> |
参考:在git中checkout历史版本
git log
查看版本演变历史:1
2git log --pretty=oneline # 检查提交日志,都在一行:<commit id> <message>
git log --oneline # 与上面命令等价
查看某人的提交:1
git log --author=michael
- 一个常用的选项是
-p
,用来显示每次提交的内容差异; - 可以加上
-2
或者-n2
来仅显示最近两次提交:
1 | git log -n2 # 查看最近的2次提交 |
列出最近两周内的提交:1
git log --since=2.weeks
图形化查看分支演变:1
2
3
4# 加了 --all 表示查看所有分支的历史,否则只能看到当前分支的演变历史
git log --all --graph
# 只查看指定分支的演变历史,比如 temp 分支,此时就不能使用参数 --all
git log --oneline --graph temp
检查某个文件的历史记录:1
2# --follow 选项会让 Git 在日志中回溯并找到内容相关联的地整个历史记录
git log --follow mydata
参考:
- git log命令全解析,打log还能这么随心所欲!
- git-scm 2.3 Git 基础 - 查看提交历史
远程仓库的使用
查看远程仓库
1 | git remote -v |
如果想查看远程仓库更多的信息,可以使用git remote show <remote-name>
命令。
远程仓库的移除与重命名:1
2git remote rename <old-remote-name> <new-remote-name>
git remote rename pb paul
如果因为一些原因要移除一个远程仓库,可以使用git remote rm <remote-name>
。
添加一个新的远程 Git 仓库,同时指定一个可以轻松引用的简写:
1 | git remote add <remote_host_name> <url> |
这里的remote_host_name
常常取名为origin
。所以,常见的origin
其实是一个你 Git 仓库跟踪的远程仓库的简写。
拉取远端仓库有但你本地没有的信息:
1 | git fetch <remote_host_name> |
如果你使用clone
命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以origin
为缩写。
git rev-parse
git rev-parse
命令可以将任何形式的提交名——标签、相对名、简写或者绝对名称,转换成对象库中实际的、绝对的提交散列 ID。
git tag
列出标签
1 | git tag # 列出所有标签 |
创建标签
Git使用两种主要类型的标签:
- 附注(annotated)标签:会创建一个标签对象
- 轻量(ightweight)标签:轻量级标签仅仅是一个提交对象的引用,可以
git rev-parse v1
来查看它的 SHA1 值
前者会包括一些注释信息,来进一步解释这个 tag 的作用,而后者就仅仅只是一个 tag 的名字
附注标签:
1 | git tag -a v1.4 -m 'my version 1.4' |
通过 git show <tag-name>
命令可以看到标签信息。其实,还以通过 git show branch_name:file_name
查看具体分支或者标签中的文件的内容。
轻量标签:
1 | git tag v1.4 |
没用-a
、-m
的参数,只需要提供标签名
tag 是一个静态的名字,相当于一个 snapshot 的概念,而分支是动态的。如果你创建的 tag 名和分支名一样,容易造成混淆,就必须使用索引名全称来区分它们。比如
refs/tags/v1
、refs/heads/v1
删除标签
1 | git tag -d <tagname> |
补打标签
假设忘记给项目打标签,可以在之后加上:
基于某历史节点的commit id
补打Tag
:
1 | git tag -a v1.2 <commit id> |
共享标签
默认情况下,git push
命令并不会传送标签到远程服务器上。在创建完标签后你必须显示地推送标签到共享服务器上。这个过程就像共享远程分支一样,可以运行git push origin [tagname]
如果想要一次性推送很多标签,也可以使用--tags
选项的git push
:1
git push origin --tags
检出标签
根据标签打的时间点,新建一个分支:
1 | git checkout -b <new_br> <tag_name> |
参考:
- The Junior Git
- 6 Git 基础 - 打标签
高阶命令
git bisect
二分查找,快速定位 bug 的提交
场景:定位 Bug,当前版本有B ug,上个版本没有,两个版本之前有上千次 commit
二分查找,N 个 patch 只需要测试 log2N 次(8k 个 path 仅需测试 13 次)
可以实现测试自动化,自动查找问题 patch
git bisect
:只需要在初始时提供一个初始的「好」提交和「坏」提交。然后重复回答这个版本是否 OK
- 使用
git bisect start
开始 git bisect bad
告诉是「坏」提交,默认是HEAD
git bisect good v1.0
告诉 V1.0 是「好」提交- 反复回答
git bisect good/bad
告诉是「好/坏」提交 git bisect reset
恢复到分支一开始git bisect log
记录你回答的日志
小众命令
清除未跟踪文件
有时候一个仓库目录下莫名会多出一些你不想要的文件,这些文件常常是未跟踪的文件。怎么快速将它们删除呢?
1 | git clean -df |
将未跟踪的目录及文件删除,可以加 -n
选项,先查看有哪些文件将被删除运行!
最后
- Git使用教程笔记
- Pro Git 中文
- Git常用命令备忘
- 掘金-今年下半年,中日合拍的《Git游记》即将正式开机,我将…(上集)
- git术语解释staging,index,cache
- pcottle/learnGitBranching 分支演示网站