Git 常用命令笔记

Git 的总结 Blog 很多,一般的都参考或学习廖雪峰曾写的 Git 教程
和 经典的 Pro Git,小白在此整理了 Git 的常用命令,是方便自己的复习和查阅,也希望能方便和我一样有此需求的人,O(∩_∩)O~

git status 命令可以让我们时刻掌握仓库当前的状态。

要随时掌握工作区的状态,使用 git status 命令。
如果 git status 告诉你有文件被修改过,用 git diff 可以查看修改内容。

版本回退

git log 命令显示从最近到最远的提交日志。

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

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

1
2
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed

重新恢复到新版本

只要右侧环境还在,就可以找到那个 append GPL 的 commit id 是 3628164…,于是就可以指定回到未来的某个版本:

1
2
$ git reset --hard 3628164
HEAD is now at 3628164 append GPL

版本号没必要写全,前几位就可以了,Git 会自动去找

  • HEAD 指向的版本就是当前版本,因此,Git 允许我们在版本的历史之间穿梭,使用命令 git reset –hard commit_id。
  • 穿梭前,用 git log 可以查看提交历史,以便确定要回退到哪个版本。
  • 要重返未来,用 git reflog 查看命令历史,以便确定要回到未来的哪个版本。

撤销修改

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

2:当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步:

第一步用命令 git reset HEAD file,就回到了 1;
第二步按 1 操作。

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

删除文件

一般情况下,你通常直接在文件管理器中把没用的文件删了,或者用 rm 命令删了:

$ rm test.txt

这个时候,Git 知道你删除了文件,因此,工作区和版本库就不一致了,git status 命令会立刻告诉你哪些文件被删除了。

现在你有两个选择,一是确实要从版本库中删除该文件,那就用命令 git rm 删掉,并且 git commit

1
2
3
4
5
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"

现在,文件就从版本库中被删除了。

另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:

$ git checkout -- test.txt

git checkout 其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。

命令 git rm 用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。

远程仓库

关联远程仓库

$ git remote add origin git@github.com:onlyone/learngit.git
注意:要关联一个远程库,使用命令 git remote add origin git@server-name:path/repo-name.git。

本地内容推送到远程库

本地库的所有内容推送到远程库上:

$ git push -u origin master

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


要关联一个远程库,使用命令 git remote add origin git@server-name:path/repo-name.git; 关联后,使用命令 git push -u origin master 第一次推送 master 分支的所有内容;此后,每次本地提交后,只要有必要,就可以使用命令 git push origin master 推送最新修改;

Git 支持多种协议,包括 https,但通过 ssh 支持的原生 git 协议速度最快

git 分支管理

首先,我们创建 dev 分支,然后切换到 dev 分支:

1
2
$ git checkout -b dev
Switched to a new branch 'dev'

git checkout 命令加上 -b 参数表示创建并切换,相当于以下两条命令:

1
2
3
$ git branch dev
$ git checkout dev
Switched to branch 'dev'

然后,用 git branch 命令查看当前分支:

1
2
3
$ git branch
* dev

master

git merge 命令用于合并指定分支到当前分支

前面所讲知识:汇总下这些使用命令:

查看分支:git branch

创建分支:git branch

切换分支:git checkout

创建+切换分支:git checkout -b

合并某分支到当前分支:git merge

删除分支:git branch -d

冲突

在上节中 master 分支和 feature1 分支各自都分别有新的提交,变成了这样:

这种情况下,Git 无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,执行 git merge feature1,在看
readme.txt:

<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

我们把冲突的内容修改为 Creating a new branch is quick and simple.,提交:

1
2
$ git add readme.txt 
$ git commit -m "conflict fixed"

现在,master 分支和 feature1 分支变成了下图所示:

用带参数的 git log 也可以看到分支的合并情况:

$ git log --graph --pretty=oneline --abbrev-commit

最后,删除 feature1 分支:

1
2
$ git branch -d feature1
Deleted branch feature1.

冲突解决,最后,删除 feature1 分支 git branch -d feature1。当 Git 无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。

git log --graph 命令可以看到分支合并图。

Bug 分支

 如果你有一个 bug 任务,你想创建一个分支 issue-101 来修复它,但是你当前正在 dev 上进行的工作还没有完成而不能提交,bug 需要现在修复,所以现在你需要暂停 dev 上工作,Git 提供了一个 stash 功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:$ git stash

  假定需要在 master 分支上修复,就从 master 创建临时分支:

$ git checkout master
Switched to branch 'master'
$ git checkout -b issue-101
Switched to a new branch 'issue-101'

  现在修复 bug,需要把 “Git is free software …” 改为 “Git is a free software …”,然后提交:

$ git add readme.txt 
$ git commit -m "fix bug 101"

  修复完成后,切换到 master 分支,并完成合并,最后删除 issue-101 分支:

$ git checkout master
...
$ git merge --no-ff -m "merged bug fix 101" issue-101
...
$ git branch -d issue-101
Deleted branch issue-101 (...).

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

  • 一是用 git stash apply 恢复,但是恢复后,stash 内容并不删除,你需要用 git stash drop 来删除;
  • 另一种方式是用 git stash pop,恢复的同时把 stash 内容也删了:

Feature 分支

在软件开发中,总会添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以每添加一个新功能,最好新建一个 feature 分支,在上面开发,完成后,合并,最后,删除该 feature 分支。

  现在新功能开发代号为 Vulcan:

$ git checkout -b feature-vulcan
Switched to a new branch 'feature-vulcan'

  开发完毕,添加并提交:

$ git add vulcan.py
$ git commit -m "add feature vulcan"

切回 dev,准备合并:

  一切顺利的话,feature 分支和 bug 分支是类似的,合并,然后删除。

  由于种种原因,此功能又不需要了,现在这个分支需要就地销毁:

$ git branch -d feature-vulcan
error: The branch 'feature-vulcan' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-vulcan'.

  销毁失败。Git 友情提醒,feature-vulcan 分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用命令 git branch -D feature-vulcan

  现在我们强行删除:

$ git branch -D feature-vulcan
Deleted branch feature-vulcan (was 756d4af).

  注意:开发一个新 feature,最好新建一个分支;如果要丢弃一个没有被合并过的分支,可以通过 git branch -D <name> 强行删除。

推送分支

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

  要查看远程库的信息,用 git remote

$ git remote
origin

  或者,用 git remote -v 显示更详细的信息:

$ git remote -v

推送分支
  推送分支,就是把该分支上的所有本地提交推送到远程库。推送时,要指定本地分支,这样,Git 就会把该分支推送到远程库对应的远程分支上:

git push origin master

  如果要推送其他分支,比如dev,就改成

git push origin dev
  • master 分支是主分支,因此要时刻与远程同步;
  • dev 分支是开发分支,团队所有成员都需要在上面工作,所以也需要与远程同步;
  • bug 分支只用于在本地修复 bug,就没必要推到远程了,除非老板要看看你每周到底修复了几个 bug;
  • feature 分支是否推到远程,取决于你是否和你的小伙伴合作在上面开发。

多人协作

 多人协作时,大家都会往 master 和 dev 分支上推送各自的修改。当你的同事也克隆一份此项目从远程库,默认情况下,只能看到本地的 master 分支。现在,你的同事要在 dev 分支上开发,就必须创建远程 origin 的 dev 分支到本地,于是他用这个命令创建本地 dev 分支:

$ git checkout -b dev origin/dev

  现在,他就可以在dev上继续修改,然后,时不时地把 dev 分支 push 到远程,并且已经向 origin/dev 分支推送了他的提交,这时你也对同样的文件作了修改,并试图推送,推送失败,因为你的同事的最新提交和你试图推送的提交有冲突,解决办法也很简单,Git 已经提示我们,先用 git pull 把最新的提交从 origin/dev 抓下来,然后,在本地合并,解决冲突,再推送,git pull 也失败了,原因是没有指定本地 dev 分支与远程 origin/dev 分支的链接,根据提示,设置 dev 和 origin/dev 的链接。

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

$ git commit -m "merge & fix hello.py"
[dev adca45d] merge & fix hello.py
$ 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 branch-name origin/branch-name

标签

在 Git 中打标签非常简单,首先,切换到需要打标签的分支上:

$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'

  然后,敲命令 git tag <name> 就可以打一个新标签:

$ git tag v1.0

  默认标签是打在最新提交的 commit 上的。还可以对历史提交打上标签,只要找到历史提交的 commit id,然后打上就可以了,例如要对 add merge 这次提交打标签,它对应的 commit id 是 6224937,输入命令:

$ git tag v0.9 6224937

  还可以创建带有说明的标签,用 -a 指定标签名,-m 指定说明文字:

$ git tag -a v0.1 -m "version 0.1 released" 3628164

  用命令 git show 可以看到说明文字:

$ git show v0.1
tag v0.1
Tagger: hubwiz <hubwiz@163.com>
Date:   Mon Aug 26 07:28:11 2015 +0800
version 0.1 released
</hubwiz@163.com>

  签名采用 PGP 签名,因此,必须首先安装 gpg(GnuPG),如果没有找到 gpg,或者没有 gpg 密钥对,就会报错:

gpg: signing failed: secret key not available
error: gpg failed to sign the data
error: unable to sign the tag

  如果报错,请参考 GnuPG 帮助文档配置 Key。

  • 命令 git tag <name> 用于新建一个标签,默认为 HEAD,也可以指定一个 commit id;
  • git tag -a <tagname> -m "blablabla..." 可以指定标签信息;
  • git tag -s <tagname> -m "blablabla..." 可以用 PGP 签名标签;
    命令 git tag 可以查看所有标签。

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

$ git tag -d v0.1
Deleted tag 'v0.1' (was e078af9)

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

如果要推送某个标签到远程,使用命令 it push origin <tagname>:

$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
 * [new tag]         v1.0 -> v1.0

或者,一次性推送全部尚未推送到远程的本地标签:

$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 554 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:michaelliao/learngit.git
 * [new tag]         v0.2 -> v0.2
 * [new tag]         v0.9 -> v0.9

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

$ git tag -d v0.9
Deleted tag 'v0.9' (was 6224937)

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

$ git push origin :refs/tags/v0.9
To git@github.com:michaelliao/learngit.git
 - [deleted]         v0.9

要看看是否真的从远程库删除了标签,可以登陆 GitHub 查看。

  • git push origin 可以推送一个本地标签;
  • git push origin –tags 可以推送全部未推送过的本地标签;
  • git tag -d 可以删除一个本地标签;
  • git push origin :refs/tags/ 可以删除一个远程标签。

 GitHub 不仅是免费的远程仓库,个人的开源项目,可以放到 GitHub 上,而且 GitHub 还是一个开源协作社区,通过 GitHub,既可以让别人参与你的开源项目,也可以参与别人的开源项目。

  在 GitHub 上,利用 Git 极其强大的克隆和分支功能,人们可以自由参与各种开源项目。比如人气极高的 bootstrap 项目,这是一个非常强大的 CSS 框架,在它的项目主页,点 “Fork” 就在自己的账号下克隆了一个 bootstrap 仓库,然后,从自己的账号下 clone。一定要从自己的账号下 clone 仓库,这样你才能推送修改。如果从 bootstrap 的作者的仓库地址 git@github.com:twbs/bootstrap.git 克隆,因为没有权限,你将不能推送修改。

  Bootstrap 的官方仓库 twbs/bootstrap、你在 GitHub 上克隆的仓库 my/bootstrap,以及你自己克隆到本地电脑的仓库,他们的关系就像下图显示的那样:

如果你希望 bootstrap 的官方库能接受你的修改,你就可以在 GitHub 上发起一个 pull request。

  • 在 GitHub 上,可以任意 Fork 开源仓库;
  • 自己拥有 Fork 后的仓库的读写权限;
  • 可以推送 pull request 给官方仓库来贡献代码。

自定义 Git

实际上,Git 还有很多可配置项。

比如,让 Git 显示颜色,会让命令输出看起来更醒目:

$ git config --global color.ui true

这样,Git会适当地显示不同的颜色

忽略特殊文件

有些时候我们需要把一些文件例如:保存了数据库密码的配置文件放在 Git 目录下,但又不提交,那么需要我们在 Git 工作区的根目录下创建一个特殊的 .gitignore 文件,然后把要忽略的文件名填进去,Git 就会自动忽略这些文件。

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

忽略文件的原则是:

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

配置别名

配置别名其实就是把命令重新设置简单些,方便输入,例如:如果输入 git st 就表示 git status。

$ git config --global alias.st status

现在都用 co 表示 checkout,ci 表示 commit,br 表示 branch:

$ 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 文件中:

$ cat .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = git@github.com:michaelliao/learngit.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[alias]
    last = log -1

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

而当前用户的 Git 配置文件放在用户主目录下的一个隐藏文件 .gitconfig 中:

$ cat .gitconfig
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
[user]
    name = Your Name
    email = your@email.com

配置别名也可以直接修改这个文件,如果改错了,可以删掉文件重新通过命令配置。

搭建 Git 服务器

搭建 Git 服务器需要准备一台运行 Linux 的机器,强烈推荐用 Ubuntu 或 Debian,这样,通过几条简单的 apt 命令就可以完成安装。

假设你已经有 sudo 权限的用户账号,下面,正式开始安装。

第一步,安装 git:

$ sudo apt-get install git

第二步,创建一个 git 用户,用来运行 git 服务:

$ sudo adduser git

第三步,创建证书登录:

收集所有需要登录的用户的公钥,就是他们自己的 id_rsa.pub 文件,把所有公钥导入到 /home/git/.ssh/authorized_keys 文件里,一行一个。

第四步,初始化 Git 仓库:

先选定一个目录作为 Git 仓库,假定是 /srv/sample.git,在 /srv 目录下输入命令:

$ sudo git init --bare sample.git

  Git 就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的 Git 仓库通常都以 .git 结尾。然后,把 owne r改为 git:

$ sudo chown -R git:git sample.git

第五步,禁用 shell 登录:

出于安全考虑,第二步创建的 git 用户不允许登录 shell,这可以通过编辑 /etc/passwd 文件完成。找到类似下面的一行:

git:x:1001:1001:,,,:/home/git:/bin/bash

改为:

git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell

这样,git用户可以正常通过 ssh 使用 git,但无法登录 shell,因为我们为 git 用户指定的 git-shell 每次一登录就自动退出。

第六步,克隆远程仓库:

现在,可以通过 git clone 命令克隆远程仓库了,在各自的电脑上运行:

$ git clone git@server:/srv/sample.git
Cloning into 'sample'...
warning: You appear to have cloned an empty repository.

管理公钥和管理权限

管理公钥

  如果团队很小,把每个人的公钥收集起来放到服务器的 /home/git/.ssh/authorized_keys 文件里就是可行的。如果团队有几百号人,就没法这么玩了,这时,可以用 Gitosis 来管理公钥。

管理权限

  有很多不但视源代码如生命,而且视员工为窃贼的公司,会在版本控制系统里设置一套完善的权限控制,每个人是否有读写权限会精确到每个分支甚至每个目录下。因为 Git 是为 Linux 源代码托管而开发的,所以 Git 也继承了开源社区的精神,不支持权限控制。不过,因为 Git 支持钩子(hook),所以,可以在服务器端编写一系列脚本来控制提交等操作,达到权限控制的目的。 Gitolite 就是这个工具。

这里我们也不介绍 Gitolite 了,不要把有限的生命浪费到权限斗争中。

主要两点:

  • 要方便管理公钥,用 Gitosis;
  • 要像SVN那样变态地控制权限,用 Gitolite。