下述内容为团队内部分享整理所得,实用性较强,整体性偏差! PS:关于完整的Git内容,请参照之前发表过一系列文章,详见:Git Pro深入浅出(一)Git Pro深入浅出(二)Git Pro深入浅出(三) 推荐两个地址:ProGitGotGit

阅读完内容,你会很快的解决下面问题,并了解其底层原理。

问题1:如何丢弃本地工作区修改的内容?

  1. $ git checkout -- <filename>

问题2:如何丢弃本地工作区和暂存区修改的内容?

  1. $ git checkout HEAD <filename>

问题3:误删了文件且已经提交推送到远程仓库,如何恢复?

  1. # 查看远程前一次提交的文件树 支持管道过滤 | grep <filename>
  2. $ git ls-files --with-tree=origin/HEAD^
  3. # 查看前一次提交的指定文件内容 > welcome.txt
  4. $ git cat-file -p origin/HEAD^:<filename>
  5. # 当然,也可以采用reflog形式

问题4:如何忽略某文件?如何只让其本地生效?

.git/info/exclude中配置

Git作者Linus Torvalds,其是一款分布式版本控制系统。

  • CVS:集中式版本控制系统。CVS采用客户端/服务器架构设计,版本库位于服务器端,实际上就是一个RCS文件容器。每一个RCS文件以“.v”作为文件名后缀,用于保存对应文件的历次更改历史。RCS文件中只保留一个版本的完全拷贝,其他历次更改仅将差异存储其中,使得存储变得更加高效。每个文件都拥有各自独立的版本号。
  • SVN:集中式版本控制系统。拥有全局版本号,每提交一次,SVN的版本号就会自动加一。利用轻量级拷贝,SVN在不同的名字空间下创建不同的目录实现里程碑和分支的创建,轻松地解决了CVS中存在的里程碑、分支创建速度慢又不可见的问题。SVN还有一个突破,就是在工作区跟踪目录(.svn目录)下为当前目录中的每一个文件都保存一份冗余的原始拷贝(工作区的根目录和每一个子目录下都有一个.svn目录)。这样做的好处一个是提高了网络的效率,在提交时仅传输变更差异,另外一个好处是部分操作不再需要网络连接,如本地修改的差异比较,以及本地更改的回退等。
  • Git:分布式版本控制系统。每个人都拥有一个完整的版本库。分布式版本控制系统的几乎所有操作包括查看提交日志、提交、创建里程碑和分支、合并分支、回退等都直接在本地完成而不需要网络连接。协同工作模型(版本库间推送、拉回,及补丁文件传送等)让开源项目的参与度有爆发式增长。

git.png

Git对象

git init 会创建一个 .git 目录。这个目录包含了几乎所有 Git 存储和操作的对象。 如若想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。

  1. $ ll .git
  2. -rw-r--r-- 1 ligang staff 6B 11 1 10:13 COMMIT_EDITMSG
  3. -rw-r--r-- 1 ligang staff 212B 10 31 10:02 FETCH_HEAD
  4. -rw-r--r-- 1 ligang staff 23B 8 21 15:23 HEAD
  5. -rw-r--r-- 1 ligang staff 41B 10 31 10:02 ORIG_HEAD
  6. drwxr-xr-x 2 ligang staff 64B 8 15 2017 branches
  7. -rw-r--r-- 1 ligang staff 311B 8 15 2017 config
  8. -rw-r--r-- 1 ligang staff 73B 8 15 2017 description
  9. drwxr-xr-x 31 ligang staff 992B 5 10 14:44 hooks
  10. -rw-r--r-- 1 ligang staff 6.9M 11 1 10:13 index
  11. drwxr-xr-x 4 ligang staff 128B 5 9 16:11 info
  12. drwxr-xr-x 4 ligang staff 128B 5 9 16:11 logs
  13. drwxr-xr-x 152 ligang staff 4.8K 11 1 10:13 objects
  14. -rw-r--r-- 1 ligang staff 166B 5 9 16:11 packed-refs
  15. drwxr-xr-x 6 ligang staff 192B 7 3 09:53 refs

-rw-r—r—:第一位用于标识文件类型,d表示目录、l表示连结文件、-表示文件;其他表示系统用户权限rw-rw-(Owner)r—(Group)r—(Other)【r可读、w可写、x可执行】

版本库位于工作区根目录下的.git目录中,且仅此一处,在工作区的子目录下则没有任何其他跟踪文件或目录。Git的这种设计,将版本库放在工作区根目录下,所有的版本控制操作(除了和其他远程版本库之间的互操作)都在本地即可完成。

  1. $ git log -1 --pretty=raw
  2. # 本次提交的唯一标识
  3. commit b93afd2cce7e065dd4e7c33d1c6a4b3a7a75b259
  4. # 本次提交所对应的目录树
  5. tree 1e0c5cb85e1d2b4ff6875a5bbaa9183389ace668
  6. # 本地提交的父提交(上一次提交)
  7. parent 3365948518aad171336a52674cbdf0450679b4dc
  8. author ligang <381510688@qq.com> 1543761152 +0800
  9. committer ligang <381510688@qq.com> 1543761152 +0800
  10. feat(git): git 汇总

研究Git对象ID的一个重量级武器就是git cat-file命令。

查看一下这三个ID的类型:

  1. $ git cat-file -t b93afd2cce7e065dd4e7c33d1c6a4b3a7a75b259
  2. commit
  3. $ git cat-file -t 1e0c5cb85e1d2b4ff6875a5bbaa9183389ace668
  4. tree
  5. $ git cat-file -t 3365948518aad171336a52674cbdf0450679b4dc
  6. commit

查看对象的内容:

  1. $ git cat-file -p <commitID>

Git 有一个底层命令git rev-parse 可以用于显示引用对应的提交ID

  1. $ git rev-parse master
  2. b93afd2cce7e065dd4e7c33d1c6a4b3a7a75b259
  3. $ git rev-parse refs/heads/master
  4. b93afd2cce7e065dd4e7c33d1c6a4b3a7a75b259
  5. $ git rev-parse HEAD
  6. b93afd2cce7e065dd4e7c33d1c6a4b3a7a75b259

可以看出它们都指向同一个对象!

git-objects.png

  • 显示版本库.git 目录所在的位置
  1. $ git rev-parse --git-dir
  2. /Users/ligang/Documents/github/practice/.git
  • 显示工作区根目录
  1. $ git rev-parse --show-toplevel
  2. /Users/ligang/Documents/github/practice

git rev-parse 是Git的一个底层命令,其功能非常丰富(或者说杂乱),很多Git脚本或工具都会用到这条命令。

  • 显示分支 $ git rev-parse --symbolic --branches
  • 显示tags $ git rev-parse --symbolic --tags
  • 显示HEAD对应的SHA1哈希值 $ git rev-parse HEAD

版本库存储

本地(工作区、暂存区、HEAD)

工作区、版本库、暂存区原理图.png

说明
工作区
Git暂存区(stage,或称为index)
HEAD(当前分支,注意非远程
  • HEAD实际是指向master分支的一个“游标”,HEAD全部可以使用master替换
  • objects为Git的对象库,位于 .git/objects 目录下;
  • 工作区 <==> 暂存区| 命令 | 说明 | | —- | —- | | git add ./<filename> | 将工作区变更提交到暂存区 | | git checkout ./<filename>
    git checkout -- <filename> | 暂存区内容覆盖工作区 | | git rm --cached <file> | 直接从暂存区删除文件,工作区则不做出改变 |
  • 暂存区 <==> HEAD| 命令 | 说明 | | —- | —- | | git commit -s -m "" | 将暂存区提交到master分支
    (即master指向的目录树就是原暂存区的目录树) | | git reset HEAD ./<filename> | 使用master指向的目录树替换缓存区 | | git checkout HEAD ./<filename> | 用HEAD指向的master分支内容替换暂存区及工作区的文件 |

重点来了! 如何还原本地工作区某文件

  1. $ git reset HEAD <filename>
  2. $ git checkout <filename>
  3. # 替换命令
  4. $ git checkout HEAD <filename>

本地(stash)

git stash 保存当前工作进度,会分别对暂存区和工作区的状态进行保存。

  1. $ git stash [save [–patch] [-k|–[no-]keep-index] [-q|–quiet] [<message>]]
  • save "message..." 保存工作进度时使用指定说明
  • --patch 会显示工作区和HEAD的差异,通过对差异文件的编辑决定在进度中最终要保存的工作区的内容
  • -k或者--keep-index参数,在保存进度后不会将暂存区重置。缺省会将暂存区和工作区强制重置!

注意: 本地没有被版本控制系统跟踪的文件并不能保存进度,即新创建文件需要 git add

恢复工作进度,可以通过下述命令:

  1. $ git stash apply [–index] [<stash>]
  2. $ git stash drop [<stash>]
  3. # 等价于上述两条命令
  4. $ git stash pop [–index] [<stash>]

远程(remote)

  1. $ cd .git/refs/remotes
  2. $ ll
  3. drwxr-xr-x 5 ligang staff 160B 11 28 10:42 origin
  4. drwxr-xr-x 11 ligang staff 352B 11 27 00:11 upstream
  5. $ ll origin
  6. -rw-r--r-- 1 ligang staff 32B 11 8 09:43 HEAD
  7. -rw-r--r-- 1 ligang staff 41B 11 28 10:42 feature-v2.1
  8. -rw-r--r-- 1 ligang staff 41B 11 28 10:42 master
  9. $ ll upstream
  10. -rw-r--r-- 1 ligang staff 41B 11 27 00:11 develop
  11. -rw-r--r-- 1 ligang staff 41B 11 27 00:11 master
  12. -rw-r--r-- 1 ligang staff 41B 11 27 00:11 themes
  13. $ cat origin/develop
  14. eeaa2013d901bda74eaa9fe102abe1e474b7a5d6
  15. $ git ls-tree eeaa2013d901bda74eaa9fe102abe1e474b7a5d6

Git 这样的设计是非常巧妙的,在向远程版本库执行获取操作时,不是把远程版本库的分支原封不动地复制到本地版本库的分支中,而是复制到其命名空间中。如在克隆一个版本库时,会将远程分支都复制到目录 .git/refs/remotes/origin/ 下。这样向不同的远程版本库执行获取操作,因为远程分支相互隔离,所以就避免了相互的覆盖。

  • 添加新远程版本库
  1. $ git remote add remote-name git@x.x.x.x:project-namespace/project-name.git
  2. $ git remote -v

版本库操作

日志—log

显示提交历史!当不使用任何参数调用,相当于使用了缺省的参数HEAD,即显示当前HEAD能够访问到的所有历史提交。可以指定某个远程或者分支进行查看:

  1. $ git log upstream/master
参数 说明
--oneline 最精简的日志输出
--graph 分支图显示
-<n> 显示最近的几条日志
--stat 显示每次提交的变更概要
--all 显示所有分支的历史记录

提交—commit

commit 分为两种:一种是常规的 commit,也就是使用 git commit 提交的 commit;另一种是 merge commit,在使用 git merge 合并两个分支之后,你将会得到一个新的 merge commit。

merge commit 和普通 commit 的不同之处在于 merge commit 包含两个 parent commit,代表该 merge commit 是从哪两个 commit 合并过来的。

  1. # cdc4a1为merge生成的commitID
  2. $ git show cdc4a1
  3. commit cdc4a1c252171c24b7c98b92a6fb94010637de44 (HEAD -> hotfix-inspection, origin/hotfix-inspection)
  4. Merge: 81cf95f5 41cc17e6

使用命令git describe 将提交显示为一个易记的名称。

  • 这个易记的名称来自于建立在该提交上的里程碑;
  • 如果提交没有对应的里程碑,但是在其祖先版本上建有里程碑,则使用类似<tag>-<num>-g<commit>(“基础版本号” - 距离“基础版本”的数字 - 该提交的SHA1哈希值缩写)的格式显示;
  • 如果提交本身没有包含里程碑,其祖先版本上也没有里程碑,可以通过传递--always参数显示精简提交ID,否则出错。
  1. $ git describe
  2. v2.0.0-143-gcffed5c2

补充:最后一次的提交信息,会存储在.git/COMMIT_EDITMSG 中,这对于对提交信息格式校验很有帮助,具体可以查看:Git提交信息规范化

  1. $ cat .git/COMMIT_EDITMSG
  2. feat(git): git总结

可以在commit命令后加参数-s,为在提交说明的最后添加“Signed-off-by:”签名。

  1. $ git commit -s -m "提交说明"

没有对工作区的文件进行任何修改,Git默认不会执行提交,参数--allow-empty 允许执行空白提交。

  1. # 重新修改最新的提交,改正作者和提交者的错误信息
  2. $ git commit --amend --allow-empty --reset-author

提交空文件夹:默认情况下,Git不能对空文件夹进行提交。所以,我们可以在相关文件夹下创建一个文件.gitkeep来进行占位。

建议:

  • 一次提交只干一件事
  • 每次提交尽量完整,可以使用git stash或其他分支保持当前进度
  • 尽量保持暂存区和HEAD一致(即add后的内容,及时commit)

对比变更

上述提到的工作区、暂存区、HEAD,如何做比较呢?

命令 说明
git diff 工作区和暂存区比较
git diff --cached/--staged 暂存区和HEAD(master)比较
git diff HEAD/master 工作区和HEAD(当前工作分支,注意非远程

获取—fetch or pull

fetch

从另一个存储库下载对象和引用。

在执行git fetch命令的时候,可以通过 --no-tags 参数设置不获取里程碑只获取分支及提交

  1. $ git fetch --no-tags

或在注册远程版本库的时候,使用--no-tags 参数避免将远程版本库的里程碑引入本地版本库

  1. $ git remote add --no-tags

获取的引用名称及其指向的对象名称将写入.git / FETCH_HEAD 中。

  1. $ cat .git/FETCH_HEAD
  2. 7ccd4971011fe8b0df6599efb38d011f25975979 branch 'master' of x.x.x.x:namespace/project-name

git fetch origin 就相当于执行了下面的命令,将远程版本库的所有分支复制为本地的远程分支

  1. $ git fetch origin +refs/heads/*:refs/remotes/origin/*

示例:合并upstream/master提交到本地

获取到的提交会更新到本地跟踪共享版本库(远程)master分支的本地引用.git/refs/remotes/upstream/master

  1. $ git fetch upstream master
  2. # 合并操作
  3. $ git merge upstream/master

pull

git pull 又都做了哪些操作?

  1. $ git config --list
  2. remote.origin.url=git@x.x.x.x:project-namespace/project-name.git
  3. remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
  4. branch.master.remote=origin
  5. branch.master.merge=refs/heads/master
  6. # 另外一个分支
  7. branch.feature-v2.1.remote=origin
  8. branch.feature-v2.1.merge=refs/heads/feature-v2.1
  9. # 另外一个远程
  10. remote.upstream.url=git@y.y.y.y:project-namespace/project-name.git
  11. remote.upstream.fetch=+refs/heads/*:refs/remotes/upstream/*
  1. $ git pull
  • 不带参数执行git pull 相当于执行了git pull <remote>
  • 当前分支未设置 branch.<branchname>.remote,则为origin
  • 获取的远程版本库的URL地址由 remote.<remote>.url给出
  • 如果为注册的远程版本库设置了fetch参数,即通过 remote.<remote>.fetch 配置了一个引用表达式,则使用该引用表达式执行获取操作
  • 合并的分支,如果设定了branch.<branchname>.merge,则对其设定的分支执行合并,否则报错退出

所以,上述fetch示例可以通过下述命令替代

  1. $ git pull upstream master

推送—push

  1. $ git push <remote> <branchname>
  • 不带参数执行git push相当于执行了git push <remote>
  • 当前分支未设置 branch.<branchname>.remote,则为origin
  • 如果为注册的远程版本库设置了push参数,即通过remote.<remote>.push配置了一个引用表达式,则使用该引用表达式执行推送
  • 否则使用“:”作为引用表达式。该表达式的含义是同名分支推送,即对所有在远程版本库有同名分支的本地分支执行推送。
  1. $ git push branch.<branchname>.remote branchname

个人建议: push默认的规则使得git push 可以像 git push origin <currentBranch> 一样的效果,但是个人并不建议这种操作(你要明确你的确是想向origin提交;然后你的本地分支和远程分支名称必须一致<当然强烈建议创建分支本地和远程保持一致!>),以上,还是希望提供完整的命令!

重置—reset or reflog or revert

.git/refs/heads/<branch> 中记录了其分支中对应的最新提交ID,下述为master分支最新提交ID:

  1. $ cat .git/refs/heads/master
  2. e695606fc5e31b2ff9038a48a3d363f4c21a3d86

reset

重置命令git reset 的一个用途就是修改引用(如master)的游标。可以将“游标”指向任意一个存在的提交ID。

  1. $ cat .git/HEAD
  2. ref: refs/heads/master

在执行重置命令的时候没有使用任何参数对所要重置的分支名进行设置,这是因为重置命名实际上所针对的是头指针HEAD。

方式一:不会重置引用
  1. $ git reset [-q] [<commit>] [--] <paths>...

包含了路径<paths>的用法。不会重置引用,更不会改变工作区,而是用指定提交状态(<commit>)下的文件(<paths>)替换掉暂存区中的文件。

  1. $ git reset HEAD/commitID [--] <filePaths>

注意:为了避免路径和引用(或者提交ID)同名而冲突,可以在<paths>前用两个连续的短线作为分隔。

方式二:重置引用
  1. $ git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]

不使用路径<paths>的用法。会重置引用。根据不同的选项,可以对暂存区或者工作区进行重置。会彻底的丢弃历史(git log查看不到提及历史)。

参数 影响范围
—hard 引用指向新的提交ID,替换暂存区和工作区
—soft 只更改引用的指向,不改变暂存区和工作区
—mixed(缺省即为--mixed 更改引用的指向以及重置暂存区,但是不改变工作区
  1. $ git reset --hard HEAD^
  2. $ git reset --hard <commitID>

!!!注意: 使用重置命令很危险,会彻底的丢弃历史。那么还能够通过浏览提交历史的办法找到丢弃的提交ID,再使用重置命令恢复历史么?不可能!因为重置让提交历史也改变了。在一个共享的仓库中这会造成问题的。如果其他人已经有你将要重写的提交,你应当避免使用 reset;如果有任何其他提交在合并之后创建了,那么这个方法也会无效;移动引用实际上会丢失那些改动。

reflog

显示操作历史!

上述reset的第二种方式会丢失历史,如果真的做了上述操作,该如何还原呢?

  1. $ ll .git/logs
  2. -rw-r--r-- 1 ligang staff 111K 11 30 15:02 HEAD
  3. drwxr-xr-x 5 ligang staff 160B 11 29 09:44 refs

通过.git/logs 目录下日志文件记录了分支的变更。默认非裸版本库(带有工作区)都提供分支日志功能,这是因为带有工作区的版本库都有如下设置。

  1. $ git config core.logallrefupdates

Git提供了一个git reflog 命令,对这个文件进行操作。使用show子命令可以显示此文件的内容。

  1. $ git reflog show HEAD
  2. 61f26bdb (HEAD -> master) HEAD@{0}: commit: camile.txt
  3. 622c9bd0 HEAD@{1}: reset: moving to HEAD^
  4. 4e0e3777 HEAD@{2}: commit: camile.txt
  5. 622c9bd0 HEAD@{3}: reset: moving to HEAD^

注意: 使用git reflog 的输出和直接查看日志文件最大的不同在于显示顺序的不同,即最新改变放在了最前面显示,而且只显示每次改变的最终的SHA1哈希值

  1. # 还原某个提交!!
  2. $ git reset --hard HEAD@{2}

git loggit reflog 区别:

  • git log 显示提交历史;
  • git reflog 显示操作历史(包括已经被删除的 commit 记录和 reset 的操作)。

revert

revert 命令十分直观易用,相当于做一次被 revert 的提交的「反操作」并形成一个新的 commit。

上面commit中提到了,commit分为常规commit和merge的commit两种。所以,revert也对应两种方式:

常规commit

使用 git revert <commit id> 即可,git 会生成一个新的 commit,将指定的 commit 内容从当前分支上撤除

merge的commit

revert merge commit 这时需要添加 -m 选项以代表这次 revert 的是一个 merge commit,-m 选项接收的参数是一个数字,数字取值为 1 和 2,也就是 Merge 行里面列出来的第一个还是第二个。

  1. $ git log
  2. commit e6930af365f89b2bdc47d4e190b0e7e1c10bc0b8
  3. Merge: e215da30 fb5baba1
  4. # 具有merge标识,是从e215da30和fb5baba1两个commit合并而来
  5. $ git revert -m 1 e6930af365f89b2bdc47d4e190b0e7e1c10bc0b8

注意: revert后的代码想在合并进来,必须进行反向revert,否则会丢掉相关内容!http://blog.psjay.com/posts/git-revert-merge-commit/

检出—checkout

git checkout 会重写工作区。

方式一:包含了路径<paths>的用法

不会改变HEAD头指针,主要是用于指定版本的文件覆盖工作区中对应的文件

  1. $ git checkout [-q] [<commit>] [--] <paths>...

<commit>是可选项,如果省略则相当于从暂存区(index)进行检出。这和上一章的重置命令大不相同:重置的默认值是 HEAD,而检出的默认值是暂存区。因此重置一般用于重置暂存区(除非使用--hard参数,否则不重置工作区),而检出命令主要是覆盖工作区(如果<commit>不省略,也会替换暂存区中相应的文件)。

方式二:不使用路径<paths>的用法

改变HEAD头指针。之所以后面的参数写作<branch>,是因为只有HEAD切换到一个分支才可以对提交进行跟踪,否则仍然会进入“分离头指针”的状态。在“分离头指针”状态下的提交不能被引用关联到而可能会丢失。

  1. $ git checkout [<branch>]

关于”分离头指针“ HEAD指向的提交将作为新提交的父提交,查看当前HEAD的指向。

  1. $ cat .git/HEAD

分离头指针,指的就是HEAD头指针指向了一个具体的提交ID,而不是一个引用(分支)。

  1. $ git log --pretty=oneline -1
  2. 6f44ded81092794fc186e7869eadb4ca5ef225bb (HEAD -> master) test2
  3. $ git checkout 6f44de
  4. # reflog是HEAD头指针的变迁记录,目前是非master分支
  5. $ git reflog -1
  6. 6f44ded8 (HEAD, master) HEAD@{0}: checkout: moving from master to 6f44de
  7. $ cat .git/HEAD
  8. 6f44ded81092794fc186e7869eadb4ca5ef225bb

方式三:创建和切换到新的分支

新的分支从<start_point>指定的提交开始创建。新分支和我们熟悉的master分支没有什么实质的不同,都是在refs/heads命名空间下的引用。

  1. $ git checkout [-m] [[-b|--orphan] <new_branch>] [<start_point>]

注意,这里不能对远程分支进行checkout,远程分支不是真正意义上的分支,是类似于里程碑一样的引用。如果针对远程分支执行检出命令,会看到大段的错误警告。

分支—branch

  1. # 基于远程分支创建本地分支的过程中
  2. $ git checkout feature-2.2
  3. $ git checkout -b feature-2.2 origin/feature-2.2
  • 删除本地分支shell $ git branch -d/-D develop

  • 删除远程分支
    冒号前面的空格不能少,原理是把一个空分支push到server上,相当于删除该分支。shell $ git push origin :develop

  • 删除本地仓库相对于远程origin不存在的仓库

  1. $ git remote prune origin

merge

会保留修改内容的历史记录。--no-ff(non fast-forward)总会生成一个merge commit,不添加该参数时,只有冲突时才会生成merge commit。

rebase

rebase 是在原有提交的基础上将差异内容反映进去。这个时候可能会有冲突,当出现冲突时,解决冲突后的提交不是使用 commit 命令,而是执行 rebase 命令指定 --continue 选项。若要取消 rebase,指定 --abort 选项。

注意: 变基操作的 实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。

merge还是rebase? 有一种观点认为,仓库的提交历史即是 记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改,这些痕迹就应该被保留下来,让后人能够查阅。从这个角度看来,改变提交历史是一种亵渎。 另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。

cherry-pick

  1. $ git cherry-pick <commitID>

从众多的提交中挑选出一个提交应用在当前的工作分支中。操作过程相当于将该提交导出为补丁文件,然后在当前HEAD上重放形成无论内容还是提交说明都一致的提交。对于从一个分支单独一个或者两个提交而不是合并整个分支的所有变更是非常有用的。可以通过git cherry-pick --abort 取消cherry-pick操作。

里程碑—tag

配置—config

版本库级别的配置文件 ~/.gitconfig,对应.git/config

  1. $ git config -e

全局配置文件--global(用户主目录下) ~/Documents/github/Blog/.git/config

  1. $ git config -e --global

系统级配置文件--system(/etc目录下) /private/etc/gitconfig

  1. $ git config -e --system

git config 使用INI文件格式。

  • 使用命令$ git config <section>.<key>,来读取INI配置文件中某个配置的键值;
  • 使用命令$ git config <section>.<key> <value>,来更改和设置INI配置文件中某个配置的值。

常见问题

忽略文件

文件.gitignore的作用范围是其所处的目录及其子目录。忽略只对未跟踪文件有效,对于已加入版本库的文件无效!

本地独享式忽略文件

  • 一种是针对具体版本库的“独享式”忽略。即在版本库.git目录下的一个文件.git/info/exclude来设置文件忽略。
  • 另外一种是全局的“独享式”忽略。即通过Git的配置变量core.excludesfile指定的一个忽略文件,其设置的忽略对所有文件均有效。

Git忽略语法

  1. *.a # 忽略所有以 .a 为扩展名的文件。
  2. !lib.a # 但是 lib.a 文件或者目录不要忽略,即使前面设置了对 *.a 的忽略。
  3. /TODO # 只忽略根目录下的 TODO 文件,子目录的 TODO 文件不忽略。
  4. build/ # 忽略所有 build/ 目录下的文件。
  5. doc/*.txt # 忽略文件如 doc/notes.txt,但是文件如 doc/server/arch.txt 不被忽略。
  • 可以使用通配符,参见Linux手册:glob(7)。例如:星号(*)代表任意多字符,问号(?)代表一个字符,方括号([abc])代表可选字符范围等。
  • 如果名称的最前面是一个路径分隔符(/),表明要忽略的文件在此目录下,而非子目录的文件。
  • 如果名称的最后面是一个路径分隔符(/),表明要忽略的是整个目录,同名文件不忽略,否则同名的文件和目录都忽略。
  • 通过在名称的最前面添加一个感叹号(!),代表不忽略。

文件名大小写问题

Linux、Solaris、BSD及其他类Unix操作系统使用的是大小写敏感的文件系统,而Windows和Mac OS X(默认安装)的文件系统则是大小写不敏感的文件系统。即用文件名READMEreadme以及Readme(混合大小写)进行访问,在Linux等操作系统上访问的是不同的文件,而在Windows和Mac OS X上则指向同一个文件。换句话说,两个不同文件READMEreadme在Linux等操作系统上可以共存,而在Windows和Mac OS X上,这两个文件只能同时存在一个,另一个会被覆盖,因为在大小写不敏感的操作系统看来,这两个文件是同一个文件。

可以通过设置core.ignorecase 为false,来开启文件名大小写敏感!

  1. $ git config core.ignorecase false

换行符问题

文本文件的每一行结尾用一个或者两个特殊的ASCII字符进行标识,这个标识就是换行符。

  • LF,“\\n”表示:用于Multics、Unix、类Unix(如GNU/Linux、AIX、Xenix、Mac OS X、FreeBSD等)、BeOS、Amiga、RISC OS等操作系统中
  • CR,“\\r”表示:用于DEC TOPS-10、RT-11和其他早期的非Unix,以及CP/M、MP/M、DOS(MS-DOS、PC-DOS等)、Atari TOS、OS/2、Microsoft Windows、Symbian OS、Palm OS等系统中
  • CRLF,“\\r\\n”表示:用于Commodore 8位机、TRS-80、苹果II家族、Mac OS 9及更早版本
  1. $ git config --global core.autocrlf true/input

http://www.worldhello.net/gotgit/08-git-misc/040-eol.html

延伸阅读

Git的功能极其强大,我们可以使用Git做更多有意义的事情。