Git的基础使用
Git基础使用
1. 创建
在本地创建一个git仓库:git init
克隆一个远程仓库:git clone <网址>
2. 文件跟踪、修改与提交
仅在本地使用时,git可分为三个区域:工作区、暂存区(stage)与本地仓库(repository)。工作区就是当前目录,使用git add
命令可将已修改(modified)文件中希望提交的文件存储到暂存区,暂存区中记录了预期的下一次提交,使用git commit
命令将暂存区中的文件提交到本地仓库,生成一条记录,相关文件回到未修改(unmodified)状态。在工作区、暂存区、仓库之外还有一个"未跟踪"(untracked)文件列表,其中的文件不会出现在工作区。下面这张图展现了git中各个区域的关系
正确编写.gitignore
本地目录中不止有源码,还可能有编译器生成的中间文件、二进制文件,或者IDE生成的一些临时文件。将它们一并列入跟踪文件会引起巨大的混乱,而不跟踪又要每次在untracked列表中见到它们。.gitignore(注意此文件以点号开头)可以帮助忽略这些文件。
目录结构
一份.gitignore文件会递归地应用到所有子目录
- 直接写文件名表明忽略此文件:
.gitignore
- 使用在结尾的
/
表明这是个目录,且要忽略整个目录:build/
- 使用在开头的
/
表明仅忽略当前目录下的文件,阻止递归:/TODO.txt
通配符
*
匹配任意字符串:*.pdf
忽略所有pdf文件[...]
匹配括号中任意一个字符:main.[oa]
忽略main.o与main.a文件;如果方括号中字符ASCII码是连续的可以用-
省略,如[a-zA-z0-9]
!
显式地表明需要跟踪:*.pdf
!README.pdf
忽略所有pdf文件但不要忽略README.pdf- 使用
**
匹配任意目录:a/**/main.exe
忽略a/main.exe, a/b/main.exe与a/b/c/main.exe
将文件从仓库中移除
有时会不小心将某个不需要跟踪的文件(比如本应使用.gitignore忽略的临时文件)提交到仓库,甚至几个提交后才刚发现。但木已成舟,此时再去修改.gitignore文件是没有作用的。 应使用以下命令
git rm -cached <文件名>
使用此命令只是将文件不再纳入版本管理,而不影响本地磁盘上的文件。
修改最近的一次提交
有时可能提交到仓库后才发现漏了一个文件,此时可以继续使用git add
修改暂存区并在提交时增加--amend
选项,这样会在旧的提交上添加补丁而不是新增一次提交。也适用于提交说明中有一个拼写错误需要修改的情况
git commit --amend
取消暂存
和上一种情况不同,有时使用git add
将已修改的文件加入到暂存区后想撤销,但还没有使用git commit
提交。
git reset HEAD <文件名>
文件回档
有时修改了某个文件后不work了,急于回档到之前某个稳定的版本。
git checkout -- <文件名>
这会丢失此文件中所有的工作记录,回到上一次提交的状态
工作区回档
有时整个目录下的文件都改得乱七八糟,不想要了,怎么让它恢复到稳定版本?
git reset --hard HEAD
这样做会丢失所有此工作目录下的工作记录。
还可以回档到之前几次提交
git reset --hard HEAD^ 回到上一次提交状态
git reset --hard HEAD^^ 回到上一次再上一次提交状态
git reset --hard HEAD~3 回到之前三次提交状态
查看历史小技巧
单单使用git log
命令可以显示所有历史提交,此外这一条命令还有丰富的选项可以帮助筛选内容
选项 | 作用 | 使用场景 |
-n,n为数字 | 显示最近的n次提交 | 如果不想被一堆提交信息淹没,这个选项很有用 |
--pretty=oneline | 将提交信息放在一行显示 | 浏览大量历史记录时 |
-stat | 显示所有被修改的文件名和这些文件增加、删减的行数 | 查看某一次提交修改了哪些文件 |
-p | p是patch的缩写,意为除了显示提交信息之外还显示patch的内容 | 查看某一次提交具体作了什么修改 |
--grep | 后接希望查询的内容,在提交信息中查询并列出匹配的历史;使用多个--grep时默认多个条件为“或”逻辑,再添加--all-match则是“与”逻辑 | 查询实现某种功能的patch,有没有用取决于小组中每个人是否认真写了提交信息,以及提交信息中不能有拼写错误 |
-S | 后接希望查询的内容,在patch中查询 | 查询某个具体的函数在哪个patch中被添加、删除 |
--no-merges | 不列出合并提交记录 |
Git分支管理与远程仓库
分支管理是Git最有特色的功能,也是最开始使用时发生问题最多的功能。
Git命令 | 功能 |
git branch -a | 显示所有分支 |
git branch <分支名> | 从当前分支创建一个新的分支 |
git checkout <分支名> | 切换分支 |
git checkout -b <分支名> | 以上两条命令可以合并到一起的快捷命令 |
git branch -d <分支名> | 安全删除一个分支,只有当此分支已经合入到其他分支后此命令才可能成功 |
git branch -D <分支名> | 强制删除分支,这是一条危险的命令,可能会丢失工作进度 |
远程仓库相关
Git命令 | 功能 |
git remote add <远程仓库名> <远程仓库URL> | 为本地仓库添加关联的远程仓库 |
git branch --set-upstream <远程仓库名>/<远程仓库中的分支名> | 将目前本地分支与远程仓库中某个分支关联,从此跟踪该远程分支的变化 |
git fetch | 从关联的远程分支获取最新的patch,之后使用git merge 可以合并这些修改 |
git pull | 相当与git fetch + git merge |
分支合并
git merge
命令会有两种行为
- 如果需要合并修改的文件全都来自远程仓库,本地没有修改,会进行fast-forward,即将HEAD指针直接指向最新的patch
- 如果同一个文件本地修改过远程仓库patch也修改过,会进行是所谓的三方合并(3-way merge)。在合并不产生冲突的情况下,三方合并一定会产生一条合并提交记录(形如Merge ... from ...),无法避免。这些合并提交并不包含很多有用的信息,也难怪
git log
指令专门有一个--no-merges
选项忽略它们。当三方合并发生冲突时,Git会停下并要求用户手动地修改源文件 此时,可以使用git status
列出发生冲突的文件名,打开这些文件后会看到其中有类似<<<<<<===========>>>>>>
的字段,等号上方是本地所做的修改,等号下方是远程分支所做的修改,两者只能保留其一。
git rebase <to> <from>
命令也是用来合并分支的,但不会生成一条合并提交记录。它的原理是抛弃from分支上现有的提交,保留这些提交所做的修改,之后在to分支上重新应用这些修改。这样合并后的分支一直是沿着to分支前进的,提交记录为线性。
子模块 submodule
在Git中一个完整的项目有自己的仓库,而这个仓库可以作为一个子模块加入另一个仓库中。子模块的添加有些类似于Linux中硬盘挂载的方式,如果只克隆了母仓库而没有克隆子仓库,只会有一个空文件夹存在。
- 克隆仓库时一起克隆子仓库:
git clone --recursive-submodules
- 遍历所有子仓库:
git submodule foreach "<shell命令>"
,比如git submodule foreach "git checkout develop"
可以将每个子模块都切换到develop分支 - 子模块由维护信息存储在.gitmodule文件夹中,可以用以下命令列举当前目录下所有子模块:
git config --file .gitmodules --name-only --get-regexp path