Idle Works, Idle Thoughts

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仓库之间。

.Git暂存区

每一次commit前,都要把相关的文件add到暂存区,然而再对暂存区中的文件commit到Git仓库。

.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图示:

.git 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切换分支

.git checkout branch

切换分支的核心工作:把HEAD指针指向新分支(即所谓的我们切换到那个分支了);让暂存区和工作目录的文件和HEAD指向的新分支保持一致,即新分支的所有文件复制到暂存区和工作目录,只存在于旧分支的所有文件被删除,不属于新旧分支的文件依然被视为untracked files,不受影响。

如果直接checkout一个commit,而不是分支名,就会得到一个匿名分支。例如checkout一个tag、SHA-1值或像master~3、HEAD~之类的东西,就会得到匿名分支,也被称为detached HEAD

.git checkout detached

如上图所示状态,此时就处于detached HEAD state。如果你在次状态下修改文件,并提交,你就是在更新一个匿名分支,当之后切换到其他分支时,这条匿名分支上的提交节点可能就再也不会被引用到:

.git commit detached

如上图,起初切换到b325c,然而提交形成新节点2eecb,此时若切换分支,则2eecb可能再也不会被引用到:

.git checkout after detached

如果想要保留这个状态,以便之后引用,可以在完成提交后,切换分之前,为它创建一个分支:

$ git checkout new

.git checkout new before detached

用git checkout撤销

恢复指定commit的指定文件到工作区:

$ git checkout <commit> <file>

.git checkout files

恢复暂存区文件到工作区(放弃工作区的修改):

$ 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命令:

Git命令

以上涉及到的几个专用名词:

文件通常是:加入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选项:

如:

$ 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>