精通Git
起步
下面带领大家入门
Git 有三种状态
文件可能处于其中之一: 已提交(committed)、已修改(modified)和已暂存(staged) 。
已提交:表示数据已经安全的保存在本地数据库中。
已修改:表示修改了文件,但还没保存到数据库中。
已暂存:表示对一个已修改文件的当前 版本做了标记,使之包含在下次提交的快照中。
由此引入 Git 项目的三个工作区域的概念:Git 仓库、工作目录以及暂存区域。
Git 仓库目录:是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。
工作目录:是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
暂存区域:是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。 有时候也被称作‘索引’,不过一般说法还是叫暂存区域。
基本的 Git 工作流程如下:
- 在工作目录中修改文件。
- 暂存文件,将文件的快照放入暂存区域。
- 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。
如果 Git 目录中保存着的特定版本文件,就属于已提交状态。 如果作了修改并已放入暂存区域,就属于已暂存状态。 如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。
初次运行 Git 前的配置
Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置:
/etc/gitconfig
文件: 包含系统上每一个用户及他们仓库的通用配置。 如果使用带有--system
选项的git config
时,它会从此文件读写配置变量。~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。 可以传递--global
选项让 Git 读写此文件。- 当前使用仓库的 Git 目录中的 config 文件(就是
.git/config
):针对该仓库。
每一个级别覆盖上一级别的配置,所以.git/config
的配置变量会覆盖/etc/gitconfig
中的配置变量。
在 Windows 系统中,Git 会查找 HOME 目录下(一般情况下是 `C:\Users\USER)的
.gitconfig文件。 Git 同样也会寻找
/etc/gitconfig` 文件,但只限于 MSys 的根目录下,即安装 Git 时所选的目标位置。
用户信息
当安装完 Git 应该做的第一件事就是设置你的用户名称与邮件地址。 这样做很重要,因为每一个 Git 的提交都会使用这些信息,并且它会写入到你的每一次提交中,不可更改:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
再次强调,如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事情, Git 都会使用那些信息。 当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运行没有 --global 选项的命令来配置。
如果想要检查你的配置,可以使用git config --list
命令来列出所有 Git 当时能找到的配置。
你可以通过输入 git config <key>
: 来检查 Git 的某一项配置 $ git config
user.name
John Doe
获取帮助$ git help <verb>
例如,要想获得 config 命令的手册,执行 $ git help config
Git 基础
获取 Git 仓库
有两种取得 Git 项目仓库的方法。
第一种是在现有项目或目录下导入所有文件到 Git 中;
第二种是从一个服务器
1.打算对现有的项目进行管理,进入该项目目录并输入: $ git init
该命令将创建一个名为 .git
的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。
如果你是在一个已经存在文件的文件夹(而不是空文件夹)中初始化 Git 仓库来进行版本控制的话,你应该开始 跟踪这些文件并提交。
你可通过 git add 命令来实现对指定文件的跟踪,然后执行 git commit
提交:
$ git add *.c
$ git add LICENSE
$ git commit -m 'initial project version'
2.克隆现有的仓库
如果你想获得一份已经存在了的 Git 仓库的拷贝, 用 git clone
命令。 当执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。如果服务器的磁盘坏掉了,可以使用任何一个克隆下来的用户端来重建服务器上的仓库 克隆仓库的命令格式是git clone [url]
git clone https://github.com/libgit2/libgit2
会自动创建一个文件夹 libgit2 保存项目信息。如果想要保存项目到另外一个文件夹中 则在文件的最后加上新的文件名。
git clone https://github.com/libgit2/libgit2 mylibgit
Git 支持多种数据传输协议。 上面的例子使用的是 https://
协议,也可以使用 git://
协议或者使用 SSH
传输协议。
记录每次更新到仓库
对仓库中这些文件做些修改,在完成了一个阶段的目标之后,提交本次更新到仓库。
工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。
已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。
工作目录中除已跟踪文件以外的所有其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态。
编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。 我们逐步将这些修改过的文件放入暂存区,然后提交所有暂存了的修改,如此反复。所以使用 Git 时文件的生命周期如下:
检查当前文件状态
要查看文件处于什么状态,可以用 git status
命令。
如果在克隆仓库后立即使用此命令,会看到类似这样的输出: 工作目录相当干净。所有已跟踪文件在上次提交后都未被更改过。
如果向项目中添加一个新的文件,则在状态报告中可以看到新建的 README 文件出现在Untracked files
下面。
跟踪新文件
使用命令git add
开始跟踪一个文件。 所以,要跟踪 README 文件,运行: git add README
运行 git status 命令,会看到 README 文件已被跟踪,并处于暂存状态:
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
只要在 Changes to be committed
这行下面的,就说明是已暂存状态。 如果此时提交,那么该文件版本将被留存在历史记录中。
Git add
使用文件或文件目录作为参数,如果是文件目录则会将整个文件暂存。
Git add
这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。
暂存已修改文件
修改一个已被跟踪的文件。 如果你修改了一个名为 CONTRIBUTING.md 的文件,然后运 行git status
命令,会看到下面内容:
modified: CONTRIBUTING.md
文件 CONTRIBUTING.md 出现在Changes not staged for commit
这行下面,说明已跟踪文件发生改变,还没有放到暂存区。
要暂存这次更新,需要运行 git add
命令。将这个命令理解为“添加内容到下一次提交中” 而不是 “将一个文件添加到项目中”要更加合适。
现在让我们运行 git add 将"CONTRIBUTING.md"放到暂存区,然后再看看 git status 的输出:
添加的文件 已经存在于Changes not staged for commit
这行下面。现在两个文件都已暂存,下次提交时就会一并记录到仓库。
如果你想要在 CONTRIBUTING.md 里再加条注释, 重新编辑存盘后,再运行 git status 看看:
现在 CONTRIBUTING.md 文件同时出现在暂存区和非暂存区。 实际上 Git 只不过暂存了运行 git add
命令时的版本, 如果现在提交,CONTRIBUTING.md 的版本是你最后一次运行 git add
命令时的那个版本,而不是你运行 git commit
时,在工作目录中的当前版本。 所以,运行了 git add 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来。
状态简览
git status
命令的输出十分详细,但其用语有些繁琐。使用 git status -s
命令或 git status --short
命令,你将得到一种更为紧凑的格式输出。运行git status -s
。
1.新添加的未跟踪文件前面有 ?? 标记。 2.新添加到暂存区中的文件前面有 A 标记。 3.修改过的文件前面有 M 标记。
4.M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。
忽略文件
一般总会有些文件无需纳入 Git 的管理。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。 来看一个实际的例子:
cat .gitignore
*.[oa]
*~
第一行 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。
第二行 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。
文件 .gitignore 的格式规范如下:
• 所有空行或者以 # 开头的行都会被 Git 忽略。 可以使用标准的 glob 模式匹配。(语法)
• 匹配模式可以以(/)开头防止递归。
• 匹配模式可以以(/)结尾指定目录。
• 要忽略指定模式以外的文件或目录,可以在模式前加上感叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。
星号(\*
)匹配零个或多个任意字符;
[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
问号(? )只匹配一个任意字符;
如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配 (比如 [0-9] 表示匹配所有 0 到 9 的数字)。
使用两个星号(*) 表示匹配任意中间目录,比如a/**/z
可以匹 配 a/z
, a/b/z
或 a/b/c/z
等。
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在 https://github.com/github/gitignore 找到它.
查看已暂存和未暂存的修改
如果 git status 命令的输出对于你来说过于模糊,你想知道具体修改了什么地方,可以用 git diff 命令。
下面两个问题: 1.当前做的哪些更新还没有暂存? 2.有哪些更新已经暂存起来准备好了下次提交?
尽管 git status
已经通过在相应栏下列出文件名的方式回答了这个问题,git diff
将通过文件补丁的格式显示具体哪些行发生了改变。
git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。 所以有时候你一下子暂存了所有更新过的文件后,运行 git diff 后却什么也没有,就是这个原因。
如果想看已经暂存起来的改变,需要使用 git diff --staged
如果你喜欢通过图形化的方式,以使用git difftool
命令来用Araxis
,emerge
或vimdiff
等软件输出 diff 分析结果。 使用 git difftool --tool-help
命令来看你的系统支持哪些 Git Diff
插 件。
提交更新
提交更新仅仅会对暂存区已经准备妥当的数据进行提交。也就是使用过了git add
, 然后执行 git commit -m
命令对变更进行提交。
如果想要** 跳过使用暂存区域 ** 我们可以使用git commit -a
参数,Git 会把所有已经更过的文件暂存起来并一并提交。
git 删除文件
git rm
与git rm --cached
当我们需要删除暂存区或分支上的文件,** 同时工作区也不需要这个文件了**, 可以使用
git rm file_path
git commit -m 'delete somefile'
git push
当我们需要删除暂存区或分支上的文件, 但本地又需要使用, 只是不希望这个文件被版本控制, 可以使用
git rm --cached file_path
git commit -m 'delete remote somefile'
git push
查看提交历史
使用的命令是git log
,默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。
一个常用的选项是 -p
,用来显示每次提交的内容差异。 你也可以加上-2
来仅显示最近两次提交: git log -p -2
--stat
选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。
--pretty = xxxx
这个选项有 一些内建的子选项供你使用。 比如用 oneline
将每个提交放在一行显示,查看的提交数很大时非常有用。 另外还有 short
,full
和 fuller
可以用,展示的信息或多或少有些不同。
--graph
显示 ASCII 图形表示的分支合并历史。
--author
显示某个作者的提交记录。
git 查看日志的方式还有很多,这里仅仅写出了一些常用的功能。
撤销操作
注意,有些撤消操作是不可逆的。有些时候撤销可能会导致工作的丢失。
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend
选项的提交命令尝试重新提交: git commit --amend
。
文本编辑器启动后,可以看到之前的提交信息。 编辑后保存会覆盖原来的提交信息。 例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
git commit -m 'initial commit'
git add forgotten_file
git commit --amend
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。
总结:git commit --amend
相当于上次提交错误的信息被覆盖了,gitk
图形化界面上看不到上次提交的信息,git log
上也看不到之前的信息,而 add
后再 commit 相当于重新加了一个信息。相当于打了个补丁?
取消暂存的文件
如果你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输 入了 git add
暂存了它们两个。
如何只取消暂存两个中的一个呢? git status 命令提示了你:
在 “Changes to be committed” 文字正下方,提示使用 git reset HEAD <file>...
来取消暂存。
撤消对文件的修改
如果你并不想保留对CONTRIBUTING.md
文件的修改怎么办? 你该如何方便地撤消修改 ---- 将它还原成上次提交 时的样子?
幸运的是,git status 也告诉了你 应该如何做。 在最后一个例子中,未暂存区域是这样:
git chekcout -- [file]
不过它是一个危险的命令,你对那个文 件做的任何修改都会消失。除非你确实清楚不想要那个文件了,否则不要使用这个命令。
查看远程仓库
如果想查看你已经配置的远程仓库服务器,可以运行 git remote
命令。 它会列出你指定的每一个远程服务器的简写。 你也可以指定选项 -v
,会显示读写远程仓库使用的 Git 保存的简写与其对应的 URL。
添加远程仓库
remote add <shortname> <url>
添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写:
从远程仓库中抓取与拉取
git fetch [remote-name]
这个命令会访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。
这个命令相当于执行代码仓库的同步,会获得之前还没有的数据。
如果你使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 “origin” 为简写。
所以,git fetch origin
会抓取克隆(或上一次抓取)后新推送的所有工作。
必须注意 git fetch
命令会将 数据拉取到你的本地仓库 ---- 它并不会自动合并或修改你当前的工作。当准备好时你必须手动将其合并入你的工作。(fetch 之前要好代码的提交工作,fentch 并不会进行自动合并)。
如果你有一个分支设置为跟踪一个远程分支(也就是当前分支,git branch 可以查看),可以使用 git pull 命令来自动的抓取然后合并远程分支到当前分支。 这对你来说可能是一个更简单或更舒服的工作流程;
运行 git pull 通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。
推送到远程仓库
当你想分享你的项目时,必须将其推送到上游。 这个命令很简单:git push [remote-name] [branch- name]
。
例如: git push origin master
只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。
当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。
你必须先将他们的工作拉取下来并将其合并进你的工作后才能推送。
注意:只有代码先被 pull 了 才能进行 push。
查看远程仓库信息
如果想要查看某一个远程仓库的更多信息,可以使用 git remote show [remote-name]
命令。 这个命令会列出:当你在特定的分支上执行git push/pull
会自动地推送到哪一个远程分支。
远程仓库的移除与重命名
如果想要重命名引用的名字可以运行 git remote rename
去修改一个远程仓库的简写名。 例如,想要将 pb
重命名为 paul
,可以用 git remote rename
这样做:
git remote rename pb paul
注意的是这会修改远程分支名字。 那些过去引用 pb/master
的现在会引用 paul/master
。 如果不再想使用某一个特定的镜像了,又或者某一个贡献者不再贡献了,可以使用git remote rm
打标签
Git 可以给历史中的某一个提交打上标签,以示重要。例如:主要的发布打上(v1.0 等等)。
列出标签,在 Git 中列出已有的标签。 输入 git tag
。 可以使用正则 git tag -l 'v1.8.5*
创建标签
Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated)。
一个轻量标签很像一个不会改变的分支----它只是一个特定提交的引用。
附注标签是存储在 Git 数据库中的一个完整对象。
附注标签: git tag -a v1.4 -m 'my version 1.4'
, -m
选项指定了一条将会存储在标签中的信息。
通过使用 git show 命令可以看到标签信息与对应的提交信息: git show v1.4
轻量标签: 本质上是将提交校验和存储到一个文件中 - 没有保存任何其他信息。 只需要提供标签名字: git tag v1.4-lw
如果在标签上运行 git show,你不会看到额外的标签信息。 命令只会显示出提交信息:git show v1.4-lw
后期打标签,git tag -a v1.2 9fceb02
,之后就可以看到打的标签了。
git push 命令并不会传送标签到远程仓库服务器上。 你可以运行 git push origin [tagname]
。
如果想要一次性推送很多标签,也可以使用带有 --tags 选项的 git push 命令。 这将会把所有不在远程仓库服务器上的标签全部传送到那里。
git push origin --tags
如果想要工作目录与仓库中特定的标签版本完全一样,可以使用git checkout -b [branchname] [tagname]
在特定的标签上创建一个新分支:(从某个 tag 上创建分支)
git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
如果在这之后又进行了一次提交,version2 分支会因为改动向前移动了,那么 version2 分支就会和 v2.0.0 标签稍微有些不同,这时就应该当心了。
git 分支
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的文件快照。
分支创建
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支,你需要使用 git branch 命令:git branch testing
这会在当前所在的提交对象上创建一个指针。git branch
命令仅仅创建 一个新分支,并不会自动切换到新分支中去。
Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。 在 Git 中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)。
git log --oneline --decorate
可以查看当前分支所指向的对象。
分支切换
要切换到一个已存在的分支,你需要使用 git checkout
命令。 我们现在切换到新创建的 testing 分支去:git checkout testing
这样 HEAD 就指向 testing 分支了。
分支切换实质上是 HEAD 指向 checkout 的分支。表示当前要处理的快照。
**分支切换会改变你工作目录中的文件在切换分支时,一定要注意你工作目录里的文件会被改变。 **如果是切换到一个较旧的分支,工作目录会恢复到该分支最后一次提交时的样子。
如果 Git 不能干净利落地完成这个任务, 它将禁止切换分支。(例如当前工作区中是存在已经修改没有提交的文件。)
你可以简单地使用 git log 命令查看分叉历史。运行git log --oneline --decorate --graph --all
,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。
创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单能不快吗?
任何规模的项目都能在瞬间创建新分支。这些高效的特性使得 Git 鼓励开发人员频繁地创建和使用分支。
分支的新建与合并
让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:
- 开发某个网站。
- 为实现某个新的需求,创建一个分支。
- 在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理: - 切换到你的线上分支(production branch)。
- 为这个紧急任务新建一个分支,并在其中修复它。
- 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
- 切换回你最初工作的分支上,继续工作。
现在,要解决你的公司使用的问题追踪系统中的 #53 问题。 ** 想要在当前的分支上新建一个分支并同时切换到那个分支上**,你可以运行一个带有-b 参数的git checkout
命令: git checkout -b iss53
。 注意:这个命令是在当前的分支上新建一个分支
它是下面两条命令的简写:
git branch iss53
git checkout iss53
如果 iss53 问题已经修改完成,分支需要上线,操作步骤是
- 先切换到 master 分支,
- 使用 git merge iss53,将代码合并到 master 分支上。
在合并的时候,你应该注意到了"快进(fast-forward)"这个词。
由于当前 master 分支所指向的提交是你当前提交(有关 iss53 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。
换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。,其实就是将 master 分支指针向前移动到 iss53 分支上。这种情况不会产生冲突。
关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。
然而,你应该先删除 iss53 分支,因为你已经不再需要它了 --master 分支已经指向了同一个位置。
你可以使用带 -d 选项的 git branch 命令来删除分支:git branch -d iss53
(删除其实是删除分支的指针)
现在你可以切换回你正在工作的分支继续你的工作。
分支的合并
git merge
,命令用来进行分支的合并。如果出现多人修改同一处代码的时候,分支会出现自动合并失败的问题,这个时候需要手动的合并。
合并完成后 再次运行git status
来确认所有的合并冲突都已被解决。
冲突确认解决完成后需要使用 git commit
来进行提交代码。 并不需要执行git add
方法进行添加变动。
分支管理
git branch
命令不只可以创建或者删除分支,如果不加任何参数,可以查看当前的分支(也就是 HEAD 指针指向的分支)。
git branch -v
用来查看每一个分支的最后一次提交记录。
为分支添加新的源
cd existing_repo
git remote rename origin old-origin
git remote add origin git@ksogit.kingsoft.net:shiyaoting/duojoy.git
git push -u origin --all
git push -u origin —tags
跟踪分支(创建一个本地分支,并和远程分支关联)
git checkout -b [branch] [remotename]/[branch]
,创建一个本地分支并和远端的分支关联起来。 -b
后边的参数是本地分支的命名, 这个命令用来创建分支和远端分支关联。
git checkout --track origin/serverfix
同样的如果使用--track
命令,会将远程的分支复制到本地。并且分支名称和远端的保持一致。
git checkout -b sf origin/serverfix
此命令将会把远端的分支和本地的 sf
分支关联起来。
git branch -u origin/serverfix
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支.
使用 git branch -vv
来查看本地的分支 跟踪的 远端分支。
删除远程分支
git push origin --delete serverfix
删除远端的分支。基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所 以如果不小心删除掉了,通常还是可以恢复的。