一文搞定Git

分布式版本控制系统

分布式版本控制系统 Git 客户端并不只提取最新版本的文件快照,而是把原始的代码仓库完整地镜像下来。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提取操作,实际上都是一次对代码仓库的完整备份。

Git与其他版本控制区别

  1. 直接快照,而非比较差异
    • Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。其他系统(CVS 等),每次记录有哪些文件做了更新,以及都更新了哪些行的什么内容。
    • Git 并不保存这些前后变化的差异数据。实际上 git 更像是把变化的文件做快照后,记录在一个微型系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件做一快照,然后保存一个指向这快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照做一连接。
  2. 有操作都可本地执行
    • 在 Git 中的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用 CVCS 的话,差不多所有操作都需要连接网络。因为 Git 在本地磁盘上就保存着所有有关当前项目的历史更新,所以处理起来速度飞快。
  3. 持数据完整性
    • 在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。换句话说,不可能在你修改了文件或目录之后,Git 一无所知。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。

Git的三种状态

状态 描述
已提交 该文件以及被安全的保存在本地数据库中了。
已修改 修改了某个文件,但还没有提交保存。
已暂存 把已修改的文件放在下次提交时要保存的清单中。

初次配置

配置文件

  • /etc/gitconfig
    • 系统中对所有用户都普遍适用的配置。
    • 若使用 git config 时用 --system 选项,读写的就是这个文件。
  • ~/.gitconfig
    • 用户目录下的配置文件只适用于该用户。
    • 若使用 git config 时用 --global 选项,读写的就是这个文件。
  • .git/config
    • 当前项目的 git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。
    • 每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig中的同名变量。

配置用户信息

$ git config --global user.name zhangwuliang $ git config --global user.email zhangwuliang@example.com

配置文本编辑器

$ git config --global core.editor emacs

配置差异化分析工具

$ git config --global merge.tool vimdiff

使用vimdiff显示git diff

$ git config --global diff.tool vimdiff $ git config --global difftool.prompt false $ git config --global alias.d difftool $git diff

查看所有配置信息

$ git config --list

查看特定的配置

$ git config user.name

帮助

$ git help <verb> $ git <verb> --help $ man git-<verb>

从当前目录初始化一个Git仓库

$ git init $ git add *.c $ git add README $ git commit -m 'initial project version'

忽略某些文件

一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,像是日志或者编译过程中创建的等等。我们可以创建一个名为 .gitignore 的文件,列出要忽略的
文件模式。

$ cat .gitignore *.[oa] #忽略所有以 .o 或 .a 结尾的文件 *~ #忽略所有以波浪符( ~)结尾的文件

gitignore格式规范

  • 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。

  • 可以使用标准的 glob 模式匹配。

    • 星号( *)匹配零个或多个任意字符;
    • [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
    • 问号( ?)只匹配一个任意字符;
    • 如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。
  • 匹配模式最后跟反斜杠( /)说明要忽略的是目录。

  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号( !)取回。

  • 此为注释 – 将被 Git 忽略

    *.a # 忽略所有 .a 结尾的文件 !lib.a # 但 lib.a 除外 /TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO build/ # 忽略 build/ 目录下的所有文件 doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt

文件比较

查看尚未暂存的文件修改:

$ git diff #查看尚未暂存的文件修改了哪些部分 $ git checkout filename #放弃尚未暂存的修改

查看已暂存的文件和上次提交的差异:

$ git diff --cached

移除文件

要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changed but not updated” 部分(也就是_未暂存_清单)看到。如果删除之前修改过并且已经放到暂存区的话,则必须使用强制删除选项 -f。

另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a 编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore 文件中补上,用 --cached 选项即可。

git rm 1.txt git rm -f 1.txt git rm --cached 1.txt

也可使用 glob 模式:

git rm log/\*.log git rm \*.swap #通配符必须使用\,

移动文件

git mv file_from file_to

撤销操作

修改最后一次提交

有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend 选项重新提交:

$ git commit --amend

此命令将使用当前的暂存区域快照提交。如果刚才提交完没有作任何改动,直接运行此命令的话,相当于有机会重新编辑提交说明,而所提交的文件快照和之前的一样。

启动文本编辑器后,会看到上次提交时的说明,编辑它确认没问题后保存退出,就会使用新的提交说明覆盖刚才失误的提交。

如果刚才提交时忘了暂存某些修改,可以先补上暂存操作,然后再运行 --amend 提交。

$ git commit -m 'initial commit' $ git add forgotten_file $ git commit --amend

上面的三条命令最终得到一个提交,第二个提交命令修正了第一个的提交内容。

取消已暂存的文件

git reset filename

取消对文件的修改

git checkout -- filename

远程仓库的使用

查看当前的远程库

git remote git remote -v #显示对应的远程地址

添加远程仓库

git remote add [shortname] [url] git remote add pb git://github.com/zhangwuliang/xx.git #现在pb就代表对应仓库的地址了

从远程仓库抓取数据

git fetch [remote-name]

此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。

如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下。所以, git fetch origin 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新)。

有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。

如果设置了某个分支用于跟踪某个远端仓库的分支,可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用,既快且好。

实际上,默认情况下 git clone 命令本质上就是自动创建了本地的 master 分支用于跟踪远程仓库中的 master 分支(假设远程仓库确实有 master 分支)。所以一般我们运行 git pull,目的都是要从原始克隆的远端仓库中抓取数据后,合并到工作目录中当前分支。

推送数据到远程仓库

git push [remote-name] [branch-name]

查看远程仓库信息

git remote show [remote-name]

远程仓库的删除和重命名

git remote rename pb paul #把pb改成paul git remote rm paul #删除远程仓库paul

打标签

显示已有标签

git tag git tag -l 'v1.4.2.*' #列出符合条件的标签

新建标签

Git 使用的标签有两种类型:轻量级的(lightweight)和含附注的(annotated)。轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。

而含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以෽标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。

一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。

git tag -a v1.4 -m 'my version 1.4' #新建含附注的标签 git show v1.4 #显示标签详细信息 git tag v1.4-lw #轻量级标签 git tag -a v1.2 9fceb02 #针对某次提交打标签

分享标签

默认情况下, git push 并不会把标签传送到远端服务器上,只有通过显式命令才能分享标签到远端仓库。

git push origin v1.5 git push origin --tags #一次推送所有(本地新增的)标签上去

Git常用别名

$ git config --global alias.co checkout $ git config --global alias.br branch $ git config --global alias.ci commit $ git config --global alias.st status $ git config --global alias.unstage 'reset HEAD --'