Git
# Git
## 版本控制系统

版本控制系统（version control system，VCS）,版本控制是一种记录一个或若干文件内容变化，以便将来查阅特定版本修订情况的系统。历史上出现过各种各样的VCS：如1982年的RCS，现在你还可能在Unix的发布中找到它,1985年的PVCS,1990年底的CVS,1992年的clearcase,微软的VSS（welcome to Hell）,90年代中期的Perforce，以及SVN和BitKeeper，还有我们即将介绍的git. 

版本控制系统出现的原因是由实际需求推动的。在没有VCS的情况下，我们维护版本的方法是复制整个项目目录的方式来保存不同的版本，或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单。不过坏处也不少：有时候会混淆所在的工作目录，一旦弄错文件丢了数据就没法撤销恢复。解决这个问题的方法是开发版本控制系统，采用某种简单的数据库来记录文件的历次更新差异的来实现。
### 集中式版本控制系统

集中化的版本控制系统（ Centralized Version Control Systems，简称 CVCS ），有一个单一的集中管理的服务器，保存所有文件的修订版本，而协同工作的人们都通过客户端连接到这台服务器，取出最新的文件或者提交更新。这类系统，诸如 CVS，Subversion 以及 Perforce 等。优点是每个人都可以在一定程度上看到项目中的其他人正在做些什么，而管理员也可以轻松掌控每个开发者的权限，并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易；缺点是中央服务器的单点故障有丢失数据的风险。
### 分布式版本控制系统

分布式版本控制系统（ Distributed Version Control System，简称 DVCS ），采用对等网络式的方式，客户端并不只提取最新版本的文件快照，而是把代码仓库完整地镜像下来，其中存储的有不同的版本信息和维护一个repository的必要的辅助信息。这么一来，任何一处协同工作用的服务器发生故障，事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作，实际上都是一次对代码仓库的完整备份。这类系统，像 Git，Mercurial，Bazaar 以及 Darcs 等。
## Git简史

Linus在1991年创建了开源的Linux，从此Linux系统不断发展，已经成为最大的服务器系统软件了。Linus虽然创建了Linux，但Linux的壮大是靠全世界热心的志愿者参与的，这么多人在世界各地为Linux编写代码，世界各地的志愿者把源代码文件通过diff的方式发给Linus，然后由Linus本人通过手工方式合并代码。当时是有CVS、SVN这些免费的版本控制系统，但是Linus坚定地反对CVS和SVN，这些集中式的版本控制系统不但速度慢，而且必须联网才能使用。有一些商用的版本控制系统，虽然比CVS、SVN好用，但那是付费的，和Linux的开源精神不符。

到了2002年，Linux系统已经发展了十年了，代码库之大让Linus很难继续通过手工方式管理了，社区的弟兄们也对这种方式表达了强烈不满，于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神，授权Linux社区免费使用这个版本控制系统。后来Linux社区开发Samba的Andrew试图破解BitKeeper的协议，被BitMover公司发现了。到了2005年，开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束，他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区（特别是 Linux 的缔造者 Linus Torvalds ）不得不吸取教训，只有开发一套属于自己的版本控制系统才不至于重蹈覆辙。他们对新的系统制订了若干目标：
- 速度
- 简单的设计
- 对非线性开发模式的强力支持（允许上千个并行开发的分支）
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目（速度和数据量）

自诞生于 2005 年以来，Git 日臻成熟完善，在高度易用的同时，仍然保留着初期设定的目标。它的速度飞快，极其适合管理大项目，它还有着令人难以置信的非线性分支管理系统,可以应付各种复杂的项目开发需求。Git 和其他版本控制系统的主要差别在于，Git 只关心文件数据的整体是否发生变化，而大多数其他系统则只关心文件内容的具体差异。
## Git使用
### Git重要概念
#### 文件的三种状态

对于任何一个文件，在 Git 内都只有三种状态：
- 已提交（committed）:表示该文件已经被安全地保存在本地数据库中了
- 已修改（modified）:表示修改了某个文件，但还没有提交保存
- 已暂存（staged）:表示把已修改的文件放在下次提交时要保存的清单中。
#### Git的三个工作区域

文件流转的三个工作区域：Git 的工作目录，暂存区域，以及本地仓库。
- 工作目录（working directory）：存放源代码的目录，就是git目录，用来保存元数据和对象数据库的地方，每次克隆镜像仓库的时候，实际拷贝的就是这个目录里面的数据。
- 暂存区域(staging area):是个简单的文件，一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件，不过标准说法还是叫暂存区域。对修改后的文件进行快照，然后保存的区域。
- 本地仓库(repository):修改经过commit提交，从暂存区域提交到git管理的区域，这个区域保存了文件的快照。
- 远程仓库(repository):存储在远端服务器上的，仓库，大家共享的文件仓库，保存所有文件的所有快照。
#### Git的工作流程

Git的基本工作流程如下：
1. 在工作目录中修改某些文件。
2. 对修改后的文件进行快照，然后保存到暂存区域。
3. 提交更新，将保存在暂存区域的文件快照永久转储到 Git 目录中。
4. Tip: push将本地的文件快照推送到远程的Git目录中
### Git基本命令

**Git安装**

以下的讲述基于linux平台，在使用git的命令前，需要安装git程序，Fedora 上用 yum 安装

```
$ yum install git-core
```

而在 Ubuntu 这类 Debian 体系的系统上，可以用 apt-get 安装　

```
$ sudo apt-get install git
```

**Git初始化**

```
git init
```

说明：通过这个命令把当前工作目录变成git可以管理的仓库。

**Git添加文件**

```
git add filenpath 
```

```
git add * 
```

```
git add .
```

说明：用此命令添加文件到git仓库。

**git 提交文件**

```
git commit -m "Commit message"
```

说明：将暂存区的文件快照提交到本地仓库中。

**Git查看状态**

```
git status
```

说明：查看git仓库当前的状态。

**Git文件区别**

```
git diff <filepath>
```

说明：可以查看工作区文件和git仓库文件的差别。

**Git历史查询**

```
git log
```

说明：此命令可以查看commit的历史记录。

**Git查看分支合并图**

```
git log --graph
```

说明：可以查看分支合并的历史记录。（我都是在gitlab 或 github 页面里看 感觉这个命令有点 然并*）

**Git版本回退**

```
git reset [空 或者 HEAD]
```

说明：回退版本到以前提交的某个commit，也可以把暂存区的修改回退到工作目录中。
tip：如果想撤销本地的所有修改和commit，可以使用如下 git fetch origin  &&  git reset --hard origin/master 或使用 git reset --hard origin/master，如果想撤销本地的所有修改到某个commit 可以使用 git reset --hard HEAD

**Git操作记录**

```
git reflog
```

说明：记录每一次操作（有点像history命令）

**Git撤销修改**

```
git checkout .
```

说明：撤销工作目录中的修改。

**Git撤销单个修改**

```
git checkout <filename>
```

说明：取消filename中未提交的修改。

**Git删除文件**

```
git rm <filename>
```

说明：从git仓库中删除filename文件

**Git关联远程仓库**

```
git remote add 
```

说明：使本地仓库和远程仓库创建关联。

**Git复制远程仓库**

```
git clone
```

说明：从远程仓库copy一份到本地。

**Git抓取远程仓库的修改**

```
git pull
```

说明：拉取远程代码库的代码，通常是团队中其他成员提交的代码

**Git本地修改上传远程仓库**

```
git push
```

说明：推送本地的修改到远程仓库。

**Git创建分支**

```
git branch  <branch-name>
```

说明：创建branch-name分支。

**Git切换分支**

```
git checkout <branch-name>
```

说明：切换到branch-name分支。

**Git创建并切换分支**

```
git checkout -b <branch-name>
```

说明：创建分支branch-name并切换到此分支。

**Git查看当前分支**

```
git branch
```

说明：查看当前分支并列出所有分支。

tip: -a 参数可以查看远程分支信息

**Git合并分支**

```
git merge --no-ff <branch-name>
```

说明：合并指定分支到当前分支。如果你只有一个commit 可以使用 git cherry-pick [分支名，或者commitID]

**Git删除本地分支**

```
git branch -d  <branch-name>
```

说明：删除本地的分支xxx-branch-name。

**Git删除远程分支**

```
git push origin :[远程分支名]
```

说明：删除远程某个分支 冒号前面一定要有空格。

**Git本地分支推送到远程分支**

```
git push origin [本地分支名]:[远程分支名]
```

说明：将本地仓库推送到远程仓库
### Git高级用法

任何命令，都可以通过 

```
git <想要了解的命令> --help
```

来获得更详细的说明及用法
#### Blame

**说明**

针对文件的每一行，显示其最后一次被更新的信息及其作者

**命令**

```
git blame <文件名>
```

查看某一特定文件中每一行最后一次被更新的信息
#### Remote

**说明**

管理追踪的多个远端仓库。注意，一个远端仓库名称可以对应多个仓库地址(url)。

**命令**

```
git remote add <仓库名称> <仓库地址>
```

添加一个远端仓库。这样 `git fetch <仓库名称>` 就可以以此跟踪远端仓库。

```
git remote show
```

列出所有远端仓库名称。

```
git remote rename [<旧仓库名称>] <新仓库名称>
```

更改远端仓库名称。

```
git remote remove <仓库名称>
```

删除远端仓库。此操作会删除与此仓库相关的所有分支和配置信息。

```
git remote show [<仓库名称>]
```

若仓库名称为空，则列出所有远端仓库名称；否则，列出特定远端仓库的相关信息，例如HEAD branch、remote branch，配置信息等。

```
git remote set-url <仓库名称> <新仓库地址> [<旧仓库地址>]
```

更新特定远端仓库名称下的部分仓库地址，其中<仓库地址>可以是正则表达式。

```
git remote set-url --add <仓库名称> <仓库地址>
```

为特定远端仓库名称增加新的仓库地址。

```
git remote set-url --delete <仓库名称> <仓库地址>
```

删除特定远端仓库名称下的部分仓库地址，其中<仓库地址>可以是正则表达式。
#### Branch

**说明**

对分支进行管理。

**命令**

```
git branch <branch name> [<commit ID>]
```

基于当前分支的某次commitID 来创建一个新分支

commit ID可选，也可以替换成分支名称,或tag。创建后不会自动跳转至该分支，需手动 `git checkout <分支名称>`。

若想快速创建一个分支并跳转至该分支，请使用 `git checkout -b <branch name> [<commit ID>]`。

```
git branch [-a] [--list <pattern>...]
```

列出所有的分支。 -a 列出所有远端分支和本地分支。

```
git branch (-m | -M) [<旧分支名称>] <新分支名称>
```

修改分支名称。若<新分支名称>已存在，-m不会修改分支名称，并给出提示，-M会强制覆盖已有分支。-a 列出所有远端分支和本地分支。

```
git branch (-d | -D) [-r] <分支名称>...
```

删除分支。若该分支尚未被合并，-d不会删除分支，并给出提示，-D会进行强制删除。

分支被删除后，下次执行 fetch 或 pull 操作时仍会被创建，若想避免，需对 fetch 或 pull 命令进行配置。详情请查看 `git fetch --help`

```
$ git branch -v
```

查看各个分支最后一个提交对象的信息

```
$ git branch --merged
  iss53
* master
```

查看哪些分支已被并入当前分支，之前我们已经合并了 iss53，所以在这里会看到它。一般来说，列表中没有 \* 的分支通常都可以用 git branch -d 来删掉。原因很简单，既然已经把它们所包含的工作整合到了其他分支，删掉也不会损失什么。

```
$ git branch --no-merged
  testing
```

它会显示还未合并进来的分支，由于这些分支中还包含着尚未合并进来的工作成果，所以简单地用 `git branch -d` 删除该分支会提示错误，因为那样做会丢失数据，不过，如果你确实想要删除该分支上的改动，可以用大写的删除选项 -D 强制执行
#### Stash

**说明**

暂存当前工作状态。

**命令**

```
git stash [save [message]]
```

储存当前工作状态。可添加一些必要的说明，以便后期查找。

```
git stash list
```

列出所有的暂存。

```
git stash show [<stash>]
```

显示特定暂存与其起点之间的变化，与 diff 命令相似。若无<stash>参数，则默认为最后一个暂存。

```
git stash pop [<stash>]
```

恢复特定暂存，同时从暂存列表移除。若恢复时发生冲突，则暂存不会从列表中移除，需解决完冲突后手工移除( <code>git stash drop</code> )。

```
git stash apply [<stash>]
```

恢复特定暂存。该命令类似 pop, 不同的是，该命令不会自动从暂存列表移除该暂存。

```
git stash drop [<stash>]
```

从暂存列表移除特定暂存。若无<stash>参数，则默认为最后一个暂存。

```
git stash clear
```

移除暂存列表中的所有暂存。
#### checkout

**用法一**

```
git checkout <file name>
```

取消某些文件在本地的变更，原理是（用暂存区里最近一次提交的内容覆盖某文件，达到取消对某文件的修改）

**用法二**

```
git checkout <branch name>
```

切换到某分支

**用法三**

```
git checkout -b <new branch name>
```

新建一个分支，并切换到该分支

**用法四**

```
git branch <分支名> <commitID>
```

基于当前分支的某一次commit来创建分支
#### diff

**用法一**

ps: 文件三种状态，working tree, index file, commit

```
git diff <文件名>
```

是查看working tree与index file的差别的。

**用法二**

```
git diff --cached <文件名>
```

查看index file与commit的差别的。

**用法三**

```
git diff HEAD <文件名>
```

是查看working tree和commit的差别的

ps:以上三种方法不加文件文件，则比较所有文件（出去gitignore 掉的文件）

**用法四**

```
git diff <commitID>/<branch>
```

比较working tree 与某一提交/分支的差别

**用法五**

```
git diff <commitID>/<branch> <commitID>/<branch>
```

查看某一提交/分支与某一提交/分支的差别
#### reset

**用法一**

```
git reset –hard HEAD
```

回退到一个特定的历史版本。丢弃这次提交之后的所有变更。

**用法二**

取消add

```
git reset HEAD
```

回滚到一个特定的历史版本。将这个版本之后的所有变更移动到“未暂存”的阶段。这也就意味着你需要运行 git add . 和 git commit 才能把这些变更提交到仓库

**用法三**

取消commit

```
git reset --soft HEAD
```

回滚到一个特定的历史版本。将这次提交之后所有的变更移动到暂存并准备提交阶段。意味着你只需要运行 git commit 就可以把这些变更提交到仓库。

**用法五**

撤销merge

```
git reset --merge ORIG_HEAD
```

此种情况会保留你的commit，如果使用`git reset --hard` 去删除掉你的commit
任何时候使用`git reset --hard` 请慎重

#### 其他

标签
```bash
# 列出所有tag
$ git tag
# 新建一个tag在当前commit
$ git tag [tag]
# 新建一个tag在指定commit
$ git tag [tag] [commit]
# 删除本地tag
$ git tag -d [tag]
# 删除远程tag
$ git push origin :refs/tags/[tagName]
# 查看tag信息
$ git show [tag]
# 提交指定tag
$ git push [remote] [tag]
# 提交所有tag
$ git push [remote] --tags
# 新建一个分支，指向某个tag
$ git checkout -b [branch] [tag]
```