git各命令的原理和初级使用,顺手玩下git钩子
该文章是在阅读猴子也懂的git入门和抛物线大神的git原理和使用小册子,额,需要一点点零花钱之后总结的,估摸着不适合新手...
TL;DR
- 核心概念:暂存区(索引),数据库(repository)是每个commit组成的,HEAD/branch/tag都指向某个commit
- 远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。
- 没事常常
git status
和git lg
,没写错,配置git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
,炒鸡炫酷 - 需要在git执行某条命令的时候执行脚本,就用到钩子
- 搭建远程仓库
git概念理解
- 没有git的时候,想要保存文件的状态只能备份备份再备份,如果放到服务器上指不定还被谁不小心的覆盖了。用git管理文件的时候,更新的历史会保存在git里,也不会轻易被覆盖。
- 文件更新的历史保存git的数据库(repository)。有本地的数据库还有远程数据库,可以自己新建,也可以复制远程的。
- 若要把文件或目录的添加和变更保存到数据库,就需要进行提交,执行提交后,数据库中会生成上次提交的状态与当前状态的差异记录(也被称为revision),系统根据修改的内容计算出40位数字和英文生成版本号,俗称某个commit或者某个版本
- 实际操作的目录称为工作树(working tree),在数据库和工作树之间有索引,索引是为了向数据库提交做准备的区域。没有索引的文件是不能提交到数据库的~。索引很多时候又被称为stage,暂存区。工作树的内容跟着HEAD走。一旦HEAD指向新的版本,工作树的内容也会变成那个版本,形同备份~
- branch、tag、HEAD都是某个版本的引用,可以想象,数据库是一条横着的线,每次提交就是一个点,每个点有自己的id,点上面可能有branch、tag、HEAD这几个指向
git命令的本质
git clone url [projectName]
复制url的远程数据库到本地,git会将这个远程数据库命名为origin,projectName可选,省略的话文件夹名字同远程数据库的,写的话就重命名。这里HEAD指向master分支的最新的版本,自然工作区也跟着HEAD。git add file/.
,将工作树的某些文件创建索引的,.
表示所有文件,注意,add添加的不是文件名字,而是文件改动的内容。新文件必须通过这个才让git追踪这个文件。不然git不知道该文件的存在。git commit -m"认真写改了啥"
,将索引添加到本地数据库,可以想象成横线多了一个点,又长了些。HEAD自动更新到新的commit,且HEAD会拖着当前的分支,让其也指向新的commit。git pull [origin] [master]
,将远程数据库的某个分支的内容更新到本地数据库的相应分支,pull 的内部操作其实是把远程仓库取到本地后(使用的是 fetch),再用一次 merge 来把远端仓库的新 commits 合并到本地。后面两项是指哪个远程仓库的哪个分支,省略的话是origin,然后当前目录所在的分支。这里注意,执行这个操作的时候,可能远程数据库和本地数据库合并的时候,有冲突,解决掉就好,然后git add .;git commit
本地的数据库从分叉的地方开始插入到远程数据库里更新的后面,有冲突的话将会生成多生成一个commit。可以git log
看看git push [origin] [master]
,将本地的数据库添加到远程数据库哪个分支,如果总是想省略(我是省略重度依赖者。。),git config --global push.default current
,这样是处在哪个分支就会更新origin的哪个分支。如果实在master分支,这个操作会让origin/HEAD,origin/master
指向最新的commit,也就是远程仓库的HEAD和master指针。git checkout [-b] branch
,将 HEAD 指向branch ,工作目录的文件内容跟着HEAD,变成这个版本的内容 ,俗称切换分支,当然后面的参数可以是任意的commitID,需要注意的是,当前目录所在分支尽量clean,如果有需要提交的可能会切换失败,如果切换成功,默认为在新的分支下做了这些变更。-b
一般是创建新分支且切换到新分支的意思。git stash -u
,暂存当前分支的改动,工作区恢复到最新的commit版本,一般常用于紧急切换分支,又不想盲目提交commit,想要恢复改动的内容git stash pop
,-u
是将新建的文件一并暂存。git merge branch
,从目标 commit 和当前 commit (即 HEAD 所指向的 commit)分叉的位置起,把目标 commit 的路径上的所有 commit 的内容一并应用到当前 commit,然后自动生成一个新的 commit。merge会自动合并,如果同一个文件上修改可能会合并失败,解决完冲突之后git add 文件;git commit
就可以了。放弃merge的话git merget --abort
,merge后面可以是branch,也可以是一般的commit。是不是感觉和pull很像,当然!!!pull就是fetch和merge的合体~~~
git branch [newbranch] [-d]
省略后面的,会显示所有分支,加上后面的表示创建分支,删除分支的话-d
,强制删除-D
git tag tagName
,tag 是一个和 branch 非常相似的概念,它和 branch 最大的区别是:tag 不能移动。所以在很多团队中,tag 被用来在关键版本处打标记用。多用在master分支上。
git revert commitId
,将某commit取反,也就是放弃某次的修改,这个命令会生成新的commit。
git reset commitId --hard
,将HEAD以及当前的branch指向移动到目commit,工作区的内容随着HEAD一起变化,有时候想丢弃某些commits用这个命令。经常用这个git reset HEAD --hard
放弃本地所有的修改,恢复到上一次的commit状态。
git remote name url
,添加一个远程数据库,并且起一个名字,感觉用的不多
git的查找和配置
git log
,查看日志,git log --patch
查看详细的日志,git log --stat
查看简要的日志,想要切回到某个版本的时候会用到
git show [commitID] [某文件]
,看某一个版本的日志。省略commit表示当前的commit,省略某文件表示所有文件。
git diff commitID
,将工作区的内容和目标commit做一个对比。
git status
,查看当前的状态,随时会用
git config
,对git的一系列配置。属于「一次付出,终身受用」的高性价比内容。global参数针对全局git,不加那只针对当前的仓库起作用。全局配置在用户主目录下的一个隐藏文件~/.gitconfig
中,本地配置在仓库根目录的.git/config
。查看配置git config --global --list
。以下是常用的配置: 比如配置别名,git config --global alias.co checkout;git config --global alias.ci commit;git config --global alias.br branch
也就是可以git co xxbranch;git ci -m"xx";git br
比如配置不容易的命令,git config --global alias.unstage 'reset HEAD'
下次git unstage file
就表示将暂存区的修改撤销掉,回到工作树那边。git config --global alias.back 'reset HEAD --hard'
用git back
表示放弃所有修改,工作区返回到上一次的commit。git config --global alias.last 'log -1'
显示最后一次提交的信息。git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
嘿嘿,这样可以git lg
显示日志,特别好用!!!强烈推荐!!!
比如上面配置push。
分支的应用
公司中多数是这样开发的:
master分支一般只管理发布状态,在提交的时候使用标签记录发布版本号
develop分支是发布的日常开发分支
任何新的功能(feature)或 bug 修复全都新建一个 branch 来写,新分支可能feature-xxx,hotfix-xxx
,feature表示功能开发,hotfix发布的产品有紧急bug,修改完之后合并
branch 写完后,合并到 master,然后删掉这个 branch。当然也可以借助pull requests
HEAD、master与branch
每个commit就是每个版本,每个记录的意思。称呼不一样。
git log
执行后,一般第一行的 commit 后面括号里的 HEAD -> master, origin/master, origin/HEAD
,是几个指向这个 commit 的引用。
在 Git 的使用中,经常会需要对指定的 commit 进行操作。每一个 commit 都有一个它唯一的指定方式也就是上图中每个黄色的 commit 右边的那一长串字符。两个 SHA-1 值的重复概率极低,所以你可以使用这个 SHA-1 值来指代 commit,也可以只使用它的前几位来指代它(例如第一个 78bb0ab7d541…16b77,你使用 78bb0ab 甚至 78bb 来指代它通常也可以),
但毕竟这种没有任何含义的字符串是很难记忆的,所以 Git 提供了「引用」的机制:使用固定的字符串作为引用,指向某个 commit,作为操作 commit 时的快捷方式。
HEAD
HEAD 是 Git 中一个独特的引用,它是唯一的。
HEAD是当前工作目录所对应的commit。
当有新的commit的时候(commit或者pull的时候),工作目录会与最新的commit对应,HEAD也就指向最新的commit。
当使用checkout、reset等指令手动改变工作目录的commit时,HEAD也会跟过去
当前目录的commit在哪,HEAD就在哪,永远指向当前目录的commit,也就是可以用HEAD操作当前目录的commit
branch
Git 还有一种引用,叫做 branch(分支)
HEAD 除了可以指向 commit,还可以指向一个 branch,当它指向某个 branch 的时候,会通过这个 branch 来间接地指向某个 commit;另外,当 HEAD 在提交时自动向前移动的时候,它会像一个拖钩一样带着它所指向的 branch 一起移动。
- branch可以理解为是指向commit的一个引用,也可以理解为从第一个commit到branch指向的commit之间所有的commits的一个串
master
- 默认的branch,创建新仓库的时候,第一条commit创建,master会指向它,HEAD指向master
git clone
的时候,除了从远程仓库把 .git 这个仓库目录下载到工作目录中,还会 checkout (签出) master(checkout 的意思就是把某个 commit 作为当前 commit,把 HEAD 移动过去,并把工作目录的文件内容替换成这个 commit 所对应的内容)。
- origin/HEAD,origin/master是远程仓库的镜像引用
「引用」的本质
所谓「引用」(reference),其实就是一个个的字符串。这个字符串可以是一个 commit 的 SHA-1 码(例:c08de9a4d8771144cd23986f9f76c4ed729e69b0),也可以是一个 branch(例:ref: refs/heads/feature3)。
Git 中的 HEAD 和每一个 branch 以及其他的引用,都是以文本文件的形式存储在本地仓库 .git 目录中,而 Git 在工作的时候,就是通过这些文本文件的内容来判断这些所谓的「引用」是指向谁的。
git的高级(难一点)的命令
这部分不具体写命令了,如果有需求,再进行查阅吧。
rebase的意思是,给你的 commit 序列重新设置基础点(也就是父 commit)。展开来说就是,从目标 commit 和当前 commit (即 HEAD 所指向的 commit)分叉的位置起,以目标 commit 为基础,依次重新提交分叉之后所有 commit 的内容,这话听着别扭,就是把当前所在分支的分叉的所有commits,都复制一份放在参数branch后面
。
指定amend选项执行提交的话,可以修改所在分支最新的提交,git add 笑声.txt;git commit --amend
,其实相当于这次的提交直接覆盖掉上次的提交。
在cherry-pick,您可以从其他分支复制指定的提交,然后导入到现在的分支。
在rebase指定i选项,您可以改写、替换、删除或合并提交。
merge的特殊选项:squash,用这个选项指定分支的合并,就可以把所有汇合的提交添加到分支上。
示例
# 查看状态 git status # 添加索引 git add file # 添加数据库,将HEAD自动更新到新的commit,且HEAD会拖着当前的分支,让其也指向新的commit git commit -m"msg" # 将远程数据库的内容更新到本地数据库,默认的远程数据库和分支名字是 origin master git pull origin master # 将本地数据库添加到远程数据库。你用不加参数的 git push 只能上传那些之前从远端 clone 下来或者 pull 下来的分支,而如果需要 push 你本地的自己创建的分支,则需要手动指定目标仓库和目标分支(并且目标分支的名称必须和本地分支完全相同),就像上面这样。所有分支都可以用 git push 来直接 push,目标自动指向 origin 仓库的同名分支,通过 git config 指令来设置 push.default 的值为:current git push origin master # 添加远程数据库并且将远程数据库起个名字,一般起origin git remote origin http://xxxxx # 复制远程数据库,默认的新目录的名字同项目名,git会将这个地址命名为origin git clone http://xxxx projectName # 查看数据库 git log # 切换到某一版本,或者分支 git checkout xxxx # 需要暂存修改,stash是临时保存文件修改内容的区域 git stash # 释放 git stash pop # 查看分支 git branch # 创建分支 hotfix- feature- release- develop master,其实只是将当前commit创建了一个引用,HEAD指针并没有指向新分支,如果需要,git checkout xxx git branch xxx # 创建并切换到新分支,创建新引用,且将HEAD指向这个新引用 git checkout -b xxx # 删除分支,所谓的删除,只是删除这个commit的引用,可以从git log那找到这个commit,未合并到master的分支会删除失败,但可以强制删除,-d改成-D git branch -d xxx # issue1分支需要合并到master上,rebase的历史记录更少 # merge git checkout master;git pull;git merge issue1; # rebase git checkout issue1;git pull;git rebase master;git checkout master;git merge issue1 # 查看标签,想要看注解加上 -n git tag # 打标签 git tag tagnamexxxx # 打注解标签 git tag -am "注释信息" tagnamexxx # 看包含标签的历史记录 git log --decorate # 删除标签 git tag -d tagnamexxxx
git钩子的简单使用
一个新需求下来了,通常自己建个分支开始完成功能,等到完成的时候,自己测得也差不多了,就会合并到develop分支,然后部署到服务器,让测试来测。每次这样嫌费劲,就想着push到develop的时候,部署自动操作,这样省了很多麻烦吖。。。
这就用到钩子了,这边我没怎么研究,大约知道,就是在执行add commit push pull merge
等操作的时候,可以在相应的时机执行相应的脚本。
项目根目录下.git/hooks
下,有很多文件,去掉后缀.sample
就可以在相应的时机执行这个脚本。脚本的语言,自己开心就好~~
本地仓库的钩子:
pre-commit: 执行git commit命令时触发,常用于检查代码风格
post-commit: 整个git commit完成后触发,常用于邮件通知、提醒
post-merge: 成功完成一次 merge行为后触发
pre-push: 执行git push命令时触发,可用于执行测试用例
pre-auto-gc: 执行垃圾回收前触发
prepare-commit-msg: commit message编辑器呼起前default commit message创建后触发,常用于生成默认的标准化的提交说明
commit-msg: 开发者编写完并确认commit message后触发,常用于校验提交说明是否标准
applypatch-msg: 执行git am命令时触发,常用于检查命令提取出来的提交信息是否符合特定格式
pre-applypatch: git am提取出补丁并应用于当前分支后,准备提交前触发,常用于执行测试用例或检查缓冲区代码
post-applypatch: git am提交后触发,常用于通知、或补丁邮件回复(此钩子不能停止git am过程)
pre-rebase: 执行git rebase命令时触发
post-rewrite: 执行会替换commit的命令时触发,比如git rebase或git commit --amend
post-checkout: 执行git checkout命令成功后触发,可用于生成特定文档,处理大二进制文件等
远程仓库用的钩子,一般我们就是要在服务端更新代码之后运行脚步,所以我们要修改的就是post-update或者post-receive。
pre-receive: 当服务端收到一个push操作请求时触发,可用于检测push的内容
update: 与pre-receive相似,但当一次push想更新多个分支时,pre-receive只执行一次,而此钩子会为每一分支都执行一次
post-receive: 当整个push操作完成时触发,常用于服务侧同步、通知
拿我为例,我用的,这里说下,最好是在服务器端执行git pull
,但因为此项目是另外一个团队开发,我临时负责一个功能,也不知道服务器那边为啥不用git,我得到的信息是只能用复制替换
# .git/.hooks 新建pre-push ##!/bin/sh # 拿到分支名 NAME=$(git branch | grep '*' | sed 's/* //') echo "正在上传到服务器 $NAME" # 在develop上push的时候主动将代码复制到服务器上面 if [ "$NAME" = "develop" ] then scp -r /Users/creen root@10.2.2.156:/newoackend fi
搭建git服务器
我觉得买服务器的时候,估计就用到,所以怕忘了,索性记一笔,大段引用廖雪峰大神的git教程。 远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。GitHub就是一个免费托管开源代码的远程仓库。但一般公司需要自己搭建。
Ubuntu或Debian系统完成git安装需要以下几步:
# 安装git sudo apt-get install git # 创建一个git用户,用来运行git服务 sudo adduser git # 创建证书登录,其实就是将所有需要登录的用户的id_rsa.pub文件,导入到服务器的/home/git/.ssh/authorized_keys文件里,一行一个。mac用户的公钥位置`~/.ssh/id_rsa.pub` # 初始化Git仓库,先选定一个目录作为Git仓库,假定是/srv/sample.git,在/srv目录下输入命令,Git就会创建一个裸仓库,裸仓库没有工作区,因为服务器上的Git仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区,并且服务器上的Git仓库通常都以.git结尾 cd /srv sudo git init --bare sample.git # 把owner改为git sudo chown -R git:git sample.git # 禁用shell登录,出于安全考虑,第二步创建的git用户不允许登录shell,这可以通过编辑/etc/passwd文件完成。找到类似git:x:1001:1001:,,,:/home/git:/bin/bash改为:git:x:1001:1001:,,,:/home/git:/usr/bin/git-shell
git的用户可以在本地clone了
git clone git@server:/srv/sample.git