Git 笔记与总结

本文章参考了廖雪峰的博客
Git 官网
散尽浮华的博客

Git 安装

安装就没有什么好说的了, 大家根据自己的平台安装对应的 Git 的就行了, 安装完后需要进行一下设置, 这儿不设置以后连接到远程仓库时也会让你设置的, 设置很简单, 只需要执行一下两个命令就可以了:

git config --global user.name "Your name"
git config --global user.email "[email protected]"

注意 git config命令的 --global参数, 用了这个参数, 表示你这台机器上所有的 Git 仓库都会使用这个配置, 当然也可以对某个仓库指定不同的用户名和 Email 地址.

初始化一个 Git 仓库

仓库, 英文名 repository, 你可以简单理解成一个目录, 这个目录里面的所有文件都可以被 Git 管理起来, 每个文件的修改、删除, Git 都能跟踪(被 .gitignore 文件忽略的文件或文件夹除外), 以便任何时刻都可以追踪历史, 或者在将来某个时刻可以"还原". 你可以使用 git init命令将一个文件夹初始化为一个 Git 的仓库, 但是为了不跟踪不需要跟踪的文件或文件夹, 你可以在 git init之前先创建一个 .gitignore文件, 把需要忽略的文件或文件夹添加进去. 然后使用 git add <file>或者 git add .将文件添加到仓库, 最后使用 git commit -m"你的说明"提交文件, 因此初始化一个 git 仓库的操作如下:

touch .gitignore
vim .gitignore
git init
git add .
git commit -m"init"

当我们修改或添加了被 git 跟踪的文件时我们就可以先使用 git add <file>添加文件, 在使用 git commit -m"你的说明"来提交你的修改.

例如我们修改了 README.md文件, 使用 git status我们就能够看到修改或添加了哪些文件:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

从提示可以看到使用 git add <file>可以更新文件(到暂存区), 使用 git checkout -- <file>可以放弃更改. 此外使用 git diff可以查看具体修改了哪些地方. 当我们执行 git add README.md后再使用 git status可以看到:

$ git add README.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   README.md

这时我们可以使用 git commit -m"你的说明"来提交修改, 或者使用 git reset HEAD <file>回到 git add <file>之前的状态.
当我们执行完 git commit后再执行 git status就可以看到:

$ git commit -m"modified readme.md"
[master 33c0075] modified readme.md
 1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working tree clean

Git 告诉我们当前没有需要提交的修改, 而且, 工作目录是干净(working tree clean)的. 前面讲了可以使用 git reset HEAD <file>来放弃暂存的修改, 但如果已经提交了, 在想退回去怎么办呢, 那么就要用到 Git 的版本穿梭的功能了.

Git 版本穿梭

使用 Git 的一个好处就是可以进行版本穿梭, 每当你发现对自己的修改后悔了, 你都可以回到曾经提交过的地方. 那么怎么使用 Git 进行版本穿梭呢?

像前面那样, 你不断对文件进行修改, 然后不断提交修改到版本库里, 就好比玩游戏时, 每通过一关就会自动把游戏状态存盘, 如果某一关没过去, 你还可以选择读取前一关的状态. 有些时候, 在打 Boss 之前, 你会手动存盘, 以便万一打Boss失败了, 可以从最近的地方重新开始. Git 也是一样, 每当你觉得文件修改到一定程度的时候, 就可以 “保存一个快照”, 这个快照在 Git 中被称为 commit. 一旦你把文件改乱了, 或者误删了文件, 还可以从最近的一个 commit 恢复, 然后继续工作,而不是把几个月的工作成果全部丢失.

在 Git 中, 我们用 git log命令查看提交历史:

$ git log
commit 4387ffa3959a1b1e68763981ab680ec7fca4a7b0 (HEAD -> master)
Author: zh <[email protected]>
Date:   Tue Dec 25 21:49:26 2018 +0800

    Commit 3

commit c00dfc9e1fe2a2d10d680958813d7893fcfc913b
Author: zh <[email protected]>
Date:   Tue Dec 25 21:49:03 2018 +0800

    Commit 2

commit 8a0a30d501ed386b17567cd52db061ac8d0d4750
Author: zh <[email protected]>
Date:   Tue Dec 25 21:43:08 2018 +0800

    Commit 1
...

如果嫌输出信息太多, 看得眼花缭乱的, 可以试试加上 --pretty=oneline参数:

$ git log --pretty=oneline
4387ffa3959a1b1e68763981ab680ec7fca4a7b0 (HEAD -> master) Commit 3
c00dfc9e1fe2a2d10d680958813d7893fcfc913b Commit 2
8a0a30d501ed386b17567cd52db061ac8d0d4750 Commit 1
33c0075d47c7a9e67116f3fe2b5da1031f8bca91 modified readme.md
f60ac55c9584d03bcb7baa0c7b4f4eb50b1b7d37 readme
563b874ed93da2538d1f950e83af855e7806b227 init

要进行版本穿梭, 首先, Git 必须知道当前版本是哪个版本, 在 Git 中, 用 HEAD表示当前版本, 也就是最新的提交 4387ffa39..., 上一个版本就是 HEAD^, 上上一个版本就是 HEAD^^, 当然往上 100 个版本写 100 个 ^比较容易数不过来, 所以写成 HEAD~100.

现在,我们要把当前版回退到上一个版本, 就可以使用 git reset命令:

$ git reset --hard HEAD^
HEAD is now at c00dfc9 Commit 2

这时仓库里面的文件已经回到了上一次提交后的样子. 最新的那个版本已经看不到了! 但是如果你还想再回去怎么办呢? 办法其实还是有的, 只要上面的命令行窗口还没有被关掉, 你就可以顺着往上找啊找啊, 找到那个后面那次提交的的 commit id4387ffa39..., 于是就可以指定回到未来的某个版本:

$ git reset --hard 4387
HEAD is now at 4387ffa Commit 3

这样我们就又回到版本回退之前的位置了. 现在, 你回退到了某个版本, 关掉了电脑, 第二天早上就后悔了, 想恢复到新版本怎么办? 找不到新版本的 commit id怎么办? 在 Git 中, 总是有后悔药可以吃的. 当你用 git reset --hard HEAD^回退到上一版本时, 再想恢复到当前版本, 就必须找到当前版本的 commit id. Git 提供了一个命令 git reflog用来记录你的每一次命令:

$ git reflog
4387ffa (HEAD -> master) HEAD@{0}: reset: moving to 4387
c00dfc9 HEAD@{1}: reset: moving to HEAD^
4387ffa (HEAD -> master) HEAD@{2}: commit: Commit 3
c00dfc9 HEAD@{3}: commit: Commit 2
8a0a30d HEAD@{4}: commit: Commit 1
33c0075 HEAD@{5}: commit: modified readme.md
f60ac55 HEAD@{6}: commit: readme
563b874 HEAD@{7}: commit (initial): init

Git 远程仓库

添加远程仓库

添加远程远程仓库是为了更好的进行多人协作. 那么怎么为本地的 Git 仓库添加远程仓库呢. 下面就以 github 上的远程仓库为例讲解怎么为一个本地仓库添加远程仓库.

首先在 github 上面新建一个空的仓库, 例如我在我的 github 上创建了一个名为 init_repository 的仓库, 下面我们就可以使用 git remote add origin [email protected]:zh-bupt/init_repository.git命令将本地仓库与远程仓库关联起来, 添加后, 远程库的名字就是origin, 这是 Git 默认的叫法, 也可以改成别的, 但是 origin 这个名字一看就知道是远程库.

git remote add origin [email protected]:zh-bupt/init_repository.git

下一步, 就可以把本地库的所有内容推送到远程库上:

$ git push -u origin master
Counting objects: 18, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (18/18), 1.69 KiB | 575.00 KiB/s, done.
Total 18 (delta 0), reused 0 (delta 0)
To github.com:zh-bupt/init_repository.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

把本地库的内容推送到远程, 用 git push命令, 实际上是把当前分支master推送到远程. 由于远程库是空的, 我们第一次推送 master分支时, 加上了 -u参数, Git 不但会把本地的 master分支内容推送的远程新的 master分支, 还会把本地的 master分支和远程的 master分支关联起来, 在以后的推送( git push)或者拉取( git pull)时就可以简化命令.

从远程库克隆

上面将的是怎么为一个本地仓库添加远程仓库. 现在假设已经远程已经有一个仓库了, 你要加入这个开发小组进行开发怎么办呢. 很简单, 只需要使用 git clone命令就能把远程分支"克隆"到本地, 然后也不用在设置远程分支了:

$ git clone [email protected]:zh-bupt/init_repository.git
Cloning into 'init_repository'...
remote: Enumerating objects: 18, done.
remote: Counting objects: 100% (18/18), done.
remote: Compressing objects: 100% (12/12), done.
Receiving objects: 100% (18/18), done.
remote: Total 18 (delta 0), reused 18 (delta 0), pack-reused 0

然后你就可以加入这个工程的开发小组愉快的进行开发了.

Git 分支管理与多人协作

Git 分支管理

分支就是科幻电影里面的平行宇宙, 当你正在电脑前努力学习 Git 的时候, 另一个你正在另一个平行宇宙里努力学习 SVN.

如果两个平行宇宙互不干扰, 那对现在的你也没啥影响. 不过, 在某个时间点, 两个平行宇宙合并了, 结果, 你既学会了 Git 又学会了 SVN!

分支在实际中有什么用呢? 假设你准备开发一个新功能, 但是需要两周才能完成, 第一周你写了 50% 的代码, 如果立刻提交, 由于代码还没写完, 不完整的代码库会导致别人不能干活了. 如果等代码全部写完再一次提交, 又存在丢失每天进度的巨大风险.

现在有了分支, 就不用怕了. 你创建了一个属于你自己的分支, 别人看不到, 还继续在原来的分支上正常工作, 而你在自己的分支上干活, 想提交就提交, 直到开发完毕后, 再一次性合并到原来的分支上, 这样, 既安全, 又不影响别人工作.

同时分支在多人写作中有着至关重要的作用, 因为在显示生活中一个项目往往由很多人参与开发, 那么分支就能够让每个人专注于自己开发的功能, 而不用去管别人开发的情况, 并且能够在必要时整合整个团队的开发进度.

分支的创建与管理

在版本穿梭里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即 master分支。HEAD严格来说不是指向提交,而是指向 mastermaster才是指向提交的,所以,HEAD指向的就是当前分支.

一开始的时候,master分支是一条线,Git用 master指向最新的提交,再用 HEAD指向 master, 就能确定当前分支, 以及当前分支的提交点:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tuqxtCt2-1571360357564)(https://i.loli.net/2018/12/26/5c22ebbdc05fb.png)]
每次提交, master分支都会向前移动一步, 这样, 随着你不断提交,master分支的线也越来越长.

当我们创建新的分支, 例如 dev时, Git 新建了一个指针叫 dev, 指向 master相同的提交, 再把 HEAD指向 dev, 就表示当前分支在 dev上, 上面的操作可以用一下命令实现:

git branch dev
git checkout dev

或者使用命令:

git checkout -b dev


Git创建一个分支很快, 因为除了增加一个 dev指针, 改改 HEAD的指向, 工作区的文件都没有任何变化! 不过, 从现在开始, 对工作区的修改和提交就是针对 dev分支了, 比如新提交一次后, dev指针往前移动一步, 而 master指针不变:

假如我们在 dev上的工作完成了, 就可以把 dev合并到 master上. Git 怎么合并呢? 最简单的方法, 就是直接把 master指向 dev的当前提交, 就完成了合并. 以上操作可以通过一下命令实现:

git checkout master
git merge dev

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MgKckBZG-1571360357568)(https://i.loli.net/2018/12/26/5c22ee2acb2b5.png)]
合并完分支后, 甚至可以删除 dev分支. 删除 dev分支就是把dev指针给删掉, 删除分支可以使用命令:

git branch -d <branch>

删除没有完全合并的分支, Git 是会报错的, 如果想要强制删除可以使用 git branch -D <branch>来强制删除一个分支. 删掉后, 我们就剩下了一条 master分支:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKqPFOob-1571360357568)(https://i.loli.net/2018/12/26/5c22eefeb2519.png)]

冲突解决

在真正的小组开发中, 分支的管理并不会像上面那么简单. 注意到上面合并分支时的 Fast-forward信息, Git 告诉我们, 这次合并是"快进模式", 也就是直接把 master指向 dev的当前提交, 所以合并速度非常快. 当然, 也不是每次合并都能 Fast-forward, 我们后面会讲其他方式的合并.

现在假设你在你的 feature1分支上完成了你的开发, 但是在你开发的同时, 会有其他的人在另外的分支上进行开发, 完成后也会向 master分支上合并, 这样当你想向 master分支上合并时, 就会像下面这个样子:

这种情况下, Git无法执行"快速合并", 只能试图把各自的修改合并起来, 但这种合并就可能会有冲突:

$ git merge feature1
Auto-merging readme.txt
CONFLICT (content): Merge conflict in readme.txt
Automatic merge failed; fix conflicts and then commit the result.

Git 用 <<<<<<<, =======, >>>>>>>标记出不同分支的内容, 我们处理完所有冲突并删除所有上面的标记后保存再提交就可以了:

$ git add readme.txt 
$ git commit -m"conflict fixed"
[master cf810e4] conflict fixed

这样分支的状态就变成下面这样了:

最后, 删除 feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 14096d0).

git rebase

在上面我们看到了, 多人在同一个分支上协作时, 很容易出现冲突. 即使没有冲突, 后push的童鞋不得不先 pull, 在本地合并, 然后才能 push成功. 每次合并再push后, 分支变成了这样:

$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
*   e5e69f1 Merge branch 'dev'
|\  
| *   57c53ab (origin/dev, dev) fix env conflict
| |\  
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/  
* |   12a631b merged bug fix 101
|\ \  
| * | 4c805e2 fix bug 101
|/ /  
* |   e1e9c68 merge with no-ff
|\ \  
| |/  
| * f52c633 add merge
|/  
*   cf810e4 conflict fixed

总之看上去很乱, 有强迫症的童鞋会问: 为什么 Git 的提交历史不能是一条干净的直线? 其实是可以做到的! Git有一种称为 rebase(变基) 的操作.

关于变基可以去看 Git 官网的教程, 翻译的还是很好的.

讲了 rebase, 还有一个 git pull --rebase值得讲一讲:

git pull --rebase

这部分参考了散尽浮华的博客

使用下面的关系区别 git pullgit pull --rebase这两个操作:

git pull = git fetch + git merge
git pull --rebase = git fetch + git rebase

现在来看看 git mergegit rebase的区别.
假设有3次提交A, B, C.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a0BNXGoq-1571360357570)(https://i.loli.net/2018/12/18/5c18b78b4c157.png)]
在远程分支 origin的基础上创建一个名为 mywork的分支并提交了, 同时有其他人在 origin上做了一些修改并提交了.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VsvByjqe-1571360357571)(https://i.loli.net/2018/12/18/5c18b7d26d44c.png)]
其实这个时候E不应该提交, 因为提交后会发生冲突. 如何解决这些冲突呢? 有以下两种方法:

  1. git pull
    git pull命令把 origin分支上的修改 pull下来与本地提交合并( merge)成版本M, 但这样会形成图中的菱形, 让人很困惑.
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1EQzEWaG-1571360357572)(https://i.loli.net/2018/12/18/5c18b84f6c175.png)]
  2. git pull --rebase
    git pull --rebase会创建一个新的提交R, R的文件内容和上面M的一样, 但我们将 E 提交废除, 当它不存在(图中用虚线表示). rebase的好处是避免了菱形的产生, 保持提交曲线为直线, 让大家易于理解.
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-owYVECcZ-1571360357572)(https://i.loli.net/2018/12/18/5c18b929a91df.png)]
    rebase的过程中, 有时也会有冲突, 这时 Git 会停止 rebase并让用户去解决冲突, 解决完冲突后, 用 git add命令去更新这些内容, 然后不用执行 git commit, 直接执行 git rebase --continue, 这样 Git 会继续 apply 余下的补丁. 在任何时候,都可以用 git rebase --abort参数来终止 rebase的行动, 并且 mywork分支会回到 rebase开始前的状态.
发布了11 篇原创文章 · 获赞 7 · 访问量 8926

猜你喜欢

转载自blog.csdn.net/zh20166666/article/details/85259883
今日推荐