一. 起步
1. Mac上安装 GIt
安装的方式有两种:
- 安装Xcode,Xcode自带Git,不建议。
- 官网下载,建议。
安装结束后,查看Git的版本:
1 | git --version |
2. Git的配置
Git 自带一个 git config
的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位置:
/etc/gitconfig
文件: 包含系统上每一个用户及他们仓库的通用配置。~/.gitconfig
或~/.config/git/config
文件:只针对当前用户。.git/config
文件:当前使用仓库的 Git 目录中的config
文件,针对该仓库。
设置用户信息:
1 | //这个东西很重要,多人协作的时候能查看到这些信息 |
查看配置信息:
1 | git config --list |
3. 获取帮助
1 | git help -a |
二. Git基础
1. 获取Git仓库
① 在已有的目录中初始化仓库
1 | git init |
1> 该命令将创建一个名为 .git
的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的骨干。 但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪。
2>.git
的子目录默认看不到,使用Command + Shift + .
查看隐藏的文件。
② 克隆已有的仓库
1 | //克隆path路径的仓库到本地,仓库名和拉取仓库的名相同 |
2. Git基础操作
① 文件状态分类
请记住,你工作目录下的每一个文件都不外乎这两种状态:已跟踪(Untracked
)或未跟踪(Tracked
)。
已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改(Unmodified
),已修改(Modified
)或已放入暂存区(Staged
)。
初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态。
② 查看文件状态
1> 如果在克隆仓库后立即使用git status
命令,会看到类似这样的输出:
1 | On branch master |
这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。 此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。 最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。 现在,分支名是 “master”,这是默认的分支名。
2> 如果在刚刚git init
的仓库使用此命令,会看到类似这样的输出:
1 | On branch master |
这和上面的说明一致,只不过我们没有提交东西。
3> 现在,让我们在项目下创建一个新的 README 文件。 如果之前并不存在这个文件,使用 git status
命令,你将看到一个新的未跟踪文件:
1 | On branch master |
在状态报告中可以看到新建的 README 文件出现在 Untracked files
下面。 未跟踪的文件意味着 Git 在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围。
4> 我们使用git add
跟踪一个文件:
1 | On branch master |
此时我们看到看到 README 文件已被跟踪,并处于暂存状态。只要在 Changes to be committed
这行下面的,就说明是已暂存状态。 如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。
git add
命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。git add *
是追踪所有文件的意思。
我们尝试创建一个CONTRIBUTING.txt
文件,并继续使用git add
跟踪一个文件:
1 | On branch master |
1 | On branch master |
5> 现在我们来修改一个已被跟踪的文件。 如果你修改了一个名为 CONTRIBUTING.txt 的已被跟踪的文件,然后运行
git status` 命令,会看到下面内容:
1 | On branch master |
文件 CONTRIBUTING.txt
出现在 Changes not staged for commit
这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。 要暂存这次更新,需要运行 git add
命令。
git add
命令:
- 开始跟踪新文件
- 把已跟踪的文件放到暂存区
- 合并时把有冲突的文件标记为已解决状态
所以将这个命令理解为“添加内容到下一次提交中”。
此时有一个疑问:CONTRIBUTING.txt` 文件同时出现在暂存区和非暂存区,这是允许的吗?
实际上 Git 只不过暂存了你运行 git add
命令时的版本, 如果你现在提交,CONTRIBUTING.txt
的版本是你最后一次运行 git add
命令时的那个版本,而不是你运行 git commit
时,在工作目录中的当前版本。
所以,运行了 git add
之后又作了修订的文件,需要重新运行git add
把最新版本重新暂存起来。
现在,解决困惑后,我们运行 git add
命令把已跟踪的文件CONTRIBUTING.txt
放到暂存区:
1 | On branch master |
6> 实际操作截图
③ 忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore
的文件,列出要忽略的文件模式。
注意,.gitignore
文件需要被纳入 Git 的管理,也就是处于追踪状态才能生效。(创建以.
开头的文件会被隐藏的,需要打开隐藏文件)
我们来看一个实际的例子:
1 | //查看忽略的文件 |
1 | //查看状态 |
理论上来说,log.c文件应该会出现在Untracked files下面,但是由于被.gitignore
文件列出,所以被忽略了。
文件 .gitignore
的格式规范如下:
- 所有空行或者以
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式可以以(
/
)开头防止递归。 - 匹配模式可以以(
/
)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore
文件列表,你可以在 https://github.com/github/gitignore 找到它。
④ 查看已暂存和未暂存的修改
1> 首先我们简单的修改CONTRIBUTING.txt
文件,原本它是一个空的,在里面添加hello
。使用git status
查看得到以下内容:
1 | On branch master |
2> 使用git diff
查看已暂存和未暂存的修改
1 | diff --git a/CONTRIBUTING.txt b/CONTRIBUTING.txt |
3> 使用p4merge作为可视化工具,下载地址:perforce-visual-merge-and-diff-tools。下载到P4V.dmg
文件后,双击打开,拖动P4Merge
到Application
文件夹上就可以完成安装了。
配置p4merge,输入以下命令即可:
1 | git config --global diff.tool p4merge |
4> 使用可视化工具查看已暂存和未暂存的修改
1 | git difftool |
5> 查看已经暂存起来的变化
1 | git diff --cached |
⑤ 提交更新
提交更新的时候请一定要确认还有什么修改过的或新建的文件还没有 git add
过,否则提交的时候不会记录这些还没暂存起来的变化。
所以,每次准备提交前,先用 git status
看下,是不是都已暂存起来了, 然后再运行提交命令 git commit
。
如果你想一次性提交并写入commit的信息可以使用下面的方式。
1 | git commit -m "commit信息" |
如果我们在之前的基础上提交,会出现下面的信息:
1 | [master (root-commit) 1f823c7] 测试 |
当前是在哪个分支(master
)提交的,本次提交的完整 SHA-1 校验和是什么(1f823c7
),本次提交commit的信息是什么(测试
),以及在本次提交中,有多少文件修订过,多少行添加和删改过。
还有一种直接提交的方式:
1 | git commit -a |
Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤
⑥ 移除文件
1> 从已跟踪文件清单中移除并连带从工作目录中删除指定的文件
1 | git rm [path] |
删除之后,下一次提交时,该文件就不再纳入版本管理了。
如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f
(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。
2> 从已跟踪文件清单中移除但是仍然希望保留在当前工作目录中
1 | git rm --cached [path] |
⑦ 重命名文件
1 | git mv file_from file_to |
⑧ 查看提交历史
1> 我们可以使用下面的命令回顾提交历史:
1 | git log |
按提交时间列出所有的更新,最近的更新排在最上面。
对我们之前的Test1仓库进行回顾,内容如下:
1 | commit 1f823c7ab4fc9ab44f8480fb18caf8887394f9ad (HEAD -> master) |
2> 显示每次提交的内容差异,可以快速浏览某个搭档提交的 commit 所带来的变化
1 | git log -p |
对我们之前的Test1仓库进行回顾,内容如下:
1 | commit 1f823c7ab4fc9ab44f8480fb18caf8887394f9ad (HEAD -> master) |
3> 查看每次提交的简略的统计信息
1 | git log --stat |
对我们之前的Test1仓库进行回顾,内容如下:
1 | commit 1f823c7ab4fc9ab44f8480fb18caf8887394f9ad (HEAD -> master) |
4> 指定使用不同于默认格式的方式展示提交历史
1 | git log --pretty=format |
对我们之前的Test1仓库进行回顾,内容如下:
1 | $ git log --pretty=oneline |
format,常用的选项如下:
选项 | 说明 |
---|---|
%H |
提交对象(commit)的完整哈希字串 |
%h |
提交对象的简短哈希字串 |
%T |
树对象(tree)的完整哈希字串 |
%t |
树对象的简短哈希字串 |
%P |
父对象(parent)的完整哈希字串 |
%p |
父对象的简短哈希字串 |
%an |
作者(author)的名字 |
%ae |
作者的电子邮件地址 |
%ad |
作者修订日期(可以用 –date= 选项定制格式) |
%ar |
作者修订日期,按多久以前的方式显示 |
%cn |
提交者(committer)的名字 |
%ce |
提交者的电子邮件地址 |
%cd |
提交日期 |
%cr |
提交日期,按多久以前的方式显示 |
%s |
提交说明 |
给出一个小例子:
1 | $ git log --pretty=format:"%h - %an, %ar : %s" |
5> 显示 ASCII 图形表示的分支合并历史
1 | git log --graph |
当 oneline 或 format 与另一个 log
选项 --graph
结合使用时尤其有用。 这个选项添加了一些ASCII字符串来形象地展示你的分支、合并历史:
给出一个小例子:
1 | $ git log --pretty=format:"%h %s" --graph |
⑨ 撤销,回滚操作
1> 重新提交
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend
选项的提交命令尝试重新提交。
1 | git commit --amend |
这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。
文本编辑器启动后,可以看到之前的提交信息。 编辑后保存会覆盖原来的提交信息。
给出一个小例子:
1 | $ git commit -m 'initial commit' |
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。
2> 取消暂存的文件
例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输入了 git add *
暂存了它们两个。 如何只取消暂存两个中的一个呢?
1 | $ git add * |
根据提示使用 git reset HEAD <file>...
来取消暂存。
1 | $ git reset HEAD CONTRIBUTING.md |
3> 撤销对文件的修改
如果你并不想保留对 CONTRIBUTING.md
文件的修改怎么办? 你该如何方便地撤消修改 - 将它还原成上次提交时的样子(或者刚克隆完的样子,或者刚把它放入工作目录时的样子)?
1 | Changes not staged for commit: |
根据提示使用 git checkout -- <file>...
来取消暂存。
1 | $ git checkout -- CONTRIBUTING.md |
4> 删除未跟踪文件
1 | //删除 untracked files |
5> 回滚
参考:Git撤销&回滚操作
我们将已被提交到“远程仓库”的代码还原操作叫做“回滚”
1 | //找到要回滚的commitID |
删除最后一次远程提交
1 | //revert的方式:放弃指定提交的修改,但是会生成一次新的提交,需要填写提交注释,以前的历史记录都在; |
删除某次提交
1 | git rebase -i commit_id |
已在本地进行了多次git commit操作,现在想撤销到其中某次Commit
1 | git reset [--soft|mixed(默认)|hard] [commit|HEAD] |
比如本地仓库commit一次(暂且称之为A):只是提交一个文件(暂且称之为B)
- 如果使用 git reset – soft A回滚版本,A提交没有了,B文件处于未提交状态。
- 如果使用 git reset – mixed A回滚版本,A提交没有了,B文件处于未暂存状态。
- 如果使用 git reset – hard A回滚版本,A提交没有了,B文件不存在了。
具体的使用参考文章—Git reset命令的使用
三. 远程仓库的使用
前提:我在我自己的Gitlab上创建了一个空的项目。
1. 查看远程仓库
1 | //先将远程仓库克隆到本地 |
① git remote
命令会列出你指定的每一个远程服务器的简写。
1 | $ git remote |
如果显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL,使用下面的命令:
1 | $ git remote -v |
2. 添加远程仓库
添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写
1 | git remote add <shortname> <url> |
给出一个例子:
1 | $ git remote add test_add https://gitlab.com/xuxiaoshi/test_add.git |
3. 删除一个远程仓库以及重命名
1 | //删除一个远程仓库 |
四. 打标签
1. 列出Tag
以字母顺序列出标签
1 | git tag |
2. 创建Tag
一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用。
附注标签是存储在 Git 数据库中的一个完整对象。 它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard (GPG)签名与验证。 通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的。
① 轻量标签(lightweight)
1 | git tag <name> |
② 附注标签(annotated)
1 | git tag -a <name> -m '信息' |
-m
选项指定了一条将会存储在标签中的信息。 如果没有为附注标签指定一条信息,Git 会运行编辑器要求你输入信息。
3.查看Tag对应的信息
1 | git show <tag> |
五. Git分支
1. 分支简介
在进行提交操作时,Git 会保存一个提交对象(commit object)。知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并产生的提交对象有多个父对象。
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 它会在每次的提交操作中自动向前移动。
Git 的 “master” 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为
git init
命令默认创建它,并且大多数人都懒得去改动它。
2.分支的创建,删除和合并
① 分支的创建
1> Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。使用下面的命令就可以创建分支了:
1 | git branch 分支名 |
2> Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD
的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD
概念完全不同。 在 Git 中,它是一个指针,指向当前所在的本地分支。
② 分支切换
要切换到一个已存在的分支(实际上就是更改HEAD
指针的指向),你需要使用以下命令:
1 | git checkout 分支名 |
有时候也许你想在创建分支的时候就切换到创建的分支,你可以使用下面的命令:
1 | git checkout -b 分支名 |
③ 分支删除
1 | git branch -d 分支名 |
④ 分支合并
1 | //根据给的分支名更新当前分支的内容 |
1> 如上图所示,如果你想将hotfix分支的内容合并到master分支
1 | //切换到master分支 |
合并的时候会出现Fast-forward
,这是因为当前 master
分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
2> 如下图所示,如果你想将iss53分支的内容合并到master分支
1 | git merge iss53 |
在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master
分支所在提交并不是 iss53
分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4
和 C5
)以及这两个分支的工作祖先(C2
),做一个简单的三方合并。
3> 上面的合并不可能一直很顺利。如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。
你可以在合并冲突后的任意时刻使用 git status
命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件。
如果你想使用图形化工具来解决冲突,你可以运行 git mergetool
,该命令会为你启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突。等你退出合并工具之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决: 你可以再次运行 git status
来确认所有的合并冲突都已被解决。
如果你对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit
来完成合并提交。
下载好后,配置p4merge作为我们解决merge冲突时的可视化工具。参考文章:p4merge合并问题
1 | $ git config --global merge.tool p4mergetool |
如果你正在合并中,你可以使用下面的命令结束合并:
1 | git reset --hard HEAD |
或者
1 | git merge --abort |
⑤ 查看分支
git branch
命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:
1 | $ git branch |
注意 master
分支前的 *
字符:它代表现在检出的那一个分支(也就是说,当前 HEAD
指针所指向的分支)。
1 | git branch -v |
3. 远程分支
前提:拉取Gitlab上的Test仓库到本地
①关于分支
1> 默认本地master分支和origin/master分支绑定在一起
1 | $ git status |
创建本地分支 映射想要映射的远程分支,并切换到创建的分支
1 | git checkout -b 本地分支名x origin/远程分支名x |
关于映射关系可以参考这篇文章:Git branch upstream
2> 切换绑定的远程分支或者创建远程分支
1 | git push --set-upstream <remote-name> <local-branch-name>:<remote-branch-name> |
<remote-name>
:远程git服务器名称,一般设为origin<local-branch-name>
:本地分支名称<remote-branch-name>
:远程分支名称
1 | $ git push --set-upstream origin master:test |
3> 删除远程分支
1 | git push origin --delete 远程分支名 |
例如删除刚才创建的远程分支
1 | $ git push origin --delete test |
4> 查看所有的分支,包括远程分支
1 | $ git branch -a |
② 分支合并
1 | //推送本地分支到远程,之后需要merge request |
git pull时遇到error: cannot lock ref ‘xxx’: ref xxx is at (一个commitID) but expected的解决办法
1 | //大佬的建议 |
4. 变基
之前介绍过,整合分支最容易的方法是 merge
命令。 它会把两个分支的最新快照(C3
和 C4
)以及二者最近的共同祖先(C2
)进行三方合并,合并的结果是生成一个新的快照(并提交)。
其实,还有一种方法:你可以提取在 C4
中引入的补丁和修改,然后在 C3
的基础上应用一次。 在 Git 中,这种操作就叫做 变基。 你可以使用 rebase
命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
在上面这个例子中,运行:
1 | git checkout master |
它的原理是首先找到这两个分支(即当前分支 experiment
、变基操作的目标基底分支 master
)的最近共同祖先 C2
,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3
, 最后以此将之前另存为临时文件的修改依序应用。之后,进行一次快进合并。
六. 命令合集
参考:git命令-远程仓库拉取、本地仓库更新、工作空间提交等等