git各命令的原理和初级使用,顺手玩下git钩子

3,553 阅读17分钟

该文章是在阅读猴子也懂的git入门抛物线大神的git原理和使用小册子,额,需要一点点零花钱之后总结的,估摸着不适合新手...

TL;DR

  • 核心概念:暂存区(索引),数据库(repository)是每个commit组成的,HEAD/branch/tag都指向某个commit
  • 远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。
  • 没事常常git statusgit 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

官方git文档
猴子也懂的git入门
抛物线大神的git原理和使用小册子,额,需要一点点零花钱
廖雪峰大神的git教程
巧用git钩子
imweb的git钩子,特别是可以用package.json指定钩子
拿到git分支的名字