git基本操作

准备工作

软件下载及注册

  • 下载git(win)
  • 打开Git?
    1. 开始菜单Git Bash。
    2. 鼠标右键打开Git Bash。
  • 注册GitHub

配置ssh keys

使用ssh keys免密码将本地git项目与远程的github建立联系。

  • 检查ssh keys的设置

    1
    2
    3
    cd ~/.ssh   #检查本机的ssh密钥 

    如果提示:No such file or directory 说明你是第一次使用git。
  • 生成新的SSH Key:

    1
    2
    3
    4
    ssh-keygen -t rsa -C "your_name@youremail.com"

    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/your_user_directory/.ssh/id_rsa):<回车就好>

    然后系统会要你输入密码:

    1
    2
    Enter passphrase (empty for no passphrase):<输入加密串>
    Enter same passphrase again:<再次输入加密串>

在回车中会提示你输入一个密码,这个密码会在你提交项目时使用,如果为空的话提交项目时则不用输入。这个设置是防止别人往你的项目里提交内容。

  • 添加ssh Key到GitHub
    在本机设置SSH Key之后,需要添加到GitHub上,以完成SSH链接的设置。

    1
    2
    3
    1. 打开本地 C:\Users\your_user_name\.ssh\id_rsa.pub文件。此文件里面内容为刚才生成人密钥。如果看不到这个文件,你需要设置显示隐藏文件。准确的复制这个文件的内容,才能保证设置的成功。 
    2. 登陆github。点击右上角的 Account Settings—->SSH Public keys —-> add another public keys
    3. 把你本地生成的密钥复制到里面(key文本框中), 点击 add key 就ok了
  • 测试

1
ssh -T git@github.com    # 查看设置是否成功,git@github.com的部分不要修改

如果是下面的反馈:

1
2
3
The authenticity of host 'github.com (207.97.227.239)' can't be established.
RSA key fingerprint is \*\*\*\*.
Are you sure you want to continue connecting (yes/no)?

不要紧张,输入yes就好,然后会看到:
Hi cnfeat! You’ve successfully authenticated, but GitHub does not provide shell access.

设置用户信息

现在你已经可以通过SSH链接到GitHub了,还有一些个人信息需要完善的。

1
2
$ git config --global user.name "your_user_name"              
$ git config --global user.email "your_name@youremail.com"

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

操作流程

在本地创建仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mkdir git_learning         # 在本地硬盘创建一个空文件夹 
cd git-learning
git init # 把这个目录变成git可以管理的仓库
touch test.txt # 创建一个文件

git add test.txt # 将文件添加到暂存区
git commit # 将文件提交到版本库
git commit -m "comments" # -m 后面输入的是本次提交的说明

注意:
add 一次可以添加多个文件,commit 一次可以对多个add有效

git add . #支持通配符和环境变量?
git add test.txt
git add test2.txt test3.txt
git commit -m "add 3 files." # 将暂存区中的多次修改同时提交到版本库

修改 test.txt 文件的内容,比如增加一行。

1
2
3
4
5
6
7
8
git status         # 掌握仓库当前的状态,输出结果会显示test.txt被修改过了,但还没有准备提交的修改。
git diff test.txt # 查看修改的内容,显示的格式正是Unix通用的diff格式

git add test.txt
git commit -m "add a new line to test.txt"
git status # 再次查看当前的状态,显示 nothing to commit

重复上述步骤,再熟悉一遍。

回退到上一个版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
git log                           # 查看修改历史,显示结果为从近到远
git log -1 # 查看最近一次修改的log
git log --pretty=oneline # 简化输出信息
git log --pretty=oneline --abbrev-commit

看到的一大串类似3628164...882e1e0的是commit id(版本号)

在Git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100

git reset --hard HEAD^ # 回退到上一个版本

git log # 再看看现在版本库的状态,发现最新的版本已经看不到了

回到最新的版本(前提是当前的终端没有关闭)
git reset --hard 3628164 #数字为最新版本对应的commit id,只需要写出前几位就行了,git会自动找到

回到最新的版本(终端已经关闭过)
git reflog # 查询每一次操作的历史记录
找到最新版本对应的 id 之后再 git reset

工作区与版本库

将working dir(工作区),stage(暂存区),master(版本库) 看成版本的三个状态

git 工作区:自己的pc中能看到的目录,比如git_learning

版本库: 工作区有一个隐藏目录.git,这个不算工作区,而是Git的版本库

Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git为我们自动创建的第一个分支master,以及指向master的一个指针叫HEAD

前面讲了我们把文件往Git版本库里添加的时候,是分两步执行的:

第一步是用git add把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支。

因为我们创建Git版本库时,Git自动为我们创建了唯一一个master分支,所以,现在,git commit就是往master分支上提交更改。

你可以简单理解为,需要提交的文件修改通通放到暂存区,然后,一次性提交暂存区的所有修改。
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的

每次修改,如果不add到暂存区,那就不会加入到commit中

1
git diff HEAD -- text.txt     #查看工作区和版本库中最新版本的区别

Git比其他版本控制系统设计得优秀,因为Git跟踪并管理的是修改,而非文件。

撤销修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git checkout -- file           #可以丢弃工作区的修改

在 test.txt中添加一行后保存

git checkout -- test.txt # 把test.txt文件在工作区的修改全部撤销

这里有两种情况:

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

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

git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令

如果做完修改后已经git add到暂存区了,

1
2
3
git reset HEAD file  # 可以把暂存区的修改撤销掉(unstage),重新放回工作区

git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。

场景1:当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git checkout -- file

场景2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git reset HEAD file,就回到了场景1,第二步按场景1操作。

场景3:已经提交了不合适的修改到版本库时,想要撤销本次提交,参考版本回退一节,不过前提是没有推送到远程库。

1
2
3
4
5
6
7
8
9
git reset                 # 不指定HEAD,用来清空缓存区的修改
git reset filename # 清空缓存区指定文件的修改
git reset --hard # 不指定HEAD,用来清空工作区和缓存区的修改
git reset --hard filename # 清空工作区和缓存区指定文件的修改

git checkout branch #切换branch, 同时重置缓存区和工作区, 如果工作区有修改没有提交, 需要先commit或stash
git checkout branch --force #切换branch, 同时重置缓存区和工作区
git checkout --force #不指定branch, 用来清空工作区的修改(缓存区不变, 如果之前有add, 则工作区与缓存区一致)
git checkout -- filename #清空工作区指定文件的修改

删除文件

1
2
3
4
5
6
7
8
前提:版本库中存在test.txt文件,并已经在工作区中删除 test.txt.
git status #可以发现工作区中删除的文件

若要从版本库中删除此文件
git rm test.txt
git commit -m "remove test.txt"
若是错删,需要从版本库中恢复
git checkout -- test.txt

远程仓库

先在 github 中创建一个 repo,命名为 for_test,其它保持默认设置(让它是空的)。

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
git remote add origin git@github.com:your_name/for_test.git    #连接远程库

添加后,远程库的名字就是origin,这是Git默认的叫法,也可以改成别的。

git push -u origin master #把本地版本库中所有内容推送到远程库,实际上是把当前分支master推送到远程

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

git push origin master # 普通本地提交

git remote # 查看远程库的信息
git remote -v

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

git remote remove origin # 移除连接
git remote add origin https://github.com/your_name/for_test.git


git clone git@github.com:your_name/for_test.git # 从远程库克隆

git clone https://github.com/your_name/git-learning.git test_repo/git-learning # 设置保存的名称

GitHub给出的地址不止一个,还可以用https这样的地址。
实际上,Git支持多种协议,默认的git://使用ssh,但也可以使用https等其他协议。

使用https除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用ssh协议而只能用https。

分支管理

你本身拥有master分支(稳定版本),然后创建了一个dev分支(开发版本),然后你可以在dev分支干活,直到开发完毕后,再一次性合并dev分支到master分支上,这样,既安全,又不影响别人工作。

当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:

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
git checkout -b dev     #创建并切换到dev分支,相当于以下两条命令:

git branch dev
git checkout dev

git branch #查看当前分支,会列出所有分支,当前活跃分支前面会标一个*号

然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改。
现在,dev分支的工作完成,我们就可以切换回master分支:

git checkout master # 切换回master分支后,再查看readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变

git merge dev # 把dev分支的工作成果合并到master分支上

git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。

注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。

git merge --no-ff dev # 加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并

git branch -d dev # 合并完成后,就可以放心地删除dev分支了:

git branch -D branch_name # 强行删除一个没有被合并过的分支

git branch # 删除后,查看branch,就只剩下master分支

因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全

git push origin dev # push本地dev分支到远程

分支管理原则

  • master分支应该是非常稳定的,也就是仅用来发布新版本,平时不能在上面干活;
  • 干活都在dev分支上,也就是说,dev分支是不稳定的,到某个时候,比如1.0版本发布时,再把dev分支合并到master上,在master分支发布1.0版本;
  • 你和你的小伙伴们每个人都在dev分支上干活,每个人都有自己的分支,时不时地往dev分支上合并就可以了。
  • 合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并

Bug分支

如果你现在正在dev分支干活,突然发现原master分支有一个bug需要及时修复,这时你需要停下dev分支的工作,建立bug分支来修bug。
Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
git stash

git status # 查看工作区,发现是干净的,因此可以放心地创建分支来修复bug。

git checkout dev # 在master分支中干完活之后再回到dev分支

git status # 发现工作区是干净的,刚才的工作现场存到哪去了?

git stash list # 查看stash的内容

工作现场还在,Git把stash内容存在某个地方了,但是需要恢复一下,有两个办法:

一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;

另一种方式是用git stash pop,恢复的同时把stash内容也删了:

git stash list # 再次查看,就看不到任何stash内容了:

feature分支

开发一个新feature,最好新建一个分支;

多人协作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
git remote
git remote -v

推送分支

git push origin master
git push origin dev # 推送其它分支,如dev

抓取分支
多人协作时,大家都会往master和dev分支上推送各自的修改

git clone git@github.com:michaelliao/learngit.git

现在,你的小伙伴要在dev分支上开发,就必须创建远程origin的dev分支到本地,于是他用这个命令创建本地dev分支:

git checkout -b dev origin/dev

现在,他就可以在dev上继续修改,然后,时不时地把dev分支push到远程

你的小伙伴已经向origin/dev分支推送了他的提交,而碰巧你也对同样的文件作了修改,并试图推送:

推送失败,因为你的小伙伴的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git已经提示我们,先用git pull把最新的提交从origin/dev抓下来,然后,在本地合并,解决冲突,再推送:

1
2
3
4
5
6
7
8
9
10
11
git pull

git pull也失败了,原因是没有指定本地dev分支与远程origin/dev分支的链接,根据提示,设置dev和origin/dev的链接:

git branch --set-upstream dev origin/dev

再pull:

git pull

这回git pull成功,但是合并有冲突,需要手动解决,解决的方法和分支管理中的解决冲突完全一样。解决后,提交,再push:

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

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

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

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

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

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

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

标签管理

在Git中打标签非常简单,首先,切换到需要打标签的分支上:
然后,敲命令git tag tag_name就可以打一个新标签:

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
git tag v1.0     # 默认为最新提交的commit打标签

git tag #查看所有标签,按字母顺序排列

git show v0.9 #查看标签信息

默认标签是打在最新提交的commit上的。如果需要对历史提交打标签,只需到历史提交的commit id,然后打上就可以了

git log --pretty=oneline --abbrev-commit # 查看历史提交的commit id

git tag v0.9 6224937 # 对历史提交打标签

git tag -a v0.1 -m "version 0.1 released" 3628164 # 创建带有说明的标签,用-a指定标签名,-m指定说明文字

git tag -d v0.1 # 删除标签

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

git push origin v1.0 # 推送某个标签到远程,使用命令git push origin <tagname>

git push origin --tags # 一次性推送全部尚未推送到远程的本地标签

如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:

git tag -d v0.9

然后,从远程删除。删除命令也是push,但是格式如下:

git push origin :refs/tags/v0.9

使用github

如何参与一个开源项目呢?比如人气极高的bootstrap项目,这是一个非常强大的CSS框架,你可以访问它的项目主页,点Fork就在自己的账号下克隆了一个bootstrap仓库,然后,从自己的账号下clone:

1
git clone git@github.com:michaelliao/bootstrap.git

一定要从自己的账号下clone仓库,这样你才能推送修改。如果从bootstrap的作者的仓库地址git@github.com:twbs/bootstrap.git克隆,因为没有权限,你将不能推送修改。

如果你想修复bootstrap的一个bug,或者新增一个功能,立刻就可以开始干活,干完后,往自己的仓库推送。

如果你希望bootstrap的官方库能接受你的修改,你就可以在GitHub上发起一个pull request。当然,对方是否接受你的pull request就不一定了。

自定义git

1
git config --global color.ui true    # 让git显示颜色

在Git工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,Git上传时就会自动忽略这些文件。

不需要从头写.gitignore文件,GitHub已经为我们准备了各种配置文件,只需要组合一下就可以使用了。所有配置文件可以直接在线浏览

  • 忽略文件的原则是:
    1. 忽略操作系统自动生成的文件,比如缩略图等;
    2. 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
    3. 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。

配置别名

1
2
3
4
5
git config --global alias.st status      # 为 git status 配置别名为 git st

git config --global alias.co checkout
git config --global alias.ci commit
git config --global alias.br branch

--global参数是全局参数,也就是这些命令在这台电脑的所有Git仓库下都有用

配置Git的时候,加上--global是针对当前用户起作用的,如果不加,那只针对当前的仓库起作用。

每个仓库的Git配置文件都放在本仓库的 .git/config文件中

别名就在[alias]后面,要删除别名,直接把对应的行删掉即可。

当前用户的Git配置文件放在用户主目录下的一个隐藏文件.gitconfig中,配置别名也可以直接修改这个文件。

搭建git服务器


本文内容源于廖雪峰的Git教程。
更多git笔记可参考github上对应的repo