赞
踩
本文是关于Git探索的一篇文章,阐述了Git的大部分命令和使用方式,并列举了几个典型的使用场景以供参考和体会。
多host环境
clone,remote,fetch,pull,push
gitignore
add
stash
commit
status,log,diff,blame,grep
branch,checkout,merge
tag
cherry-pick
checkout,reset,revert,rm
rebase
mv,repack,bisect
Other Case
对于Git这个分布式的VCS,从链表的角度来看待是最容易理解的:
一次commit
相当于添加一个节点,节点由hash
标识,内容就是所做修改的索引;每个分支都是一条链,有一个指向头结点的指针HEAD
。
下载地址:点我跳转下载
以Windows为例:
%GIT_HOME%\bin;%GIT_HOME%\usr\bin;
ssh-keygen -t rsa -C "example@abc.com"
会提示输入路径,建议:
/c/User/用户名/.ssh/id_rsa_abc
添加多host环境,对于在公司使用自己的机器是很有必要的,公私分明嘛~
文件位置/C/User/用户名/.ssh/config
,没有就新建一个:内容如下
# 配置git.oschina.net
Host git.oschina.net
HostName git.oschina.net
IdentityFile C:\\Users\\用户名\\.ssh\\id_rsa_oschina
PreferredAuthentications publickey
User oschinaUserName
# 配置github.com
Host github.com
HostName github.com
IdentityFile C:\\Users\\用户名\\.ssh\\id_rsa_github
PreferredAuthentications publickey
User githubUserName
测试连接:
连通后会生成 /C/User/用户名/.ssh/known_hosts
文件:
ssh -T git@github.com
ssh -T git.oschina.net
这样就可以把公钥 id_rsa_xxx
添加到git服务器上的 SSH Keys
中去了。
最后可按需要,顺带配置用户信息:
全局配置:
git config --global user.name 姓名
git config --global user.email 邮箱
为当前仓库设置用户信息:
git config --local user.name 姓名
git config --local user.email 邮箱
一般仓库可以采用SSH方式关联,但必须以HTTP/HTTPS方式关联远程仓库时,可以考虑记住密码:在用户目录下生成文件.git-credential
记录用户名密码的信息。
# 默认会话时长(据说是15min):
git config --local credential.helper cache # linux/mac
git config --local credential.helper wincred # windows
# 自己定义会话时间:
git config credential.helper 'cache --timeout=3600' # linux/mac
git config credential.helper 'wincred --timeout=3600' # windows
# 不过期会话时间:
git config --local credential.helper store
至此,Git配置结束。
Git相关的命令如果记不太清楚可以使用提示:
git --help
git 具体某条命令 --help
在当前目录初始化一个仓库:
git init
克隆一个远程仓库:
git clone <URL> [--branch <branch> --depth=1] [./dir]
# 准备后续免密push的操作(字符需要Escape方式编码,git remote -v可看到密码)
git clone http://账号:密码@git.ops.test.com.cn/root/puppet.git
添加子module,这样子文件夹会做文件处理:
git submodule add <URL> <./submodule>
远程仓库关联:
git remote [-v]
git remote add <name> <URL>
git remote set-url <name> <URL>
git remote remove <name>
拉取本地仓库没有的内容(新增分支,远程分支修改等):
git fetch [origin <branch>]
拉取本分支的更新并合并:pull = fetch + merge
,默认拉去所有合并到对应分支,当指定远程分支时默认合并到当前分支:
git pull origin <remote_branch>[:<local_branch>]
准备推送本分支的修改到远程仓库前可以先查看本地分支和远程分支的关联情况:
git branch -vv
已经关联远程分支可以直接push:
git push
远程分支不存在:
git push [-u] origin <local_branch>:<remote_branch>
# -u 表示添加关联
只关联不push:
git branch --set-upstream-to <origin/remote_branch> <local_branch>
编辑仓库中的 .gitignore 文件,内容格式如下
vim .gitignore
# 忽略目录
build/
# 忽略yml后缀文件
*.yml
# 不忽略a.yml
!a.yml
gitignore
只能忽略untracked
文件,对于stracked
文件需要删除索引:
git rm --cached [-r] <file>
Git仓库中文件大概有如下几种状态,并可通过相应的操作切换:
保存(new-untracked)和(modified),不包括(remove-untracked):
git add .
保存(remove-untracked)和(modified),不包括(new-untracked):
git add -u
保存所有变化:
git add -A
部分提交,后续会有选择,舍弃的修改将留在working directory:
git add -p filename
# y - 存储这个hunk
# n - 不存储这个hunk
# q - 离开,不存储这个hunk和其他hunk
# a - 存储这个hunk和这个文件后面的hunk
# d - 不存储这个hunk和这个文件后面的hunk
# g - 选择一个hunk
# / - 通过正则查找hunk
# j - 不确定是否存储这个hunk,看下一个不确定的hunk
# J - 不确定是否存储这个hunk,看下一个hunk
# k - 不确定是否存储这个hunk,看上一个不确定的hunk
# K -不确定是否存储这个hunk,看上一个hunk
# s - 把当前的hunk分成更小的hunks
# e - 手动编辑当前的hunk
# ? - 输出帮助信息
这样可以保持分支的工作现场干净,操作对象是整个staging area
:
# push
git stash
# pick
git stash apply
# pop
git stash drop
# pop & pick
git stash pop
提交:
git commit [filename] -m "annotation"
提交working directory
,相当于:git add -u + git commit -m "boom~"
:
git commit -am "boom~"
撤销HEAD并提交staging area
补充到HEAD提交:
git commit -amend -m "make up"
查看staging area
和 working directory
的状态:
git status
查看提交记录:
git log --online --graph --all
# all: 所有分支(默认当前)
# oneline: 简易hash, --pretty=oneline
# graph: 画出合并切出图
操作记录,含reset
git reflog --oneline
文件对比:
对比 (staging area
| repository
) 和 working directory
:
git diff [branch1 branch2] <file>
# 加branch会对比两个分支中的文件
对比 repository
和 working directory
:
git diff HEAD <file>
对比 repository
和 staging area
:
git diff --cached <file>
查看修改人:
git blame <file>
查找内容:
git grep '[123]\{1,\}'
分支可以理解为链表,链表中的元素是commit
。
查看分支:
git branch [-a]
# -a 包含远程分支
切出分支:
git checkout [-b <new_branch>] origin/release|$hash$|tags/v1.0
合并develop
到master
:
合并前检查是否存在冲突:
git diff master develop --stat
开始合并,并解决冲突:
(master)$ git merge [--squash] develop
# --squash 融合提交记录,参见rebase
# 对于无法自动合并的冲突需要手动处理
<<<<<< HEAD
# 当前更改
======
# 传入的更改
>>>>>> dev
提交前检查冲突是否处理完毕:
git diff --check
# 如果没问题就可以 commit -am 了
如果冲突太多太麻烦,想放弃解决了,可以终止:
git merge --abort
删除分支:
# -r表示一并删除origin里面分支,这只是删除了一个本地追踪,下次不会再fetch它:
git branch -d [-r] <branch_name>
# 删除远程的分支还需要push过去删除
git push origin -d <remote_branch>
Fetch仓库TAG:
git fetch origin --tags
查看TAG:
git tag
git tag -l 'v1.0.*'
git show v1.0.0
在当前分支HEAD打TAG:
git tag v0.1
# 在某个commit上打tag,并加附注
git tag -a v0.2 -m "version 0.1 released" $hash$
push tag:
git push origin v1.0
# push 本地所有tag
git push origin --tags
删除tag
# 删除本地tag
git tag -d v1.0
# 删除远程tag
git push origin --delete tag v1.0
使用cherry-pick
可以把其他分支的一部分commit提交到当前分支:
git log --all --online
# cherry-pick
git cherry-pick [-n] $hash$ $hash$
#-n,表示先不commit,把commits保留到staging area
checkout:用staging area
覆盖work directory
:
git checkout -- <file>
reset:用repository
覆盖staging area
,若working directory
有修改,则丢弃staging area
:
git reset HEAD <file>
repository
的($hash$,HEAD]切出到staging area
:git reset --soft HEAD~1|$hash$
repository
的($hash$,HEAD]切出到working directory
:git reset --mixed HEAD~1|$hash$
repository
的($hash$,HEAD]抛弃:git reset --hard HEAD~1|$hash$
案例:找回reset掉的内容:
git reflog --oneline
HEAD
指向它:git reset --hard $reset_hash$
# 或者
git rebase --onto HEAD HEAD $reset_hash$
revert:提交逆修改,HEAD
继续前进,而reset
让HEAD
回退:
git revert HEAD~1|$hash$
rm --cached:删除追踪索引,不能操作work directory
的文件;该操作会把文件从staging area|repository
移到work directory
并置为untracked
;此外如果该文件曾commit
过,会在staging area
生成一个deleted:<file>
记录:
git rm --cached [-r] <file>
rm:删除repository
中的文件,并在staging area
添加一个deleted:<file>
:
git rm <file>
rm -f:删除untracked文件和文件夹:
git rm <file> -f [-r] [-q]
# -r 表示递归
# -q 无删除回显
clean -fd:清理工作区,递归删除所有untracked
的文件和文件夹:
git clean -fd
好了,重头戏来了。
这个翻译其实有些抽象,rebase
的作用是对一段线性提交做一些操作,操作后分化出一个新的分支。
这里我对提交的理解是:若干个commit以hash标识成点,形成一个commit链;其中HEAD
是指针变量,分支名
是指针常量(链表头结点,或者理解成一维数组名)。
下面的语法中,endpoint
是可选的,默认为HEAD
。如果发现endpoint
是常量指针
(即分支名),则分化出的分支自动应用到该分支并checkout
过去。
语法:
git rebase [-i] [--onto] [base_branch] startpoint [endpoint]
# 其中线性提交段是 (startpoint, endpoint],前开后闭
rebase合入分支最新修改(merge按时间排序commit,rebase根据分支排序),reabase把冲突处理交给提MR的人处理,提交记录比merge更干净:
git rebase origin/master [HEAD]
# solve conflicts
git add <conflict_files>
git rebase --continue
编辑分支上的一段线性提交(reword,squash,reorder,drop):
git rebase -i startpoint [endpoint]
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交说明
# e, edit = 使用提交,但停止以便进行提交修补
# s, squash = 使用提交,但和前一个版本融合
# f, fixup = 类似于 "squash",但丢弃提交说明日志
# x, exec = 使用 shell 运行命令(此行剩余部分)
# d, drop = 删除提交
下面分析4个rebase
典型案例,假设当前分支状态如下图所示:
[外链图片转存失败(img-odPtFdEq-1562834637827)(https://i.loli.net/2019/03/14/5c8a3fbc6f7d9.png)]
案例1-并集:next基于master;此时得知master有新修改,next需要同步master的最新修改。
git rebase master next
# 命令完成后自动切到next分支
# 此时next = [master + next#HEAD]
案例2-差集:topic基于next,next基于master;此时得知next有重大bug,要求topic中属于next部分的提交都得去掉。
# 差集计算:topic = master + (topic - next)
git rebase --onto master next topic
# 多段差集:topic = master + (topic#HEAD~4, topic]
git rebase --onto master HEAD~4 topic
案例3-修改commit:编辑(reword,reorder,edit,drop)一段Commit:
git rebase -i HEAD~4 HEAD
案例4-融合commit:融合(squash)操作也属于修改,但这里单独拿出来示例,因为它可以多人合作完成,也可以个人独立完成:
# A同学
git rebase -i next topic
# B同学
(next)$ git merge topic
(next)$ git merge --squash topic
git commit -m "merged topic to next"
# 重命名/移动staged的文件后自动处于staged
git mv oldfile newfile
git mv file folder
# repack 把松散对象打包以提高git运行效率
git repack -d
# 二分查找有问题的提交
git bisect start master f608824
git bisect run make test
# test脚本为判断是否有问题的依据
line = gets
exit 1 if line != "15\n"
浅克隆:对于比较大的仓库,本地不想要那么多无关的分支,无关的历史记录,可以考虑浅克隆。
# 以浅克隆的方式获取库 git clone <URL> --branch <master> --depth=1 # 添加origin中的分支 git remote set-branches origin <branch_new1> <branch_new2> # 以浅克隆的方式fetch origin中的分支 git fetch --depth 1 origin [<branch_new1>] # 深化origin 中的克隆深度为 N git fetch origin --deepen N # 删除分支 git branch -rd <branch_name> git push origin -d <branch_name>
拓扑序:对于提交日志,默认是按commit时间排序,拓扑序可以把相关联的多个提交放在一起显示。
# 默认按时间排序
git log --date-order --oneline
# 指定拓扑排序
git log --topo-order --oneline
二分查找:有时候无法从final版本代码定位问题,可能需要回滚代码定位。可以考虑log(N)复杂度的二分查找。
# 开始二分查找(reset的时候会回到这个HEAD)
git bisect start
# 告诉git,当前的HEAD提交是有问题的
git bisect bad
# 告诉git,某个提交是OK的
git bisect good $hash$
# 这时git会reset到中间的commit,此时编译验证,并告知结果
git bisect <bad/good>
# 这样git会不断划分区间查找,最终会告诉你哪个commit有问题。找到后退出查找模式
git bisect reset
以上。
此外,如题,笔者也在不停探索,如果有错误欢迎指出斧正~
最后感谢这个游戏:Githug
以及这个通关攻略:「Githug」Git 游戏通关流程
还有这个在线练习平台:Git在线练习
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。