Git 一个先进的分布式版本控制系统
分布式版本控制系统与集中式版本控制系统最大的区别就是它没有“中央服务器”;每个人的电脑上都是一个完整的版本库,同事之间只需要将自己的修改推送给对方,就可以互相看到对方的修改了。
不过在实际使用 Git 的时候,其实很少在两个人之间的电脑互推。通常也有一台充当“中央服务器”的电脑,这个服务器仅仅是用来方便同步大家的修改。
安装 Git
因为 Xcode 自带的有 Git 所以就不在介绍了。
创建版本库(repository)
1
git init # 创建一个空的仓库
init
后用ls -ah
命令可以看到当前目录下多了一个.git
隐藏目录。这个目录是 Git 用来跟踪管理版本库的,不要随意手动修改这个目录里的任何文件。
把文件添加到版本库(add)
1
git add . # 把当前目录下的所有文件添加到仓库
add 只是把文件修改添加到暂存区
把文件提交到仓库(commit)
1
git commit -m "日志消息"
commit 是把暂存区里的修改提交到当前分支
查看版本库当前的状态(status)
1
git status
查看文件修改(diff)
1
2
git diff README.md
git diff HEAD -- README.md # 查看README.md 文件在工作区和版本库里面最新版本的区别
查看历史记录(log)
1
2
git log # 显示从最近到最远的提交日志
git log --pretty=oneline # 只显示版本号和修改内容
回退版本(reset)
1
git reset --hard HEAD^ # 回退到上一个版本
当你回退到了某个版本后,又想恢复到新版本怎么办?想回到新版本必须找到新版本的版本号。我们可以用 git reflog
来查询。
1
2
git reflog # 查询每次提交的 id
git reset --hard 3637964 # 3637964 是最新版本的版本号
撤销暂存区的修改(reset)
1
git reset HEAD README.md # 把暂存区的修改撤销掉,重新放回工作区
撤销工作区的修改(checkout)
1
git checkout -- README.md # 把 README.md 文件在“工作区”的修改全部撤销
--
一定要有,没有--
就变成了“切换到另一个分支”的命令
删除文件(rm)
1
2
git rm README.md # 删除暂存区、工作区和分支上的文件
git rm --cached README.md # 删除暂存区和分支上的文件;工作区里的文件保留
远程仓库1 - 配置 SSH Key:以 GitHub 为例
由于本地 Git 仓库和 GitHub 仓库之间的传输是通过 SSH 加密的,所以,需要一点设置:
第1步:创建 SSH Key。在用户主目录下
Finder前往~/.ssh
,看看有没有.ssh
目录如果有,再看看这个目录下有没有 id_rsa 和 id_rsa.pub 这两个文件,如果已经有了,可直接跳到第2步。如果没有就用以下命令创建1
ssh-keygen -t rsa -C "youremail@example.com" # 把邮件换成你自己的邮件地址,然后一路回车,无需设置密码
.ssh
里有id_rsa
和id_rsa.pub
两个文件,id_rsa
是私钥不能泄露出去,id_rsa.pub
是公钥,可以放心地告诉任何人。第2步:登陆 GitHub,打开 “Settings”,“SSH and GPG keys” 页面:然后,点 “New SSH Key”,填上任意 Title,在 Key 文本框里粘贴 id_rsa.pub 文件的内容:然后点击 “Add SSH Key”,你就应该看到已经添加的 Key。
远程仓库2 - 关联和推送:以 GitHub 为例
情景:我们先有本地 Git 仓库,后有远程仓库,需要将本地和远程仓库关联。
第1步:关联远程仓库(假如你在 GitHub 上已经新建了一个仓库 LearnGit)。
1
git remote add origin git@github.com:liam-i/LearnGit.git
把上面的
liam-i
替换成你自己的 GitHub 账户名。第2步:把本地库的所有内容推送到远程库。
1
git push -u origin main # 把当前分支 main 推送到远程
-u
不但会把本地的 main 推送到远程 main 分支,还会把本地的 main 和远程的 main 关联起来。第一次推时需要,以后再推/拉时就可以简化了。第一次推送有可能报以下错误:
1 2 3 4 5 6 7 8 9 10
LiamMacBook-Pro:LearnGit liam$ git push -u origin main To github.com:liam-i/LearnGit.git ! [rejected] main -> main (fetch first) error: failed to push some refs to 'git@github.com:liam-i/LearnGit.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
造成这个错误的原因是 Git 仓库中已经有一部分代码,所以它不允许你直接把你的代码推送上去。于是我们有2个选择方式:
强推,用你本地的代码替代 Git 仓库内的内容:
1
git push -f origin main # 注:会覆盖远程仓库里的内容
把服务器上的内容合并到本地在推送:
git pull
可是这时又报错了。1 2
LiamMacBook-Pro:LearnGit liam$ git pull fatal: refusing to merge unrelated histories
这时我们需要用以下两个命令配置
.git/config
文件:1 2
git config branch.main.remote origin # 当本地是 main 分支, 默认的 remote 就是 origin git config branch.main.merge refs/heads/main # 当本地是 main 分支使用 git pull 时,没有指定 remot 分支,那么 git 就会采用默认的 origin 来 merge 在 main 分支上所有的改变
这时我们已经可以正常的
pull
和push
了。完整操作如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14
LiamMacBook-Pro:LearnGit liam$ git config branch.main.remote origin LiamMacBook-Pro:LearnGit liam$ git config branch.main.merge refs/heads/main LiamMacBook-Pro:LearnGit liam$ git pull Already up-to-date. LiamMacBook-Pro:LearnGit liam$ git push origin main Counting objects: 26, done. Delta compression using up to 8 threads. Compressing objects: 100% (21/21), done. Writing objects: 100% (26/26), 12.68 KiB | 0 bytes/s, done. Total 26 (delta 1), reused 0 (delta 0) remote: Resolving deltas: 100% (1/1), done. To github.com:liam-i/LearnGit.git f01930f..0892bfe main -> main
远程仓库3-克隆:以 GitHub 为例
情景:我们需要从零开发,那么最好的方式是先创建远程仓库,然后克隆。
第1步:登录GitHub新建一个仓库 LearnGit。
第2步:克隆到本地:
1
git clone git@github.com:liam-i/LearnGit.git
分支管理1 - 创建/切换分支(branch/checkout)
1
2
3
git branch MyBranch # 创建分支 MyBranch
git checkout MyBranch # 切换到分支 MyBranch
git checkout -b MyBranch # 创建并切换到分支 MyBranch
分支管理2 - 删除/查看分支(branch)
1
2
git branch -d MyBranch # 删除 MyBranch 分支
git branch # 查看当前分支
分支管理3 - 合并分支/解决冲突(merge)
1
2
3
4
5
git merge MyBranch # 将 MyBranch 合并到当前分支
# 注:如果可能 Git 会用 Fast forward 模式,这种模式下删除分支后,会丢掉分支信息。
git merge --no-ff -m "merge with no-ff" MyBranch # 将 MyBranch 合并到当前分支
# 注:`--no-ff` 强制禁用 Fast forward 模式,Git 会在 merge 时生成一个新的 commit,这样,从分支历史上就可以看出分支信息。
1
2
3
4
5
6
LiamMacBook-Pro:LearnGit liam$ git checkout main # 切回 main 分支
LiamMacBook-Pro:LearnGit liam$ git merge MyBranch # 将 MyBranch 合并到 main
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
合并分支时 README.md 文件发生冲突,必须手动解决冲突后再 commit。
用 git status
也可以查看冲突的文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
LiamMacBook-Pro:LearnGit liam$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.md
no changes added to commit (use "git add" and/or "git commit -a")
打开 README.md 文件查看内容:
1
2
3
4
5
6
7
8
9
10
11
class ScanViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
<<<<<<< HEAD
print("main -> viewDidLoad")
=======
print("MyBranch -> viewDidLoad")
>>>>>>> MyBranch
}
}
git 用 <<<<<<<
,=======
和 >>>>>>>
标记出不同分支的内容,我们修改如下后保存:
1
2
3
4
5
6
7
class ScanViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print("main -> viewDidLoad")
}
}
再提交
1
2
git add README.md # 添加到暂存区
git commit -m "conflict fixed" # 提交到 main 分支
分支管理4 - 查看分支的合并情况(log)
1
2
git log --graph # 查看分支合并图
git log --graph --pretty=oneline --abbrev-commit # 查看本次分支的合并情况
1
2
3
4
5
6
7
8
LiamMacBook-Pro:LearnGit liam$ git log --graph --pretty=oneline --abbrev-commit
* 6deee3e Fix conflicts
|\
| * de23acd MyBranch
* | 76c6451 main
|/
* 873c772 ..
分支管理5 - 创建 BUG 分支(stash)
情景:当你接到一个修复紧急 bug 的任务时,需要创建一个分支来工作。但是,你当前正在 MyBranch 分支上进行工作,而且工作只进行到一半还没法提交。 这时,我们就需要用 stash
功能,将当前工作现场“储藏”起来,等 bug 解决了再恢复现场继续工作。
1
git stash # 把当前工作现场“储藏”起来
stash
后,用git status
查看工作区是干净的,因此可以创建分支来修复 bug 了。
- 创建 bug 分支 -> 解决 bug -> 解决完毕 -> 合并到主分支 -> 删除 bug 分支
当 bug 解决完毕后需要切回到原来的 MyBranch 分支继续工作:
1
2
3
4
5
6
7
git checkout MyBranch # 切回到 MyBranch 分支
git stash list # 查看被储藏的工作区
git stash apply stash@{0} # 恢复指定的工作区
git stash drop stash@{0} # 删除指定的 stash 内的记录
or
git stash pop stash@{0} # 恢复的同时把 stash 内的指定记录也删了
分支管理6 - 推送分支(push)
1
2
3
4
5
git remote # 查看远程仓库的信息
git remote -v # 查看远程仓库的更详细信息
git push origin main # 将本地 main 分支推送到远程库对应的远程分支
git push origin MyBranch # 将本地 MyBranch 分支推送到远程库对应的远程分支
分支管理7 - 获取分支(pull)
pull 之前必须要先克隆:
1
git clone git@github.com:liam-i/LearnGit.git
此时你只能看到本地的 main 分支,如果你想看到其他分支必须创建远程 origin 的 MyBranch 到本地:
1
git checkout -b MyBranch origin/MyBranch
抓取分支
1
git pull # 把最新的提交从 origin/dev 抓下来
注:第一次抓取的时候有可能会报以下错误:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
remote: Counting objects: 1, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 1 (delta 0), reused 1 (delta 0)
Unpacking objects: 100% (1/1), done.
From github.com:liam-i/LearnGit
fc338631..201bea8 MyBranch -> origin/MyBranch
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details
git pull <remote> <branch>
If you wish to set tracking information for this branch you can do so with:
git branch --set-upstream dev origin/<branch>
这是因为本地 MyBranch 分支与远程 origin/MyBranch 分支没有关联,根据提示设置关联:
1
git branch --set-upstream MyBranch origin/MyBranch # 设置 MyBranch 和 origin/MyBranch 的链接
标签管理1 - 创建标签(tag)
1
2
git tag v1.0 # 打一个标签 v1.0
git tag # 查看所有标签
对历史提交的版本打一个标签
1
2
3
git log --pretty=oneline --abbrev-commit # 找到历史提交的 commit id
git tag v1.0 6279237 # 对 commit id 为 6279237 的版本打标签
git tag # 查看所有标签
1
2
git tag -a v1.0 -m "version 1.0 released" # 创建带有说明的标签
git show v1.0 # 查看标签 v1.0 的信息
-a
指定标签名,-m
指定说明文字
标签管理2 - 操作标签(tag)
1
2
git push origin v1.0 # 将标签 v1.0 推送到远程
git push origin --tags # 将全部尚未推送的标签推送到远程
1
2
git tag -d v1.0 # 删除本地标签 v1.0
git push origin :refs/tags/v1.0 # 删除远程标签 v1.0
Git 的常用配置
1
git config --global color.ui true # 让 Git 在适当的地方显示不同的颜色
配置别名:
1
2
3
4
git config --global alias.co checkout # 为 checkout 配置别名 co
git config --global alias.ci commit # 为 commit 配置别名 ci
git config --global alias.br branch # 为 branch 配置别名 br
git config --global alias.st status # 为 status 配置别名 st
忽略特殊文件(.gitignore)
在 Git 工作区的根目录下创建一个 .gitignore
文件,然后把要忽略的文件名填进去,Git 就会自动忽略这些文件。
GitHub 也为我们准备了各种配置文件:https://github.com/github/gitignore。