0%

我的git笔记

前言

看了很多教程,发现还是很多东西记不住,如果不写写的话两天就忘了,所以记一下笔记。为了让别人有教程的感觉,对笔记进行了由浅入深的排序,可以帮你们快速入门。

git由来

linux在后期的开发过程中开发人员和代码量巨大,急需版本控制工具,刚开始有公司赞助给他们用,后来他们试图破解这个工具(确实很作死),人家不给他用了,这些linux coder一气之下用C开发了初代的git,后来火了之后又在2008年做了github网站托管全球代码,从此git走入千家万户。
git是分布式的,但是我们普通人用起来和集中式没有区别。所以不用管这个概念。

安装git

linux : sudo apt-get install git
mac: 安装homebrew - > 安装git 安装xcode - > preferences->downloads->Command Line Tools->install
window: 官方安装程序链接,推荐安装setup版而不是portable版
安装完以后需要你进行设置标记下是谁的github :

1
2
3
$ git config --global user.name "name or nick name"
$ git config --global user.email "email@example.com"
#git config 的 --global参数 表示此电脑所有git仓库都使用这个配置

版本库

版本库就是一个小小的仓库,这个仓库由git管理,里面任何东西的变动它都知道,在电脑上它就是一个文件夹。
我们来创建一个仓库(文件)

1
2
3
4
5
6
7
8
mkdir testforgit
cd testforgit
git init
#上面三步我们创建了testforgit文件夹,然后初始化它为仓库交由git管理
#我们可以 ls -la 看下当前目录下有一个.git文件 这个文件就是用来跟踪记录这个仓库的。
#我们也可以初始化一个非空的仓库
#注意:git只能管控文本文档,所以图片,word等二进制文件他只能管控记录大小的更改,无法知道图片文件中的具体哪行被更改了。
#注意:初始化完成后会生成.git文件夹,我们最好不要手动去修改里面的东西,这就像是小偷一样,不跟管理员说搞走一个,git管理员可能自己混乱,仓库也被破坏了。

初始完以后我们需要尝试写一个文件,比如readme.txt 写完放入git管控的目录下,这样git才能找到它,然后我们两步提交此文件到服务器:

1
2
3
4
5
6
7
8
9
10
11
$ git add readme.txt
$ git commit -m "add 3 files."
# -m 参数是说明的意思,强烈建议每个commit写说明

# 为什么需要两步?因为这样你可以add多个文件,然后一起commit到服务器,其中git add是提交到暂存区,gitcommit把暂存区的所有文件一起提交
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
#文件夹就是工作区,而 add 是提交文件到暂存区,这个概念很重要。一定要记住啊哦!
#如果你有一个文件readme.txt修改一次 git add了 然后它就进入了暂存区,此时本地再次修改readme.txt 再次git add,此时两次修改就被git合并存在暂存区,此时commit的就是最新的修改了!
#所以切记,commit只能提交存储在暂存区的文件

基础命令

修改文件

我们修改readme文件,然后通过以下命令来查看区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git status
#会显示那些代码被修改了,我们画个垃圾草图
| -> untracked files #从没有git add过的文件,即不被git追踪的文件
文件status分为 | -> 红色 modify #修改过,但是没有add
| -> 绿色 modify #修改过 git add完了 但是还没有 commit
#如果当前没有任何改动,则会显示:
On branch master
nothing to commit, working tree clean

$ git diff readme.txt
#查看readme.txt和文件服务器上的版本有什么区别,可以清楚的知道改了哪些内容

#然后你就可以继续提交了
$ git add
$ git commit -m
版本回退

修改完文件后我们后悔了,或者说我们修改完的代码崩溃了 调不过来了,我们想回退到以前能正常运行的版本。好,这在git是非常方便的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#我们首先看下现在的reamde文件内容
$ cat readme.txt
t is a version control system
add2
add3

#用git log 查看我们一共提交了多少个版本
$ git log
commit 97f63044c14952fc5d13a08a8448aded163de489 (HEAD -> master)
Author: geeeez <gz54321.a@gmail.com>
Date: Tue Nov 5 11:07:24 2019 +0800

add3

commit 4aae9f796a8440a0a6eb882ed99596a5fb1b5a40
Author: geeeez <gz54321.a@gmail.com>
Date: Tue Nov 5 11:06:58 2019 +0800

add2

commit cedeb93177ea09827758cdf1491a7f009ac4ef25
Author: geeeez <gz54321.a@gmail.com>
Date: Mon Nov 4 23:05:19 2019 +0800

learn git

#我们一共修改了两次,分别加入了add2 和 add3 ,其中有HEAD -> master表示的代表当前版本,commit后面代表的是版本唯一id,下面就是一些修改内容,如果我们想回退上次版本(add2) 只需
$ git reset --hard HEAD^

#回退上上次
$ git reset --hard HEAD^^

#回退上一百次呢?难道要一直写^?当然不是,这不是git的哲学。我们可以~加次数
HEAD~100

#但是很多时候我们不会去属次数,所以还有一张方法,和docker一样 我们可以直接通过id进行回滚
$ git reset --hard 1094a

#现在我们回退到版本id为 cedeb(不需要写全,和docker一样)
$ git reset --hard cedeb
HEAD is now at cedeb93 learn git

#现在再来看看readme.txt的内容吧,可以看到add2 和 add3 都消失了
t is a version control system

#此时我们再看git log 发现 cedeb之后的版本全部消失了,那我们后悔了不想回退了,还想找回最新的版本怎么办?
#这在git也是可以的,我们有个命令git reflog 记录了你的所有操作
$ git reflog
cedeb93 (HEAD -> master) HEAD@{0}: reset: moving to cedeb
97f6304 HEAD@{1}: commit: add3
4aae9f7 HEAD@{2}: commit: add2
cedeb93 (HEAD -> master) HEAD@{3}: reset: moving to head^
d947e60 HEAD@{4}: commit: test
cedeb93 (HEAD -> master) HEAD@{5}: commit (initial): learn git

##通过上述的版本id,你可以恢复到任何你commmit过的版本。是不是很简单啊!

撤销修改

有一天你在写代码,本来没啥事,结果等你第二天起来的时候发现昨晚写的工作区代码有很多bug,bug多到懒得调试,此时你可以一行一行的查找删除修改,也可以直接回滚版本,但是你发现出问题的代码仅存在在 login.php中,于是你想有没有一种办法回滚单个文件?嘿嘿 确实有

1
2
3
4
5
6
7
$ git checkout -- login.php
#命令git checkout -- login.php 意思就是,把login.php文件在工作区的修改全部撤销,这里有两种情况:

#一种是login.php自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
#一种是login.php已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。

#总之,就是让这个文件回到最近一次git commit或git add时的状态。

上次逃过一劫,然后你就放松警惕了,某天晚上你又在login.php写了一堆漏洞,然后还git add到了暂存区,但是你还没有commit,这时候如果用上面的命令是没用的,因为上面的命令同样只能恢复到提交到暂存区的状态,于是你晚上大哭大闹,觉得自己真是stupid,不过git 可以帮你:

1
2
git reset HEAD <file>
#reset 可以回退版本 也可以把文件从暂存区回退回来,这样就没有git add了 我们又可以用 $ git checkout -- login.php 回滚到上个commit 的 login.php了

嘿嘿,晚上终于可以早点睡觉了!

删除文件

当你删除本地库的readme.txt文件时 git status 会显示和版本库的文件不一致 会显示你删除了那个文件。

这个时候我们可以直接删除版本库的文件 git rm readme.txt 然后再次commit 一下,这样版本库和本地库就同步了。文件就删除完成。

如果你是不小心删除的,那就是恢复功能了,直接用上面的git checkout – readme.txt

git核心功能远程仓库来了

我们掌握了git的基础命令后发现git和svn也确实没啥大区别,但是为啥那么多人用git呢?接下来就是git方便的地方了,同时通过这个也可以知道为啥github那么受欢迎。

git远程仓库可以搭建在任意一台电脑上,大家都以它为仓库来拉取和提交代码,但是对于小公司来说没有必要自己去搭建一个git服务器,我们只需要把github当作一个云端电脑即可。(省时省力省钱)

免费的git仓库

我们只需要注册一个github就获得了一个免费的github仓库,下面我们需要配置这个仓库使得我们本地和云端github仓库安全传输代码和数据。

1、首先我们创建SSH key,在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key:

1
2
$ ssh-keygen -t rsa -C "yourmail@mail.com"

剩下的一路回车选择默认值即可,当然如果你想给你的key设置密码也可以(过程中直接输入密码),不过我这里就不设置了。

然后我们就创建成功了,这时候你的用户主目录下有.ssh文件夹,里面分别是id_rsa和id_rsa.pub。这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露出去,id_rsa.pub是公钥,可以放心地告诉任何人。

2、登录github添加公钥。登录后点击头向下拉菜单找到settings,然后点击SSH and GPG keys。然后new一个新的SSH keys:1575614058950
为什么GitHub需要SSH Key呢?因为GitHub需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,GitHub只要知道了你的公钥,就可以确认只有你自己才能推送。

当然,GitHub允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到GitHub,就可以在每台电脑上往GitHub推送了。

最后友情提示,在GitHub上免费托管的Git仓库,任何人都可以看到喔(但只有你自己才能改)。所以,不要把敏感信息放进去。

如果你不想让别人看到Git库,有两个办法,一个是交点保护费,让GitHub把公开的仓库变成私有的,这样别人就看不见了(不可读更不可写)。另一个办法是自己动手,搭一个Git服务器,因为是你自己的Git服务器,所以别人也是看不见的。这个方法我们后面会讲到的,相当简单,公司内部开发必备。

确保你拥有一个GitHub账号后,我们就即将开始远程仓库的学习。

添加远程库

现在你需要登录自己的github在右上角找到“Create a new repo“ 创建一个仓库,创建完成后复制仓库的地址,回到本地的lear-git文件夹(就是你本地仓库的文件夹),把这两个仓库关联并把本地的文件push上去:

1
2
3
4
5
6
# 关联两个仓库
$ git remote add origin git@github.com:aeexxxz/git_cacatest.git
# 将本地文件push到远程仓库
$ git push -u origin master
# 把本地仓库的内容推到远程用git push命令 实际上是把master分支推送到了远程
# 第一次推送时记得加上 -u 这样 git不仅会推送master到远程,还会把本地master分支和远程master分支关联起来。这样以后推送的时候就可以简化命令了。
从远程库克隆

生活中大多数情况是我们从零开始就已经准备使用github仓库,这时候我们肯定先创建远程仓库,然后拉取到本地,而不是从本地仓库再上传到远程仓库。

创建的github仓库的步骤和上面是一样的,我们最后仅仅需要找个合适的地方:

1
git clone 地址

如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。

此外我们还发现了git也支持https 但是https速度慢,而且必须要每次推送输入口令,这种就很麻烦,除非再公司内部只开放http的情况下使用,其他情况下我们更多的用 ssh

分支管理

为什么会有分支?

我们上线了一个版本,然后我们现在要开发下一个版本,这时候我们创建一个分支进行开发,然后忽然客户说上线的环境有bug,那我们可以在上线版本上再搞一个分支出来去修复bug,这样互不耽误,改bug、开放新版本、添加新需求可以同步进行,最后上线的时候再根据需要统一合并。

为什么git创建、删除、切换分支都能在一秒内完成?

因为git的分支是通过改变指针的指向来完成,无论你的分支有多少文件,对git来说,它只是把指向其他分支的指针重新指向master分支。

创建和合并分支

git里主分支是master,head指向master,切换分支时head指向对应分支,我们的工作分支就是head所指向的分支。

创建分支:

1
2
3
4
5
6
7
$ git checkout -b dev #创建并切换到dev分支,功能等同于下面两条命令
$ git branch dev #创建dev
$ git checkout dev #切换到dev分支

# 同时为了更好记忆新版git添加了switch命令进行切换
git switch -c name #创建并切换到name分支
$ git switch <name> #切换分支

查看当前分支

1
$ git branch

合并分支

1
$ git merge 分支名称

删除废弃的分支

1
$ git branch -d dev
解决冲突

如果两个文件名相同内容不同且都在当前节点提交,就会变成这样:
1575622688980

这时候我们进行 git merge+分支名称 会提示冲突因为相同文件有不同的内容,这个时候需要我们手动修改对应文件 并再次 git add git commit 然后才可以合并成功

分支管理

git merge 命令 默认使用的是fast-forward模式,此模式下删掉分支后,会丢掉分支的信息。有时候我们需要合并后保留分支中的信息方便我们做代码变动的审查,于是就有了强制关闭Fast forward模式的合并

1
2
3
4
5
6
7
8
9
$git checkout -b dev
$git add readme.txt
$git commit -m "modify readme.txt"
$git checkout master

$git merge --no-ff -m "merge with no-ff" dev
$git log --graph --pretty=oneline --abbrev-commit #查看分支信息
#这次合并后创建了一个新的commit,这种合并后使用 git log查看分支历史可以看到分支的信息
#合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

我们在实际开发中应该这样合理分配利用分支。
1、首先master分支是非常稳定的,仅仅用来发布新版本。
2、干活儿在dev分支上,等测试没问题了再合并到master分支。
3、每个人都有自己的分支,是不是的往dev分支上合并就可以了。

借用廖老师的一张图,真正的开发应该是这样

git-br-policy

BUG分支

bug永远存在,无论你的组大佬有多少,bug永远存在。git分支让bug修复更方便。

我是bob现在我正在bob分支上开发,但是领导突然通知有bug马上让我去修复,但是我的本地的工作还没有搞完,只写了一半,也无法提交(commit)。所以我只能先把当前工作区存储起来:

1
2
3
4
5
6
7
8
$git status    #查看当前工作区是否有未提交的,如果没有就不用保存当前工作区了

#切记没有进入git版本控制中的文件是不能被git stash 存起来的 所以有些后来添加的文件,没有来得及add到git版本控制 就不会被git stash存储起来。

$git stash #git stash 保存当前工作区和暂存区进度,回退到上一个 git commit 之后的状态
$git stash list # 查看存储过的工作区列表


这里会有人问,为什么我创建的比如3.txt没有加入版本控制器,它就无法被保存,那此时的git stash并不是完全保存了我现在的工作区,他有什么作用?

是这样的,比如我们修改了一个已经存在 在 git版本控制的文件比如readme.txt 但此时我们还未提交,如果此时我们切换到master,那么修改内容也会在master分支的reandme出现,而如果我们git stash 暂存当前的工作区,那么我们就会回到上一次commit的点上,再切换到master后,我们的工作区的内容不会对master分支的工作去造成任何影响。我们就可以更方便的进行master的操作。结束后 我们回到自己的分只进行 git stash pop 恢复 所有的git版本控制中的修改就都会还原,至于那些不在git版本控制中的文件,他永远都在工作区,既存在于master分支也存在与我们自己的开发分支,只有有人把他git add 进行提交时,它才归入版本控制库,独属于某一分支。

所以git stash在修复bug、切换分支时还是很有必要的。

然后我们切换到master分支去修bug

1
2
3
4
5
6
7
8
9
10
$git checkout master
$git checkout -b issue-01 #创建bug分支
#修复完成后add commit 并切换master分支合并
$git checkout master
$git merge --no-ff -m "merge bug fix 01"
$git checkout -d issue-01 #合并后删除bug分支
#切换回工作区
$git checkout test_bob
$git stash list #查看工作现场
$git stash pop #恢复工作并删除存储的工作现场列表

这样我们就完成了一次bug修复。

Feature分支

某天客户心血来潮,说要加一个可以自动识别是人在玩儿手机还是狗在玩儿手机的功能。虽然你觉得很不靠谱且功能都开发的差不多了,但是客户就是上帝,你还是需要增加一个需求。

为了不让新功能把我们已经开发好的代码打乱,我们从当前的dev分支新建一个feature分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$git checkout -b feature-person-or-dog
$git add person-or-dog.py
$git commit -m "开发完毕,最后一个功能提交"

#提交完切回dev分支进行代码合并
$git checkout dev
$git merge --no-ff -m "合并新功能" feature-person-or-dog
$git branch -d feature-person-or-dog

#如果在上一步提交之前这时候突然有一个插曲,客户又不要这个功能了,觉得这个功能太扯,我们就应该删除这个分支,防止别人知道我们开发了这么瞎扯的一个功能。
$ git branch -d feature-person-or-dog
error: The branch 'person-or-dog' is not fully merged.
If you are sure you want to delete it, run 'git branch -D person-or-dog'.

#上面的错误就是如果一个分支未被合并是不能被删除的,只能强制删除,所以我们需要-D参数进行删除

$ git branch -D feature-person-or-dog

多人协作

当你从远程仓库克隆时,实际上git自动把本地的master分支和远程的master分支对应起来了,并且远程仓库默认名称就是origin。

要查看远程仓库的信息: $git remote 或者使用 ·$git remote -v查看更详细信息。

1
2
3
$ git remote -v
origin git@github.com:michaelliao/learngit.git (fetch)
origin git@github.com:michaelliao/learngit.git (push)

上面显示了可以抓取和推送的origin地址。如果没有推送权限,就看不到push的地址。

我们有时候建立了自己的分支,而这个分支远程库没有,这时候我们就需要自己推送分支到远程仓库。

1
$git push origin dev

什么分支是适合推送的呢:

  • master分支是主分支,因此要时刻与远程同步;

  • dev分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;

  • bug分支只用于在本地修复bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个bug;

  • feature分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

新加入的小伙伴怎么开发:

1
2
3
4
5
6
7
$git clone  git@github.com:geeeez/git_test.git
#克隆完成后 git branch只能看到master分支,那小伙伴也想在dev分支开发。
#只需要创建一个本地dev分支,

$ git checkout -b dev origin/dev
#然后在dev上继续修改,时不时的把devpush到远程
#注意此时的创建分支命令和前面的略有不同,这里的创建还有关联的意思。

你的小伙盘推送完他的提交到origin/dev 碰巧你也要改该分支同样的文件并试图推送。这时候就会推送失败,git会提示你先git pull 最新的 origin/dev,然后本地合并解决冲突后再推送。

当然如果你此时的dev与远程的dev还没有关联起来,git pull会报错,那你首先应该关联再pull

1
2
3
4
5
6
7
8
$git branch --set-upstream-to=origin/dev dev
$git pull
#记住无法pull记得看是否把本地的test_bob对应到了远程的test_bob
#git pull 实际就是为了远程版本和本地版本合并

#中间要修改冲突 add commit 然后-》

$git push origin dev

因此,多人协作的工作模式通常是这样:

  • 首先,可以试图用git push origin <branch-name>推送自己的修改;

  • 如果推送失败,则因为远程分支比你的本地更新,需要先用git pull试图合并;

  • 如果合并有冲突,则解决冲突,并在本地提交;

  • 没有冲突或者解决掉冲突后,再用git push origin <branch-name>推送就能成功!

  • 如果git pull提示no tracking information,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

  • 这就是多人协作的工作模式,一旦熟悉了,就非常简单。

总结

  • 查看远程库信息,使用 git remote -v

  • 本地新建的分支如果不推送到远程,对其他人就是不可见的;

  • 从本地推送分支,使用git push origin branch-name,如果推送失败,先用git pull抓取远程的新提交;

  • 在本地创建和远程分支对应的分支,使用git checkout -b branch-name origin/branch-name,本地和远程分支的名称最好一致;

  • 建立本地分支和远程分支的关联,使用git branch --set-upstream branch-name origin/branch-name

  • 从远程抓取分支,使用git pull,如果有冲突,要先处理冲突。

标签管理

git中打标签非常简单

1
2
3
4
5
6
7
8
9
10
11
$git branch
*test-bob
master
$git checkout master

#打标签
$git tag v1.0

#查看所有标签
$git tag

默认标签打在最新提交的commit上,有时候你想往上次的提交中打上一个标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#查看看提交的commit id
git log --pretty=online --abbrev-commit
ffa9285 (HEAD -> test_bob, tag: hahha2019-12-19, origin/test_bob) 2 modigy fix conflict
e4c1719 2 2 modify
2d8eeb3 sadsa
bfdb7dd d
7f599e8 fix conflict
18e491f lear2 bob modify 03.txt
0922ddd (origin/master, origin/HEAD, master) fix coment
a515f05 master add 02-03
483f6a0 add test_02
ab77e12 add test_01
8661a49 rm
b8d2486 a git markdown
bf14c12 learn2
e22eedb add a a on line
5da025d modify git to gid
eb4c31e add a readme.md

#添加标签到 bfdb7dd
$git tag testbfdb7dd bfdb7dd

#而且还可以创建带有说明的标签 -a 指定标签名 -m 指定说明文字
$git tag -a v0.1 -m "version 0.1 released" eb4c31e

#使用git show可以查看tag的说明文字
$git show v0.1

#注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

如果标签打错了也可以删除

1
$git tag -d v0.1

因为标签只存储在本地,不会推送到远程。所以,打错的标签可以在本地安全删除。
如果需要推送某个标签到远程,可以使用命令:bash git push origin <tagname>
或者,一次性推送全部未推送的标签:bash $ git push origin --tags

如果推送到远程再想删除就稍微有点麻烦了:

1
2
3
4
5
#先从本地删除
$git tag -d v0.1

#再从远程删除 命令也是push
$git push origin :refs/tags/v0.1
拯救强迫症

密码配置文件肯定不能上github但是每次提交时都会提醒你有一个文件未提交,这样强迫症患者很有压力,所以git解决了这个,你只需要创建一个.gitignore文件,然后编辑它,格式官方已经给出:
https://github.com/github/gitignore
比如,在windows:

1
2
# windows
777.txt

然后把.gitignore文件提交给版本库即可:

1
2
3
4
$git add .gitignore
$git commit -m "add .gitignore"
#提交好后再次gitstatus 会发现777.txt直接就被忽略了
$git status

有时候我们已经提交了一个文件,但是我们希望以后不要再追踪这个文件了,我们就可以:

1
2
3
$git rm --catch 文件名

#然后编辑.gitignore 添加进去文件名 并提交到版本库 从此以后文件的修改就不会被git status提示了

采用署名-非商业性使用-相同方式共享 4.0(CC BY-NC-SA 4.0)许可协议
「分享也是一种学习」