Git基本使用
安装 Git
首先,在终端输入git,看看系统有没有安装 Git:
- 如果已经安装过了,它会向你介绍 git 相关的命令
- 如果没有安装,它会告诉你没有安装
Linux 与 Windows 安装方式
在 Mac 上进行开发,如果安装了 Xcode,Git 也会被自动安装。
安装完成之后,需要最后一步设置:
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
因为 Git 是分布式版本控制系统,所以,每个机器都必须自报家门:名字和 Email 地址。
注意 git config 命令的 --global 参数,用了这个参数,表示你这台机器上所有的 Git 仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和 Email 地址。
创建版本库/仓库/repository
使用 git init 命定把当前目录变成 Git 可以管理的仓库。
$ git init
Initialized empty Git repository in /Users/momo/Desktop/Git测试/.git/
这就已经创建好了,并且当前目录下多了一个 .git 的目录,这个目录是 Git 来跟踪管理版本库的。
把文件添加到版本库
- 使用命令
git add <file>,注意,可以反复多次使用,添加多个文件; - 使用命令
git commit -m <message>,完成。
查看仓库的状态
使用 git status 可以查看工作区的状态
- 新建、删除、修改了哪些文件
- 这些文件的状态:not staged for commit 或者 to be committed
- 还会显示未被追踪的文件 Untracked files
使用 git diff 可以查看文件具体的修改内容
查看提交日志
git log
git log --pretty=oneline
$ git log --pretty=oneline
8a26a5c6c62bb485d0a1dbed9c5d5d7de74a8964 (HEAD -> master) 删除了文件
59c61e6748783590b4d430de11edad75cfc2b637 修改了文件名
fd131b631e8938379b93ee1ba2f675714efdcfae 添加了一个文件
每一行前面的一大串字符是 commit id (版本号)。
版本回退
在 Git 中,用 HEAD 表示当前版本,上一个版本是 HEAD^,上上一个版本是 HEAD^^,如果往上 100 个版本的话,可以写成 HEAD~100。
回退到上一个版本
$ git reset --hard HEAD^
HEAD is now at 59c61e6 修改了文件名
去某一个版本
$ git reset --hard 8a26a
HEAD is now at 8a26a5c 删除了文件
版本号没必要写全,前几位就可以了,Git 会自动去找。
查看版本号
使用 git log 可以查看之前的提交记录以及版本号。
如果已经回退到了之前的某个版本,这时候如果使用 git log 就不能看到当前版本之后的提交的版本号了。
这时如果想返回到之后的版本,可以使用 git reflog 查看命令历史,以确定版本号。
工作区与暂存区
工作区 (Working Directory)
之前使用了 git init 来把当前目录变成了 Git 可以管理的仓库。
当前目录就是一个工作区。
版本库 (Repository)
工作区有一个隐藏目录 .git,这个不算工作区,而是 Git 的版本库。
Git 的版本库里存了跟多东西,其中最重要的就是成为 stage (或者叫做 index) 的暂存区,还有 Git 为我们自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD。
在把工作区的文件添加到 Git 版本库中时需要两步:
git add把文件修改添加到暂存区git commit把暂存区的所有内容提交到当前分支
撤销修改
当你乱改了工作区某个文件的内容时,想直接丢弃工作区的修改,可以使用 git checkout -- file。这个命令其实就是用版本库里的版本替换工作区的版本,无论工作区中是修改还是删除文件,都可以"一键还原"。命令中--很重要,如果去掉了 -- 就成了切换分支的命令了。
当你乱改了工作区的某个文件内容,并且提交到了暂存区,想丢弃修改,分两步。
第一步用命令 git reset HEAD file 撤销暂存区的修改。完成之后可以用 git status 查看一下状态。
第二部用用上边提到的 git checkout -- file 来丢弃工作区的修改。
当你已经用 git commit 提交到版本库时,想要撤销修改,可以回退版本。
删除文件
假设有一个已经被 Git 追踪的文件 test.txt。
$ ls
test.txt
现在删除它
$ rm test.txt
这时查看仓库状态
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
这个时候有两种选择
- 使用
git checkout -- file放弃修改,文件会还原 - 使用
git add file或者git rm file都可以将"删除"提交到暂存区,然后可以查看暂存区的状态,再使用git commit提交到版本库。这样工作区和版本库里面的文件就都被删除了。
创建与合并分支
每次提交,Git 都把它们串成一条时间线,这条时间线就是一条分支。一开始的时候,只有一条 master 分支。
HEAD 严格来讲不是指向提交的,而是指向当前分支,现在是 master。
master 是指向当前提交的。每次提交 master 就会向前移动一步。
创建新分支的时候,比如 dev,Git 新建了一个指针 dev,指向 master 相同的提交。切换分支的时候,Git 再把 HEAD 指向 dev。
现在每次提交时,dev 就会往前移动,而 master 不变。
在 dev 上的工作完成之后,就可以把 dev 合并到 master 上了。当前这种情况,Git 会直接把 master 指向 dev 的当前提交,就完成了合并:
合并完成之后,可以删除 dev 分支。删除 dev 分支就是把 dev 指针删掉。
查看当前分支:
$ git branch
创建分支:
$ git checkout 分支名称
切换分支:
$ git branch 分支名称
创建并切换分支:
$ git checkout -b 分支名称
合并指定分支到当前分支:
$ git merge 指定分支名称
删除分支:
$ git branch -d 分支名称
Fast-forward 快进模式:
之前做的合并操作就是快进模式,直接把 master 指向 dev 的当前提交,所以合并速度非常快。除了这种模式,还有其它的合并方式。
解决冲突
当两条分支都提交了修改时,这时候合并就不是快进模式了,它只能试图把各自的修改合并起来。
如果两条分支都修改了同一处,那么它就会不知道应该改采用哪个分支的修改,这就是冲突。
$ git merge dev
Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt
这段命令试图将 dev 合并到 master,但是产生了冲突,Git 提示我在 test.txt 文件中有冲突。
使用 git status 查看工作区的状态,它也告诉我冲突的文件:
$ git status
On branch master
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: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
查看 test.txt 文件的内容:
$ cat test.txt
1
<<<<<<< HEAD
3
4
=======
2
3
>>>>>>> dev
可以看到,test.txt 文件中本来只有第一行写的是 1。
master 分支上添加了 3、4 并做了提交。
dev 分支上添加了 2、3 并做了提交。
在 git merge dev 之后,Git 把 dev 分支的 test.txt 文件合并到了 master 分支的 test.txt 文件。并且标记出了有冲突的地方。
手动修改 test.txt 处理冲突后,使用 git add 、git commit 进行提交,这样整个合并过程才算是完成。
如果合并的过程中没有出现冲突,那么 Git 会提示我们输入 message,输入完成之后已经合并成功了。
使用 git log --graph 命令也可以查看分支合并的情况:
$ git log --pretty=oneline --graph --abbrev-commit
* 5403d91 (HEAD -> master) Merge branch 'dev'
|\
| * 4f86c6a (dev) dev commit 3
* | 0372da4 master merge from dev 2
|\ \
| |/
| * cd070ad dev commit 2
* | c746cc7 master merge from dev
|\ \
| |/
| * 794d56a dev commit 1
* | cfe77bc master commit 2
|/
* 7bca0cd master commit 1
从下往上看:
- 我先在 master 上做了一次提交,master commit 1。
- 创建并切换到了 dev 分支。
- 切换回 master 分支。在 master 上修改 test.txt 并提交,master commit 2。
- 切换到 dev,修改 test.txt 并提交,dev commit 1。
- 切换到 master,开始合并分支 -> 遇到冲突 -> 修改文件处理冲突 -> 提交 -> 合并完成,master merge from dev。
- 切换到 dev,修改提交,dev commit 2。
- 切换到 master,合并分支,master commit merge from dev 2。
- 切换到 dev ,修改提交,dev commit 3。
- 切换回 master,合并,Merge branch 'dev'。
分支管理策略
合并分支时,如果可能,Git 会使用 Fast forward 模式,但这种模式下,删除分之后,会丢失掉分支的信息。
可以通过 --no-ff 强制禁用 Fast forward 模式,这样 Git 就会在 merge 时产生一个新的 commit,这样从分支历史上就可以看出分支信息。例如:
$ git merge --no-ff -m "merge with no-ff" dev
合并分支时,快进模式下没有产生提交,所以不用输入信息。非快进模式下的合并,产生了一次提交,用 -m 输入提交信息。
合并之后的图就像这样:
在实际开发中:
master 分支应该是非常稳定的,仅用来发布新版本,平时不能在上面干活。
干活都在 dev 分支上,dev 分支时不稳定的,到某个时候,比如 1.0 版本发布时,再把 dev 分支合并到 master 上,在 master 分支发布 1.0 版本;
小伙伴们在 dev 分支上干活,每个人都有自己的分支,时不时地往 dev 分支上合并就可以了。
所以,团队协作的分支看起来就像这样:
Bug 分支
当线上出现 bug 需要即刻修复时。可以切换到 master,在创建分支 bug_101,在分支上解决完 bug 之后合并到 master 上,然后删除 bug_101 分支。
在切换分支分支的时候,如果当前分支上还存在没有提交的修改,那么是不能进行切换分支的。但是现在工作只进行到了一半,还没办法提交,这时可以使用 stash 功能,可以把当前工作县城 "储藏" 起来,等以后恢复现场后继续工作:
$ git stash
$ git stash pop
新功能分支
当开发一个新功能的时候,最好也创建一个新的分支。
如果要强制删除一个没有被合并过的分支,可以使用 git branch -D <name>。
远程仓库
SSH Key
本地的 Git 仓库和远程库之间的传输一半都是通过 SSH 加密的,所以需要设置 SSH Key。
第 1 步:创建 SSH Key。在用户主目录下,看看有没有 .ssh 目录,再看看这个目录下有没有 id_rsa 和 id_rsa.pub 这两个文件,如果已经有了,可以直接跳到下一步。如果没有,创建 SSH Key:
$ ssh-keygen -t rsa -C youremail@example.com
把邮件替换成自己的邮件地址,一路回车即可,这里无需设置密码。
完成之后就可以在 ~/.ssh 中看到 i_rsa 和 id_rsa.pub 这两个文件。这就是 SSH Key 的密钥对,id_rsa 是私钥,id_rsa.pub 是公钥,可以放心地告诉任何人。
第 2 步:在远程网站上找到设置SSH Key的页面,将 id_rsa.pub 文件中的内容粘进去即可。
为什么远程网站需要 SSH Key呢?
因为远程网站需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而 Git 支持 SSH 协议,所以,它只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,也可以在网站添加多个公钥,这样就可以了使用多个电脑推送了。
关联远程仓库
先有本地仓库
假设现在已经有了一个本地仓库,又想创建一个远程仓库,并且这两个仓库可以同步,可以这样做:
第 1 步:先创建一个空的远程仓库,然后在本地仓库目录下执行命令:
$ git remote add origin git@github.com:远程仓库名称
执行后远程仓库的名称就是 origin,这也是 Git 默认的叫法,也可以改成别的。
这样 orign 就与我们的本地仓库关联起来了。
第 2 步:现在我们就可以把本地仓库的所有内容推送到远程库上:
$ git push -u origin master
把本地库的内容推送到远程,用 git push 命令,实际上是把当前分支 master 推送到远程。
由于远程库是空的,所以第一次推送 master 分支时,加上了 -u 参数,Git 不但会把本地的 master 分支内容推送的远程新的 master 分支,还会把本地的 master分支和远程的 master 分支关联起来,在以后的推送或者拉取时就可以简化命令。
第 3 步:以后本地提交之后,就可以使用:
$ git push origin master
把本地 master 分支的最新修改推送至 origin。
先有远程仓库
如果从零开发的话,最好的方式是先创建远程库,然后,从远程库克隆。使用 git clone 命令即可。
$ git clone 远程仓库给的地址
这样就可以把远程仓库克隆到本地,并且它们相关联。
多人协作
当从远程仓库克隆的时候,实际上 Git 自动把本地的 master 分支和远程的 master 分支对应起来了,并且,远程仓库的默认名称是 origin。
要查看远程库的信息,用 git remote,用 git remote -v 可以显示更详细的信息:会显示可以抓取和推送的origin的地址。如果没有推送权限,就看不到 push 的地址。
推送分支
推送分支就是把该分支上所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推送到远程库对应的远程分支上:
$ git push origin 分支名称
抓取分支
在开发时,我们会是不是向远程分支推送新的提交。如果在推送的时候,别人也碰巧修改了这里,那么就会产生冲突,推送失败。
解决的办法也很简单,先用 git pull 把最新的提交从抓下来,与本地的分支合并,解决冲突后再推送。
更详细的说明点这里。