Git Introduction
git
基本概念
Git是一个分布式版本控制软件,Linus Torvalds在2005年用十天时间编写出第一个git版本。相比于集中式版本控制软件,不需要服务端软件就能实现版本控制。
Git最出色的是其合并追踪能力,我在实际使用中发现,借助Emacs编辑器magit
插件可以非常优雅的处理rebase
和merge
过程中产生的冲突。
日常操作中,需要熟练的功能主要有:环境配置,远程管理,分支管理,错误追踪等。
推荐在Emacs下利用magit
插件做基本仓库管理,相比于直接用命令行,利用插件更加能够方便把代码和版本管理合二为一。例如差异区和代码之间跳转,选择性提交差异区等操作都比直接用命令行快捷很多。
每一个仓库都有一个.git
目录,该目录下常见文件及功能如下表所示:
文件 | 功能 |
---|---|
branches/ | 分支信息,该目录已不再使用 |
config | 配置信息 |
description | 描述信息 |
HEAD | 头指针 |
hooks/ | 用于在特定事件下触发的脚本 |
info/exclude | 用于指定要忽略的文件 |
objects/ | 数据对象,包括commits、trees、blobs、tags等 |
refs/ | 包括分支指针,远程指针,标签指针等 |
基本使用
环境配置
- 初始化配置
git init # 初始化仓库 git init --separate-git-dir <gitdir> # 初始化分离仓库 git config [--global] user.name "Micky Ching" git config [--global] user.email "mickyching@gmail.com"
- separate-git-dir
- 对于重要项目,我个人比较喜欢用分离仓库,这样可以较好的防止对代码批处理时错误的修改仓库数据库。
- global
- 使用该选项会将配置信息写入用户配置文件中,不使用则将配置信息写入当前仓库配置文件中。
- user.name
- 由于提交需要提交者信息,所以配置用户名和邮箱是很有必要的。
另外往往需要配置哪些文件需要忽略,这通过
.gitignore
文件来记录要忽略的文件或规则。 - 提交代码
仓库一旦初始化就可以添加文件并提交了,更高级的可以选择添加一个diff区或一行修改等等。
git add <files> # 添加文件/文件夹 git add -i # 交互式添加文件 git commit # 提交,启动编辑器编辑提交信息 git commit -m <message> # 提交,直接写入提交信息 git commit --amend # 提交,修改上一次提交
仓库中的文件有三种状态,其转换关系如下图所示:
在首次提交前,添加了错误文件时,由于没有HEAD,不能使用
git reset HEAD
撤销,此时可以使用如下命令来撤销添加。git rm -r --cached <files> # 撤销add添加的文件
- 信息查看
git status # 查看项目状态 git log # 查看日志 git diff <commit> # 查看相对于指定提交的更改 git ls-files # 查看当前目录下被跟踪的文件 git ls-tree -r HEAD # 查看当前目录下被跟踪的文件 # 查看所有被跟踪过的文件,包括已经被删除的文件 git log --pretty=format: --name-only --diff-filter=A | sort - | sed '/^$/d'
对于日志显示,通常需要在配置文件中指定显示格式,以便阅读,需要注意的是
--graph
选项可能会影响速度,尤其对于大型仓库。我定义了如下两个alias
命令来查看日志:ll = log --pretty=format:'%Cred%ad %h%Cgreen%d%<|(100,trunc) %Creset%s %C(cyan)%an <%ae>'\ --color=auto --date=short --abbrev-commit lg = log --pretty=format:'%Cred%ad %h%Cgreen%d%<|(100,trunc) %Creset%s %C(cyan)%an <%ae>'\ --color=auto --date=short --abbrev-commit --graph
下图展示了用于查看不同节点间差异的方法。
远程管理
- 远程配置
创建ssh密钥以避免每次上传下载都要输入密码:
ssh-keygen -t rsa -C "mickyching@gmail.com" -f ~/.ssh/micky-gmail ssh-add ~/.ssh/micky-gmail # 添加密钥 ssh -i ~/.ssh/micky-gmail -T git@github.com # 验证测试
如果不添加密钥,可能会出现如下错误提示:
Agent admitted failure to sign using the key
如果要配置多个远程仓库帐号,需要编辑配置文件
~/.ssh/config
指定登录信息。Host github # 主机名,可任意命名 HostName github.com # 登录地址 User git Port 22 IdentityFile ~/.ssh/micky-gmail # 证书路径
最后在远程服务器上设置好公钥,就可以无需输入密码实现上传和下载了。
- 远程操作
git remote add <repo-name> <repo-url> # 添加远程仓库地址 git remote -v # 查看远程仓库地址 git remote set-url <repo-name> <url> # 更改仓库地址 git pull <repo-name> <branch> # 拉取远程仓库分支到当前分支 git fetch <repo-name> # 下载远程仓库 git fetch <repo-name> <a>:<b> # 将远程分支a下载为分支b git push <repo-name> <branch> # 将当前分支发送到远程分支 git push <repo-name> <a>:<b> # 将分支a发送到远程分支b git push <repo-name> :<b> # 删除远程分支 git push --tags # 推送tag git remote set-head origin --auto # 可用于修复remote/HEAD失效
分支管理
- 分支管理
git branch [-vv] # 查看分支,冗余模式能显示跟踪对象 git branch <branchname> # 创建分支 git branch -d <branchname> # 删除已合并的分支 git branch -D <branchname> # 强制删除分支 git branch -dr <remote/branch> # 删除本地下载的远程分支 git remote prune <remote> # 删除远程已经不存在的跟踪分支 git checkout <branchname> # 切换分支 git checkout <commit> -- <filename> # 检出指定文件 git rebase <branch-b> # 基于指定分支: ours=branch-b git merge <branch-b> # 合并指定分支: ours=branch-a git reset <commit> # 复位到指定提交 git revert <commit> # 反转提交
通常来说,创建一个分支往往是为了单独实现一个较大的功能,在实现完成之后再合并到主分支。
rebase
和merge
的区别在于rebase
之后只有一条主线,而merge
之后仍然保留多条主线,只不过最后将其汇合为一点。仓库管理的基本原则是merge
越少越好,所以提交之前需要在本地做好rebase
再推送,因为当你打算推送的时候,远程可能已经被别人修改了,此时最佳策略就是将本地rebase
到远程最新提交点再提交。同样的道理,用
reset
的方法废除一个提交不会在仓库中留下痕迹,但是revert
实际上保存了两个提交。原则上仓库中revert
越少越好,所以如果一个提交有问题,需要彻底废除,在未推送到远程之前,最佳做法是通过reset
将其废除,如果已经推送,那么只能通过revert
来废除原来的提交。其实对于提交也需要同样注意,尽量做到每个提交都很好的完成一个提交的任务,如果代码改动很大,就要做成多个独立的提交。简单的说,每个提交要保证其正确性、可用性、可读性,不要滥用提交。
- 标签管理
git tag <v0.1> # 添加tag git tag # 列举tag
错误追踪
如果要查询一个文件在何时由谁引入,或者文件中某一行何时由谁引入,可以用如下命令:
git blame <filename> # 查看每一行在何时引入 git blame -L n,m <filename> # 查看文件指定行何时引入
- -L n,m
- 其中n表示起始行,m表示显示行数。
要查询某个错误在哪个提交引入的,可以尝试二分查找方法:首先标定一个起始位置,然后通过二分检出验证并标记状态的方式搜索。
git bisect start [<bad> <good>] # 开始二分查找 git bisect reset # 结束二分查找,回到开始前的位置 git bisect good [<commit>] # 标记当前为good git bisect bad [<commit>] # 标记当前为bad git checkout bisect/bad # 切换到最终定位的bad git bisect log # 查看二分日志,可以用重定向保存 git bisect replay <file.log> # 从二分日志恢复进度 git bisect HEAD1 HEAD2 # 标记查询区间
实用操作
- 暂存管理
git stash # 添加当前修改到暂存区 git stash list # 列举暂存区 git stash pop # 弹出暂存区
- 仓库清理
git clean -ndx # 显示要删除的文件列表 git clean -fdx # 删除仓库之外的文件 git gc # 垃圾清理,建议少用
所有的清理动作都应该慎重。
- 选取提交
git cherry-pick <commit> # 选取指定提交放到当前位置
- 仓库打包
git archive --prefix="emacs/" -o emacs.zip HEAD git archive -o emacs-partial.tar HEAD lisp/ site-lisp/ git archive --format=tar --prefix="emacsd/" master | gzip > emacsd.tar.gz
repo
基本用法
对于多个仓库的管理,git自带有submodule,但是并不好用。这里推荐repo
来管理多个仓库,谷歌在Android项目中的使用已经展现了repo优秀的能力。
基本命令如下:
repo help # 获取帮助信息 repo help command # 获取指定命令的帮助信息 repo init -u URL # 初始化下载地址 repo init -u URL -b <branch> # 初始化下载仓库的某个分支 repo status # 查看所有仓库的状态 repo branches # 查看每个仓库分支信息 repo sync # 同步所有仓库 repo sync [project-list] # 同步指定仓库 repo update [project-list] # 上传指定仓库 repo diff [project-list] # 查看修改 repo start <branch> # 为每个仓库创建分支 repo prune <branch> # 删除已经merge的分支 repo abandon <branch> # 强制删除分支 repo manifest # 生成manifest文件 repo foreach [project-list] -c command # 对每个仓库执行指定命令 repo forall -c command # 不限于git命令 repo version # 查看repo版本信息
在网络不好的情况下调用repo sync
经常会中途失败,可以用如下脚本来多次同步:
repo sync while [ $? -ne 0 ] do repo sync done
在调用repo status
查看的时候前两个字符分别表示暂存区和工作区状态,具体如下表所示:
第1个字符 | 暂存区状态 | 第2个字符 | 工作区状态 |
---|---|---|---|
- | 没有文件被修改 | - | 未更改 |
A | 有文件添加 | m | 已更改 |
D | 有文件删除 | d | 已删除 |
M | 有文件更改 | ||
R | 有文件重命名 | ||
C | 有文件被复制 | ||
T | 有文件模式被修改 | ||
U | 有冲突未处理 |