一、概念
提交和分支
-
Git版本管理是通过提交(commit)组织起来的,每一个新的提交只记录产生变化的文件,所以存储开销不大。
-
下文中的
<ref>
可以是分支(branch)、标签(tag)、提交号(CommitID)、HEAD或FETCH_HEAD等。其中branch、tag和FETCH_HEAD是指向某个提交的指针,它们的创建、删除和移动的开销很小。随着分支和提交的增多,提交历史形成了树状结构。 -
HEAD是当前所在分支的别名。如果分支名很长,可以用HEAD方便的表示当前分支。在一些自动化脚本里,HEAD也许会很有用。
-
在某些修改发生后(例如
git commit --amend
),这个树状结构的某一条分支的最新的提交,没有被任何branch或tag指向,则这条分支将不会显示在提交历史中,但它在短时间内仍存在于仓库中,可以使用git reflog
命令找到相关的提交,然后用git branch/checkout
创建分支。
工作区和暂存区
- 将每一个提交、工作区和暂存区分别视为一个版本,使用
git status
可查看暂存区版本和HEAD版本之间有差异的文件(Changes to be committed)、工作区版本和暂存区版本之间有差异的文件(Changes not staged for commit)。
-
在分支没做任何修改的情况下,暂存区版本和HEAD版本保持一致,工作区版本和暂存区版本保持一致,所以
git status
结果以及VSCode的Git插件下不会显示内容。 -
当对某文件做了修改,则工作区版本发生变化,工作区版本和暂存区版本就该文件不一致。
-
然后使用
git add
将工作区文件版本刷新到暂存区,此时工作区版本和暂存区版本就该文件是一致的,而暂存区版本和HEAD版本就不再一致了。 -
git commit
将暂存区版本加入到Git历史中,于是暂存区和HEAD再次一致。
远程仓库和远程分支
-
Git是分布式版本管理系统,每个仓库在Git系统中是平等的,一个仓库可以连接有多个远程仓库,它自己也可以作为其他仓库的远程仓库。
-
位于本地仓库和远程仓库的分支在Git系统中也是平等的,只不过在开发中,人为地将远程仓库的分支作为稳定版本,而本地仓库的分支作为开发分支。之所以提这一条,是因为初学者往往把master分支和origin/master分支混淆起来,造成麻烦。
使用分支的策略
-
如果无需连接远程仓库,设主干分支为master,一般新建dev分支开发,然后合入到master分支中。
-
在多人协作开发中,主干分支一般为origin/master,在本地dev分支开发完后push到远程仓库的dev分支,然后创建合并请求合入到origin/master。
二、初始化仓库
git init
text
git init <path>
初始化文件夹为Git仓库。
git init
git init .
这两行等价
--bare
create a bare repository
仓库将是bare仓库,不含工作区和暂存区。这种仓库可用作发布仓。
git clone
text
git clone <repo> [<dir>]
可用<dir>指定不同于仓库名的文件夹名。
-b, --branch=<branch>
checkout <branch> instead of the remote's HEAD
切换到指定分支,而不是远程仓库HEAD指向的分支。
--single-branch
clone only one branch, HEAD or --branch
仅拉取一个分支,HEAD指向的分支或者--branch指定的分支。
--depth=<depth>
create a shallow clone of that depth
仅拉取给定深度的提交历史,即拉取的历史提交和分支所在提交的距离不超过depth。
--bare
create a bare repository
三、工作区和暂存区
git add
- 功能:用工作区的文件刷新暂存区的文件。
- 可以使用通配符,如
git add *.c
.gitignore
文件存放不用跟踪的文件;每个文件夹都可有此文件。更多规则参考忽略文件。
git commit
- 功能:提交暂存区版本。
- 常用选项
text
-m, --message <message>
commit message
-C, --reuse-message <commit>
reuse message from specified commit
-c, --reedit-message <commit>
reuse and edit message from specified commit
--amend
amend previous commit
修订前一个提交,修改文件或提交信息。
从另一个角度理解,实际上是把暂存区版本提交了,并将当前分支位置移动到新的提交上。
-a, --all
commit all changed files
提交前,先把工作区的修改文件都转到暂存区。
- 在未使用
-m
或-C
等选项来指定提交信息时,将弹出默认编辑器来编辑提交信息。在纯终端环境,编辑器大概率是Vim类的终端文本编辑器。在Windows上可设置默认编辑器为VSCode。
bash
git config --global core.editor "code --wait"
git restore
text
git restore [--source=<ref>] [--staged] [--worktree] [--] <path>
将文件恢复为指定版本。
--staged
在未使用--staged选项时,操作工作区,指定后,操作暂存区。
同时使用--staged和--worktree选项,则同时操作暂存区和工作区。
--source
在未指定source选项时,对暂存区默认source是HEAD,对工作区默认source为是暂存区。
- 因此下列命令可以理解为
text
git restore <path>
舍弃工作区的修改,即工作区版本和暂存区版本保证一致。
git restore <path> --staged
舍弃暂存区的修改,即暂存区版本和HEAD版本保持一致,不影响工作区。
git restore <path> --staged --worktree
舍弃暂存区和工作区的所有修改,即工作区版本和暂存区版本都和HEAD保持一致。
- 下列命令也具有类似功能,但不建议使用。
checkout
主要功能是切换分支,使用下列命令容易混淆。
text
git checkout <ref> [--] <path>
将工作区和暂存区都恢复为给定版本。
等同于
git restore --source=<ref> --staged --worktree [--] <path>
git checkout [--] <path>
不指定<ref>时,仅操作工作区,使其和暂存区保持一致。
等同于
git restore <path>
git clean
git restore
不会删除新增文件和忽略文件,这些文件可通过git clean
删除。
text
git clean <paths> --dry-run
删除新增文件
-n, --dry-run
dry run
显示将要删除的文件列表,不会实际删除这些文件。
-f, --force
force
必须指定此选项才会删除
-d
remove whole directories
使用此选项才会递归文件夹
-e, --exclude <pattern>
add <pattern> to ignore rules
-x
remove ignored files, too
-X
remove only ignored files
有这两个选项,才会删除忽略文件,但是一般也不用,例如,'node_modules/'文件夹就是
项目运行调试需要但不需要加入Git的文件夹。
git stash
当工作区或暂存区操作修改时切换分支,若有冲突,则切换失败,可将这些修改放入stash,然后切换分支。 (没怎么用这些命令行,一般使用GitLen插件)
text
git stash push ...
将更改放入stash
git stash list/show ...
前者列出stash列表,后者列出一个stash的信息。
git stash branch ... ?
git stash pop/apply ...
应用stash,前者将这个stash从stash列表中移除,后者不会。
git stash clear/drop ...
前者删除全部stash,后者删一个。
git revert
(很少用)
text
git revert <commitid> <commitid> ...
在当前分支新建提交,这个提交会将commitid所做的变更取消。
四、分支
git branch
展示、创建、删除、重命名分支。
text
git branch -vva
显示所有分支(包括远程仓库的分支)和分支的跟踪关系,*标记当前分支。
git branch <branch-name> [<ref>]
在当前提交或给定提交<ref>处创建新分支,不切换到新分支。
git branch -d <branch-name>
删除分支。
git branch -m <old> <new>
重命名分支。
git checkout
切换分支。
text
git checkout <ref>
<ref>如果是CommitID/tag/带仓库名的远程分支,则将在ref创建临时无名分支,切出分支后不保留修改。
如果希望保留修改,在临时分支上创建新分支即可。
git checkout -b <new-branch> <ref>
在给定提交上创建新分支,并切换到新分支。如果<ref>是远程分支,还会设置跟踪关系。
git checkout -
切回到上一个分支。
--orphan
创建一个新的分支,分支上没有任何提交,暂存区版本和HEAD版本内容相同。
git reset
重设分支位置,并且根据选项决定对工作区和暂存区的操作。HEAD是当前分支的别名,所以HEAD也会被重设。
text
git reset [options] <refs>
--soft
不重置工作区版本和暂存区版本,即不变。
--mixed
不重置工作区版本,重置暂存区版本,
--hard
同时重置工作区和暂存区版本,使其与重置后HEAD保持一致。即丢失所有修改。
使用--mix
或--hard
可能导致丢失修改。
当<refs>为HEAD,将不会重设分支位置,命令效果和git restore
相似。(前两条从没用过,第三条用得较多。)
text
git reset --soft HEAD
无任何作用。
git reset --mixed HEAD
等同于
git restore * --staged
git reset --hard HEAD
等同于
git restore * --staged --worktree
五、分支和提交
git merge
text
git merge <ref> -m <message>
- 如果当前分支是被合并分支的祖先,则会简单移动地当前分支。
- 如果被合并分支是当前分支的祖先,则不变。
- 如果位于两条支链上,将在当前分支创建新的提交把另一个分支(节点)内容合并到当前分支,可能需要解决冲突。
text
git merge <ref1> <ref2> -m <message>
一次可以合并多个分支。
--squash
create a single commit instead of doing a merge
不使用这个选项时,提交历史将保留合并关系,即是由哪些分支合并来的。
使用这个选项,将仅创建新提交,提交历史成线性。这个选项适合被合并分支的
提交历史过多或过于杂乱的情况。
--no-ff
一个线性的提交历史上的两个分支,较老的分支合并较新的分支,在本地进行这
样的操作时,将会fast forward,如果希望采用创建新提交的方式合并,则使用
这个选项。
git rebase
text
git rebase <ref>
- 如果当前分支和是祖先关系,则同merge。
- 如果位于两条支链上,则将共同祖先到当前分支的所有变更作用到另一个分支,并生成新的一系列提交,然后将当前分支重设到最新的提交上。当前分支原来的提交历史不保留,短时间内可用
git reflog
查看。
text
git rebase --onto=<ref> <ref1>^ <ref2>
将<ref1>到<ref2>的提交移植到以<ref>为起点。
git cherry-pick
text
git cherry-pick <ref> ...
将一个提交的更改应用到当前分支。
git cherry-pick <ref1>^..<ref>
应用ref1到ref的修改到当前分支。
(未完待续)