如果你想精通Git,直接到 Git官网 把这本ProGit掌握已足以Pro Git
此文主要介绍一切开发中常用的git命令和一些配置技巧(诸如git别名配置,log打印技巧,版本回退以及分支管理等)。
后来我又写了篇主要介绍Git变基的文章《开发中关于Git那些事(Git Rebasing)》,有兴趣的可以看看。
1.简介
Git与SVN相比而言,Git的好处自然不用多说,Git完全分布式文件管理系统,加上其简单速度,可以高效管理类似 Linux 内核一样的超大规模项目。完全分布式的系统,让你可以在公交车上,火车上,家中,甚至在厕所都可以敲代码。何时何地你都可以敲代码,甚至不需要网络。好不好使,开不开心?换是SVN,SVN服务器挂了,全部人停止敲代码,停下来八卦去吧。
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都更新了哪些行的什么内容。如同下图所示:
但是,Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快 照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一连接。工作方式类似下图:
对比可以发现,Git高效也在情理之中。
现在再简单介绍一下Git管理下文件的三种状态。对于任何一个文件,只要在Git管理下,那么该文件只有三种状态:已修改(modified),已暂存(staged)和已提交 (committed)。
已修改(modified): 文件被修改,但是还没有提交保存(也就是没有使用git add,此时使用git status显示为红色)。
已暂存(staged): 已修改的文件放入下次要提交的清单中(使用了git add后的状态,此时使用git status显示为绿色)。
已提交(committed): 该文件已经被安全地保存在本地数据库中(使用了git commit后,此时使用git status已经不存在该文件的任何信息)。
具体可以参考下面两幅图来理解。
2.配置用户信息
user和email,--global参数全局配置,当然你也可以不加此参数,不同的项目用不同的用户名和邮箱。
注意:一定要认真填写邮箱,如果你项目在OSChina或则GitHub,邮箱应该和OSChina或则GitHub绑定的邮箱一致,不然之后就算你提交了代码,是看不到自己的贡献度的。
- git config --global user.name Super
- git config --global user.email 1342449****@163.com
不过之后也是可以重写已经提交的commit的用户名和邮箱,但是一般情况下不要这样操作,不提倡,重写命令:
此命令会重写所有commit,谨慎操作
- git filter-branch -f --env-filter '
- if [ "$GIT_AUTHOR_NAME" = "Super" ]
- then
- export GIT_AUTHOR_NAME="Super"
- export GIT_AUTHOR_EMAIL="xxx@gmail.com"
- fi
- '
3.配置全局别名
此配置在开发中相当重要,尤其是对于使用Terminal,习惯使用命令行的朋友,由于git不支持tab自动补全,每次想要看下工作目录 状态都要git status,相当耗时。除非你能确定你敲两个字母比六个字母用时少。
- git config --global alias.st "status -s"
- git config --global alias.br "branch"
- git config --global alias.co "checkout"
- git config --global alias.ci "commit -m"
- git config --global alias.aci "commit -a -m" // 跳过使用暂缓区,直接将git add和git commit合并为一条命令
- git config --global alias.lg "log --color --pretty=format:'%Cred%h - %Cgreen%an %C(yellow)| %ad | %Creset%s' --graph" //自定义log
第一条:git status是开发中使用最多最频繁的,至于-s 是简洁输入(Give the output in the short-format)
第二条:此条也使用频繁,但是我在开发中直接使用第三条跳过。
第三条:配置git aci 因为这样直接跳过使用暂存区域,对于已经跟踪的文件,我不要再此次使用git add加入暂缓区,然后再git commit提交到本地数据库,为了方便省事,直接将两条命令合并为一条,使用git aci "提交说明" 即可。省不省事,用下自然知道。
第四条:这里是我自定义的log信息,当然,一会看完本文你也可以自己格式化成自己喜欢的log格式。可以先看下效果,下图所示:
以后直接使用"git lg"即可,非常方便,显示简洁明了。
补充,也可以不加引号,比如这样子
- $ git config --global alias.co checkout
- $ git config --global alias.br branch
- $ git config --global alias.ci commit
- $ git config --global alias.st status
4.分支管理
- git checkout -b dev origin/dev // 拉取远程开发分支到本地并且切换到开发分支
- git push dev origin/dev
- git branch new // 创建新分支
- git push origin new // 将new分支推送到远程(git push [remote-name] [branch-name])
- git push origin :new // 删除远程分支,注意冒号位置
- git branch -r -d origin/dev // 删除远程分支(同上一行命令,作用一样)
- git brach -d new // 删除本地分支,如果有没有merge的信息,确保该分支的确不用merge,直接使用-D强行删除
- git merge dev // 将dev分支合并到当前分支
- git checkout master // 切换到主分支
- 其他不再一一列出
关于实际开发中,独立开发的话其实两个分支完全够用,一个主分支(master),一个开发分支(develop)。多人的话就按功能模块和人员来具体新建分支即可。由于这边由我一人负责整个项目,那么分支的话,我就建立了一个develop分支,发布版本时切换到master处理即可。平时都在develop分支开发,如果此时发现线上版本有bug,那么只需要切换到master分支,修改bug,然后封版即可。处理好后切换到develop分支开发就可以了。当然最好还是把刚才在master修改bug的代码merge到当前的develop分支上。
还有一点使用技巧,也就是git stash
的灵活使用。当我们正在当前分支编代码,突然某人中断你的思路,提出某功能需修改且比较紧急,更烦人的是当前的代码已经开始编写,而且改动了好多文件,重要的是不想中途进行git commit提交,此时git stash就发挥作用了。
- git stash // 暂时隐藏,之后就可以正常切换分支了,当前的修改内容只是保存并且隐藏起来
- git stash pop // 回复到之前的工作状态,默认pop的是最近的一条,这样就可以愉快的继续编写当时被中断的代码了
git stash可以执行多次,我们可以使用git stash list
命令来查看清单。当然还有好多命令,可以使用git stash --help
来查看。
其实关于git stash
还有一个使用技巧,那就是如果当前修改了众多文件,突然又不想改了,想恢复到原来的样子。那么可能你会使用git checkout
命令来取消所有的更改,但是有种情况并不好使,因为你除了修改文件,还添加了一些文件或则拖入了一些文件到工程目录下,此时使用git checkout
然后再使用git status
查看的时候,会发现工作区多出了尚未识别的文件(处于等待add的状态)。在这个时候,使用git stash
是再好不过了,因为这样将所有git控制下的文件,包括之前的目录,全部还原到之前的状态(也就是说git stash
把新增的文件也暂时隐藏起来)。如果想彻底删掉,那就再把所有stash的列表清空吧,直接git stash list
看下,然后执行git stash clear
。
还有一点,关于在Git服务器上删除分支,本地使用git branch -a
依旧可以看到被删除的分支。好比你的代码托管到了开源中国(OSChina),你通过网站,在线删除分支,就会出现这种问题,稍微有点强迫症的自然受不了。此时可以使用 git fetch -p
使fetch之后删除没有与远程分支对应的本地分支。 当然也可以通过查看远程分支。使用命令git remote show origin
显示如下:
- YJTSuper:yjtim super$ git remote show origin
- * remote origin
- Fetch URL: git@git.oschina.net:lingsui/yjtim.git
- Push URL: git@git.oschina.net:lingsui/yjtim.git
- HEAD branch: master
- Remote branches:
- dev tracked
- im tracked
- master tracked
- proV2.3.0 tracked
- refs/remotes/origin/test stale (use 'git remote prune' to remove)
- Local branches configured for 'git pull':
- master merges with remote master
- show merges with remote master
- Local refs configured for 'git push':
- dev pushes to dev (up to date)
- im pushes to im (fast-forwardable)
- master pushes to master (up to date)
- proV2.3.0 pushes to proV2.3.0 (up to date)
我们可以看到分支origin/test 已经过期(stale),可以使用命令git remote prune origin
同样可以处理。
5.远程地址切换
如果想要切换远程地址,千万不要重新再初始化一个git代码仓库,使用git add重新添加。这样的好处只有一点,所有历史提交信息全部清空,不再保留,git clone
时文件变小,理所当然,历史记录全部清空了麽。如果确定不保留,也建议这样做,关键时,谁写的代码更改了哪些东西很重要呀,还是选择保留吧。使用下面的方法,一行代码搞定,并且保留了所有的commit记录。
- git remote -v // 查看远程地址
- git remote set-url origin https://git.oschina.net/HaiShengHuo/xxx.git // 更换远程地址, 新建一个项目不添加任何文件 在本地直接push即可
6.查看某次提交修改的具体文件和内容
方法一
git reflog
或则 git log --oneline
注意:这里的 git reflog是包含所有分支操作(切换,删除等)以及所有提交记录,而git log仅仅是当前分支下的提交,不存在分支操作记录。这一点在版本回退中应该特别注意,使用git reset时,尽量使用git log.
首先要列出最近的提交记录。
git log 56c8822c --name-status
找到对应版本号,执行后可以看到本次提交之前的所有修改文件。
git diff HEAD@{3} HEAD@{4}
具体某次修改的内容(具体某次提交的内容和上次提交的内容进行比较)。git diff 56fd41f2 56c8822c
当然也可以这样,使用版本号,效果一样。
这样假如之前修改过具体某些内容,以后还需要修改的话,找起来真的很方便。比如说某些bug好久之后才发现,又要回头去修改,而修改总要找到对应的代码进行修改吧。那么通过这样几行命令就定位到具体文件和位置了,方便且节省时间。找代码有技巧,但是"找"终究还是很浪费时间。这也告诉我们,commit 提交命令很重要,需要认真写,不可为了省事而乱写。
方法二
直接使用git log 4e732dee -p
或则 git log -p 4e732dee
其中4e732dee为版本哈希值
方法三
git log --stat 4e732dee
或者git log 4e732dee --stat
查看简洁文件变化
查看本地修改的内容,尚未进行commit
直接使用git diff --stat
7.忽略跟踪
- git checkout . 清空所有更改
- 以下命令是我们在项目中已经添加了.gitignore 但是中途突然不想再跟踪某文件
- 此时发现简单的在.gitignore文件中添加要忽略的文件是不起效的,因为该文件已
- 经被track,我们还需要将其状态改为 未track(其实只需删除暂缓区文件然后将操作体检即可)
- git rm --cached Podfile.lock 将Podfile.lock从暂缓区删除,不再跟踪
8.备份
备份的话好一些,每次新的版本打一次tag,查看起来还是挺方便。不过不用也行。我不爱使用这个。
- git tag -a WeChat1.0 -m "version 1.0" :给版本打上标签
- git tag : 查看所有的标签
- git push origin WeChat1.0 : 将WeChat1.0 push 到默认分支
9.版本回退
- git reset --hard HEAD // 没有提交的情况下进行版本回退
- git reset --hard HEAD^ // 回退到上一个版本
- git reset --hard HEAD^^ // 回退到指定回退到某个版本
- git reset --hard 版本号(至少前5位) // 回退到前几个版本
- git reset --hard~1
- git revert c011eb3c20ba6fb38cc94fe5a8dda366a3990c61 // 注意该行命令 reset和revert有本质区别
注意:开发中一般托管代码到远程代码仓库,比如Github或则OSChina,加入本地已经使用git push到远程代码仓库,在本地使用git reset
回退再push明显是不可行的。因为git reset直接回退到历史中的某个Hash值,但是使用git revert就不一样了。git revert将作为一次新提交(新的Hash值)来撤销某次更改而不是历史中的某个Hash值,此时再push到远程代码仓库情理之中。当然你嫌麻烦也可以使用git reset 后再使用git push -f
强制 push 到远程代码仓库。
reset和revert区别:
• git reset 3c7bdf2 回到3c7bdf2,3c7bdf2之前的commit都会保留,3c7bdf2之后的修改都会被退回到暂存区。
• git revert 3c7bdf2 生成一个新的commit来撤销3c7bdf2,此次提交之前的commit都会被保留。
可以参考博客git revert和git reset的区别
10.日志
想回顾下提交历史,可以使用 git log 命令,其中有个--pretty 参数可以配置
- git log --pretty=oneline
- git log --pretty=format:"%h - %an, %ar : %s"
- git log --pretty=format:"%h %s" --graph
- git log --since=2.weeks
自己可以试一下,这里附上参数说明,自己可以随意配置,当然log字体颜色也都是可以修改的,不再细述。
- %H // 提交对象(commit)的完整哈希字串 %h 提交对象的简短哈希字串
- %T // 树对象(tree)的完整哈希字串
- %t // 树对象的简短哈希字串
- %P // 父对象(parent)的完整哈希字串 %p 父对象的简短哈希字串
- %an // 作者(author)的名字
- %ae // 作者的电子邮件地址
- %ad // 作者修订日期(可以用 -date= 选项定制格式) %ar 作者修订日期,按多久以前的方式显示
- %cn // 提交者(committer)的名字
- %ce // 提交者的电子邮件地址
- %cd // 提交日期
- %cr // 提交日期,按多久以前的方式显示 %s 提交说明
时间和提交者过滤
- -(n) // 仅显示最近的 n 条提交。
- --since, --after // 仅显示指定时间之后的提交。
- --until, --before // 仅显示指定时间之前的提交。
- --author // 仅显示指定作者相关的提交。
- --committer // 仅显示指定提交者相关提交。
你一定奇怪_作者(author)_和_提交者(committer)_之间究竟有何差别,其实作者指的是实际作出修改 的人,提交者指的是最后将此工作成果提交到仓库的人。所以,当你为某个项目发去补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。我们会在第五章再详细介绍两者之间的细致差别。
11.忽略某些文件
文件 .gitignore 的格式规范如下:
• 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
• 可以使用标准的 glob 模式匹配。
• 匹配模式最后跟反斜杠(/)说明要忽略的是目录。
• 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。星号(*)匹配零个或多个任意字符;[abc] 匹配 任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?) 只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配 (比如 [0-9] 表示匹配所有 0 到 9 的数字)。
- # 此为注释 – 将被 Git 忽略
- *.[oa] Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现 的,我们用不着跟踪它们的版本
- *~ Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件 (比如 Emacs)都用这样的文件名保存副本
- *.a # 忽略所有 .a 结尾的文件
- !lib.a # 但 lib.a 除外
- /TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO build/ # 忽略 build/ 目录下的所有文件
- doc/*.txt # 会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
补充
项目刚启动时(比如要把项目搭建在OSChina上),建立git时推荐方案是,先在OSChina上建立一个全新的项目,然后拿到链接后clone到本地,然后在Git工作目录建立文件夹和文件, 开始一个新的项目。这样本地和远程已经建立好了连接,随后直接git add .
,然后git commit -m "init"
,最后push到OSChina就好。
当然,有时候我们直接在本地初始化了git, 然后已将初始化了项目,想要与远程建立链接,也是可以的。但是一般不这样做,既然本地已经有了git代码仓库,那么随后再在OSChina上建立一个项目也同时生成了一个git代码仓库,,两则建立连接会有些问题, 最直接的问题就是, 本地的会直接覆盖掉OSChina上的仓库. 不过确定不要OSChina初始化文件也可以这样做。
操作步骤:
首先给本地添加远程地址 git remote add origin https://git.oschina.net/xxx/xxx.git
然后 git push origin master -f
其中-f
为强制推送远程(直接push会被拒, 因为远程已有一个代码仓库)
这样就可以了(远程OSChina初始化文件已经被覆盖)。