工具
Web 开发
Web 服务
Git 学习笔记
Git是一个分布式版本管理系统。
一、配置Git用户信息
使用git之前,先配置自己的名字和邮箱,因为每次代码提交都需要提交者信息:
$ git config --global user.name "White"
$ git config --global user.email "white@example.com"
可以通过 git help everyday
查看最常用的 Git 命令说明。
二、新建代码库
1. 在当前目录中新建一个git代码库:
$ git init
这个命令会在当前路径下新建一个.git
文件夹,并在其中记录Git仓库所需的全部元数据(SVN会在每个子目录下创建.svn文件夹)。
2. 新建一个目录,并将其初始化为git代码库:
$ git init <project>
这个命令会创建一个新目录,并在新目录中创建一个.git
子目录。
3. 新建一个目录,且不将其作为工作目录:
$ git init --bare <project>
这个命令创建的仓库叫做Bare Repository,作为中央仓库使用,且此新项目通常以.git
作为后缀,例如idleworks.git
。只有中央仓库(不直接在这个仓库中编辑代码)才以--bare
的形式创建,所有开发者的本地仓库都是non-bare repository。
4. 下载一个项目以及它的整个代码历史:
$ git clone <url>
$ git clone <url> <project>
git clone
实际上封装了多个git命令。它首先创建了一个新目录,并在新目录中用git init
来初始化一个空的Git仓库,根据你指定的URL,用git remote add
添加一个远程仓库,并将此远程仓库命名为origin
。然后对远程仓库执行git fetch
,最后通过git checkout master
将远程仓库的最新提交检出到本地的工作目录。
在开发机B获取开发机A上的代码:
$ git clone ssh://username@ip:/codes/idle
三、Git仓库配置
Git的配置文件通常在主目录下~/.gitconfig
,项目也有自己的配置文件.git/config
。
显示当前的Git配置:
$ git config --list
修改配置:
$ git config -e
$ git config -e --global
四、增删文件
添加指定文件到暂存区:
$ git add [file1] [file2] ...
添加指定目录到暂存区,包括子目录:
$ git add [dir]
删除工作区文件,并将这次删除放入暂存区:
$ git rm [file1] [file2] ...
修改文件名,并将这次修改放入暂存区:
$ git mv [src] [dst]
五、提交代码
在Git中,所有提交都是对暂存区的文件做一个快照。暂存区是一个抽象层,处于工作目录和Git仓库之间。
每一次commit前,都要把相关的文件add到暂存区,然而再对暂存区中的文件commit到Git仓库。
提交代码执行的命令是:
$ git add <files>
$ git commit -m <message>
为什么要抽象出暂存区?当你对多个文件修改后,可以组织逻辑上相关文件的分批提交,这就是暂存区的核心功能。例如,你修改了Payment、Deposit、Withdraw这几个模块的相关代码,为了形成有逻辑的提交,可以如此操作:
$ git add payment.h payment.c
$ git commit -m "update user payment interface and impl."
$ git add deposit.h deposit.c
$ git commit -m "support deposit with PayPal"
$ git add withdraw.c
$ git commit -m "support withdraw to Alipay"
当然,你可以一次性把所有修改都提交:
$ git add --all
$ git commit -m "changed payment, deposit, and withdraw codes"
可以把以上两个命令合并成一行:
$ git commit -a -m <commit messages>
git commit -a
表明把所有有修改的文件都自动放入暂存区,并作为一个整体提交。这个命令表面上跳过了暂存区,其实是git自动帮你做了这一步。然而,git commit -a只能帮你把Git已知的文件放入暂存区,也就是你曾git add并commit过的文件,是Git已知的文件,对已知文件进行删除或修改都OK,但对于全新的文件,必须要手工执行git add,再执行git commit -a方可。
六、本地分支
列出本地所有分支:
$ git branch
创建新分支:
$ git branch <branch>
切换到分支:
$ git checkout <branch>
以上两步(创建和切换)可以合并为一个命令:
$ git checkout -b <branch>
重命名分支:
$ git branch -m old new
重命名当前分支:
$ git branch -m new
合并分支:
$ git merge <branch>
删除分支:
$ git branch -d <branch>
显示所有已合并到当前分支的所有分支:
$ git branch --merged
显示还未合并到当前分支的所有分支:
$ git branch --no-merged
从其他分支迁出文件:
$ git checkout <branch> -- <files>
从其他分支获取文件:
$ git checkout <branch> <file>
例如从 crawler 分支获取 remote.php 文件:
$ git checkout crawler libs/remote.php
七、远程分支
列出所有远程分支:
$ git branch -r
列出本地和远程所有分支:
$ git branch -a
例如clone Github上Bootstrap的代码仓库,并对比本地分支和远程分支:
$ git clone https://github.com/twbs/bootstrap.git
Cloning into 'bootstrap'...
$ git branch -r
origin/HEAD -> origin/master
origin/gh-pages
origin/master
origin/v4-dev
...
$ git branch
* master
git clone只检出了master分支,可以单独检出其他分支:
$ git checkout -b v4 origin/v4--dev
$ git branch
master
* v4
也可以先执行git fetch,再checkout分支:
$ git fetch
$ git checkout gh-pages
查看远程仓库:
$ git remote -v
添加远程分支:
$ git remote add web ssh://example.com/repo/web.git
对比本地分支和远程分支:
$ git diff <local branch> <remote branch>
如:
$ git diff master web/master
推送到远程分支(把本地的master分支推送到远程web/master分支):
$ git push web master
从远程分支拉取代码(用web/master分支的代码覆盖本地的master分支):
$ git pull web master
删除远程分支:
$ git remote rm web
当远程分支的URL变化时,更新之:
$ git remote set-url web ssh://new.url/repo/web.git
把本地的develop分支推送到远程的master分支:
$ git push origin develop:master
即如下命令:
$ git push <remote> <local branch>:<remote branch>
八、标签
列出所有标签:
$ git tag
基于当前commit,新建一个标签:
$ git tag <tag>
基于指定commit,建立一个标签:
$ git tag <tag> <commit>
查看tag信息:
$ git show <tag>
提交指定tag:
$ git push <remote> <tag>
基于tag新建分支:
$ git checkout -b <branch> <tag>
删除标签:
$ git tag -d <tag>
删除远程标签:
$ git push origin :refs/tags/<tag>
标签示例
创建标签:
$ git tag v.0.2
删除标签
$ git tag -d v.0.2
把标签推送到服务器:
$ git push origin v.0.2
在 GitHub 查看标签:进入项目主页后,点击 Releases,即可查看所有标签。可直接下载此标签对应的 .zip 或 .tag.z 文件。
九、查看信息
查看工作目录的文件变化:
$ git status
git status会汇报哪些文件被添加、删除或修改了,以及哪些文件还未加入Git仓库。对于那些我们不想让 git 关心的文件和目录,应该写在:
.gitignore
文件中。其格式如:
$ cat .gitignore
_temp/
*.swp
以简短格式输出status:
$ git status -s
查看当前分支的版本历史(输出提交信息和备注):
$ git log
git log的常用参数:
命令 | 说明 |
---|---|
git log –oneline | 一行一个提交 |
git log –stat | 列出commit时发生变动的文件(变动行数统计) |
git log -p |
查看具体修改历史 |
git log –author=”name” | 按提交者查看 |
git log –grep=”expr” | 在提交备注中搜索关键字 |
git log –graph –decorate –oneline | 图形化显示历史变迁 |
显示暂存区和工作区的差异:
$ git diff
所有的提交记录都可以拿来diff,常用的diff图示:
默认diff会输出每个差异,如果只需要输出文件名,可以用:
$ git diff --name-only BranchName
如果只diff某一个文件,可以用:
$ git diff BranchName -- FileName
显示最近提交的commit:
$ git reflog
显示每次修改涉及到的文件名和其状态:
$ git log --name-status
如:
$ git log --name-status books/
...
A books/naruto.php
M books/side.php
以下参数用来过滤日志,只显示符合过滤条件的日志。
--grep=<pattern>
在 log messages 中搜索 pattern,如:
$ git log --grep="database.*flag"
在 log messages 中搜索修改了数据库标签的提交。要列出具体修改的文件,可以配合 –name-status 参数,如:
$ git log --name-status --grep="database.*flag"
十、撤销
用 git revert 回退到之前的一个提交状态:
$ git revert <commit>
这个命令不是删除之前的 commit,而是还原 commit 引起的修改。git revert 本身会创建一个新的 commit。
撤销一系列的提交:
$ git revert <oldest-commit>..<lastest-commit>
这条命令有点奇葩,它实际执行的撤销区间是:(oldest-commit, lastest-commit + 1],即:[oldest-commit + 1, lastest-commit + 1]。
找回一个已被删除的文件,先找到最近的一个提交,再 checkout 之:
$ git rev-list -n 1 HEAD -- <file>
$ git checkout <commit>^ -- <file>
git checkout
checkout主要用于切换分支,从历史提交或暂存区中拷贝文件到工作目录(撤销工作目录里的修改)。
用git checkout切换分支
切换分支的核心工作:把HEAD指针指向新分支(即所谓的我们切换到那个分支了);让暂存区和工作目录的文件和HEAD指向的新分支保持一致,即新分支的所有文件复制到暂存区和工作目录,只存在于旧分支的所有文件被删除,不属于新旧分支的文件依然被视为untracked files,不受影响。
如果直接checkout一个commit,而不是分支名,就会得到一个匿名分支。例如checkout一个tag、SHA-1值或像master~3、HEAD~之类的东西,就会得到匿名分支,也被称为detached HEAD
。
如上图所示状态,此时就处于detached HEAD state
。如果你在次状态下修改文件,并提交,你就是在更新一个匿名分支,当之后切换到其他分支时,这条匿名分支上的提交节点可能就再也不会被引用到:
如上图,起初切换到b325c,然而提交形成新节点2eecb,此时若切换分支,则2eecb可能再也不会被引用到:
如果想要保留这个状态,以便之后引用,可以在完成提交后,切换分之前,为它创建一个分支:
$ git checkout new
用git checkout撤销
恢复指定commit的指定文件到工作区:
$ git checkout <commit> <file>
恢复暂存区文件到工作区(放弃工作区的修改):
$ git checkout <file>
恢复上一个commit所有文件到工作区:
$ git checkout .
git reset
从暂存区中删除文件:
$ git reset HEAD -- <file>
通常用在错误的添加文件到Git仓库后,要把它删除之,例如:
$ git add copy/a.out
$ git reset HEAD -- copy/a.out
重置暂存区的指定文件,与上一次commit保持一致,但不要动工作区的文件:
$ git reset <file>
充值暂存区与工作区的指定文件,与上一次commit保持一致:
$ git reset --hard <file>
十一、其他
从Git仓库创建一个干净的代码树(不包括 .git 目录):
$ git archive master | bzip2 > /tmp/idleworks.tar.bz2
十二、在服务器上搭建Git仓库
起初我们在自己的开发机上建立了Git仓库,等到我们要和其他人协作开发时,可以把现有仓库导出为裸仓库,并放到服务器上。例如,我们正在开发一个名为js的项目。
1、用--bare
将现有项目导出为裸仓库:
$ git clone --bare js js.git
这步基本等同于:
$ cp -rf js/.git js.git
2、将裸仓库复制到服务器上:
$ scp -r js.git user@example.com:/opt/codes/js.git
3、在服务器上有访问权限的开发者可以clone项目到自己的开发机上:
$ git clone user@example.com:/opt/codes/js.git
Git进阶
最常用的6个Git命令:
以上涉及到的几个专用名词:
- Workspace - 工作区。
- Index/Stage - 暂存区。
- Repository - 本地仓库。
- Remote - 远程仓库。
文件通常是:加入Git仓库(git add)→ 修改后即位于暂存区 → 提交到本地库(git commit) → 推送到远程库(git push)。
快照(snapshot)
Git与其他版本控制系统的区别在于:Git只关心文件是否变化,而不关心文件内容的变化。大多数版本控制系统都会忠实地记录版本间的文件差异(diff),但Git不关心这些具体差异(哪一行有什么变动),Git只关心哪些文件修改了,哪些没有修改,修改了的文件直接复制形成新的blob(这就是所谓的快照snapshot)。
当你需要切换到或拉出一个分支时,Git就直接加载当时的文件快照即可,这就是Git快的原因。说起来,这也是用空间换取时间的经典案例。从这个角度看,Git更像是一个小型文件系统,并在这个系统上提供一系列的工具来辅助开发。
post-receive
如果$GIT_DIR/hooks/post-receive
存在且可执行,在所有的refs更新完成后,只要有refs更新成功,就会自动调用这个脚本。
git-config
当Linux程序员和Windows程序员协作时,运行git diff
会显示^M
。可以用以下命令忽略:
$ git diff --ignore-space-at-eol -b -w --ignore-blank-lines
-b 等同于--ignore-space-change
, -w 等同于--ignore-all-space
.
以上命令太长,可以配置git忽略换行符差异:
$ git config --global core.whitespace cr-at-eol
因为Windows的换行符为CR+LF,而Linux的换行符为LF。可以设置core.autocrlf
选项:
- Checkout Windows-style, commit Unix-style (core.autocrlf = true)
- Checkout as-is, commit Unix-style (core.autocrlf = input)
- Checkout as-is, commit as-is (core.autocrlf = false)
如:
$ git config --global core.autocrlf true
Git 问题集锦
一、强制把一个分支 merge 到 master
有一个名为 seo 的分支,在 merge 到 master 时出现问题,强制 merge 的方法:
$ git checkout seo
$ git merge -s ours master
$ git checkout master
$ git merge seo
此处 -s ours
是 --strategy=ours
的简写。
GitHub
如果你在多个终端编辑代码,注意user.email
的配置要与GitHub账号的Primary Email一致,否则不会纳入到Contributions中。
$ git config user.email <your-github-primary-email>