# Git的基础使用

## Git基础使用
### 1. 创建
在本地创建一个git仓库：`git init`   
克隆一个远程仓库：`git clone <网址>`
### 2. 文件跟踪、修改与提交
仅在本地使用时，git可分为三个区域：工作区、暂存区(stage)与本地仓库(repository)。工作区就是当前目录，使用`git add`命令可将已修改(modified)文件中希望提交的文件存储到暂存区，暂存区中记录了预期的下一次提交，使用`git commit`命令将暂存区中的文件提交到本地仓库，生成一条记录，相关文件回到未修改(unmodified)状态。在工作区、暂存区、仓库之外还有一个"未跟踪"(untracked)文件列表，其中的文件不会出现在工作区。下面这张图展现了git中各个区域的关系
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1654135525238/pmCUrMR0w.png align="left")
### 正确编写.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`命令会有两种行为
1. 如果需要合并修改的文件全都来自远程仓库，本地没有修改，会进行fast-forward,即将HEAD指针直接指向最新的patch
2. 如果同一个文件本地修改过远程仓库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`
