Git

概念

Git 中有 4 个区域:

  • 工作区( Working Area )
  • 暂存区( Stage )
  • 本地仓库( Local Repository )
  • 远程仓库( Remote Repository )

文件有 5 种状态:

  • 未修改( Origin )

  • 已修改( Modified )

  • 已暂存( Staged )

  • 已提交( Committed )

  • 已推送( Pushed )

  • 文件刚开始处于工作区,编辑文件后可以使用git diff查看文件进行了哪些修改

  • add 命令可以添加到暂存区,可以使用git diff --cached查看文件在暂存区和本地仓库之间的区别

  • commit 提交到本地仓库,可以使用git diff master origin/master查看本地仓库分支和远程仓库分支之间的区别
    注意,Git 保存的不是文件差异或者变化量,而只是一系列文件快照,并使用一个 commit 来指向它们。

  • push 将本地仓库的代码提交到远程仓库

索引

分支

命令

status

1
$ git status -s # 短版status

diff(比较文件)

1
2
3
4
git diff --cached # ,同--staged
git diff branch1 branch2 --stat //显示出所有有差异的文件列表
git diff branch1 branch2 文件名(带路径) //显示指定文件的详细差异
git diff branch1 branch2 //显示出所有有差异的文件的详细差异

log(显示提交历史)

对比分支差异
列出最近两次提交引入的更改(-p 相当于 diff 命令)

1
$ git log -p -2

其他选项

1
2
3
4
5
6
$ git log --stat # 列出每次提交的state(相当于加state命令)
$ git log --pretty=??? # 以某种格式化输出log
$ git log --since=2.weeks # 打印最近2周的log
$ git log -S function_name # 过滤出对function_name字符串有修改的commit,另外在log最后加--file,可以限制只打印对file有修改的commit
--decorate:在结果中显示当前所处的branch
--graph:以更直观的方式区别不同的branch

打印出炫酷的效果:

1
$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative

ignore

.gitignore 文件中的规则匹配的文件会被 git 忽略,但是文件事先要从索引中删除。

config

1
2
3
$ git config --global credential.helper cache # 设置凭证缓存,以后就不用每次都输入密码了,但默认只保存15分钟
$ git config credential.helper 'cache --timeout=3600' # 设置凭证保存一个小时
$ git config --global credential.helper store # 长期存储

add(建立索引)

1
git add .

unmodifying(已修改->未修改)

1
2
3
git checkout .
$ git checkout -- <file>... # 若已tracked的文件被修改,可以使用这条命令恢复到未修改时的情况
git reset --hard

commit(提交)

1
2
$ git commit -a # 自动add并提交追踪过的所有文件
$ git commit --amend # 覆盖上一次的commit,比如可以修改commit message,也可以删除一些文件的索引后再次进行提交,在误提交一些配置文件后可以这样挽救

unstage(撤销 add,已暂存->已修改)

1
2
3
$ git reset HEAD <file>... # 撤销上一次的add
$ git rm -rf --cached [文件] # 仅删除索引,不删除,-r递归,-f强行移除
$ git rm -rf [文件] # 从工作目录和跟踪列表中删除

tag

1
2
3
4
5
6
7
8
$ git tag # 显示所有标签
$ git tag -a v1.4 -m "my version 1.4" # 给最近一次的commit加一个注释标签(相当于一个节点)
$ git show <tag-name> # 显示tag和被tag的commit的信息
$ git tag v1.0-lw # 加一个轻量标签,仅仅在文件中记录最近一次commit的checksum
$ git tag -a v1.2 <checksum> # 在后面指定某个commit的完整(或部分)checksum也可以加一个标签,checksum可以使用$ git log --pretty=oneline得到
$ git push origin [tagname] # 将某个标签推送到远程服务器中
$ git push origin --tags # 将所有标签推送到远程服务器
$ git checkout -b [branchname] [tagname] # 转换到另一branch

aliase

1
2
3
4
5
6
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
$ git config --global alias.unstage 'reset HEAD --'
$ git config --global alias.last 'log -1 HEAD'

以后就可以使用 git ci 来 commit 了

1
$ git config --global alias.visual '!gitk'

为自己写的工具设置别名

push

1
2
3
4
$ git push origin serverfix:awesomebranch # 将serverfix代表的分支推到远程origin的awesomebranch分支上
$ git push origin --delete serverfix # 删除服务器上的一个分支
$ git push --set-upstream origin master # 将当前分支的提交上传到远程仓库的某个分支并与其建立跟踪
$ git push -u origin master # 同上

remote

1
2
3
4
5
6
7
8
9
$ git remote # 显示远程服务器名,默认赋名origin
$ git remote -v # 显示需要从该远程主机读/写时可以使用的URL
$ git remote add <shortname> <url> # 添加一个可用的远程仓库
$ git fetch [remote-name] # 获取远程仓库有而本地仓库没有的信息,但不merge
$ git pull # fetch并merge
$ git push [remote-name] [branch-name] # 向有写权限的主机push数据
$ git remote show [remote-name] # 显示远程仓库的详细信息
$ git remote rename [old] [new] # 重命名仓库的shortname
$ git remote rm [shortname] # 移除远程仓库

branch

1
2
3
4
5
6
$ git branch -v # 显示每个branch的最后一个commit
$ git branch --merged # 过滤branch,只显示并入到当前branch的branches,同样的还有--no-merged
$ git branch -u origin/serverfix # 改变当前分支track的上流分支
$ git branch -vv # 查看每个本地分支track的服务器分支(只和最近一次fetch比较)
$ git branch -r # 查看远程分支列表
$ git branch -D [branch] # 强行删除分支,不管分支有没有被并进来

checkout

1
2
3
4
5
$ git checkout -b [branch] [remotename]/[branch] # 在本地创建一个新分支,与远程的一个分支同步
$ git checkout --track origin/serverfix # 上面的缩写
$ git checkout serverfix # 上面的缩写
$ git checkout -b sf origin/serverfix # 如果将要创建的本地分支与所要track的远程分支不同名
撤销文件的修改(还未add的文件)

rebase(衍合)和 merge(合并)

  1. 实现方式:rebase 和 merge 都是用来在推送之前整合提交历史的,但是实现的方式却不同,rebase 先找出两个分支的公共祖先,然后将一个分支的从该节点之后的所有提交都当成补丁应用到另一个分支上;merge 同样是先找到分支的公共祖先,然后对两个分支的最新提交进行合并???,将合并中修改的内容生成一个新的 commit。
  2. 冲突解决:rebase 和 merge 在实施过程中都有可能会出现冲突的情况,merge 遇见冲突后会直接停止,等待手动解决冲突并重新提交 commit 后,才能再次 merge;而 rebase 遇见冲突后会暂停当前操作,开发者可以选择手动解决冲突、add 更新索引,然后 git rebase –continue 继续,或者 –skip 跳过(注意此操作中当前分支的修改会直接覆盖目标分支的冲突部分),亦或者 –abort 直接停止该次 rebase 操作。
  3. pull 命令其实相当于 fetch+merge,可以通过在后面加上 --rebase 选项来指定为 rebase 操作,或者先 fetch,之后再考虑使用 merge 还是 rebase。
  4. 不要 rebase 已经公开的提交对象,因为它会舍弃当前分支已经提交的 commit,对别人来说就像该分支被回滚了一样,如果 pull 了可能就会出问题。
    https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging#Basic-Merge-Conflicts
  5. 当有修改未 commit 时,不能进行 rebase 操作,此时可以考虑先用 git stash 命令暂存;但是 merge 可能会直接将未 commit 的内容覆盖掉。
    1
    2
    3
    4
    5
    6
    $ git rebase b # 嫁接过去,将当前分支里的补丁应用到目标分支的最后一个提交对象上,也就是在目标分支的最后一个commit的基础上重演一遍修改,最后将当前分支的???指过去
    $ git rebase --onto master server client # 将分支server上的另一分支client衍合到master上
    $ git rebase master server # 将server分支衍合到master中来
    $ git rebase -i b # 显示详细信息,包括各commit会被连接到目标分支的哪个commit后面,并且可以指定命令(策略pick、reword、edit、squash、fixup、exec)???????,默认为pick
    $ git merge b # 将目标分支合并进来
    $ git merge --ff-only # ff的意思是fast-forward,这种情况下要么当前分支已经是最新的了、要么合并是可以fast-forward的(只移动分支指针),默认情况下是--no-ff,也就是普通的合并

stash

1
2
$ git stash apply # 使用最近一次的stash记录来还原当前分支
$ git stash clear # 清空stash表

reset

统计代码量

统计每个人的代码量:

1
git log --format='%aN' | sort -u | while read name; do echo -en "$name\t"; git log --author="$name" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf "added lines: %s, removed lines: %s, total lines: %s\n", add, subs, loc }' -; done

统计某个人某段时间内的代码量:

1
2
3
4
5
6
git log --since='2019-01-15' --until='2019-12-31' --format='%aN' | sort -u |
while read name; do echo -en "$name,";
git log --since='2019-01-15' --until='2019-12-31' --author="$name" --numstat
--pretty=tformat: --no-merges | awk '{ add += $1; subs += $2; loc += $1 - $2 }
END { printf "added lines, %s, removed lines, %s, total lines, %s\n", add, subs,
loc }' -; done >> XXX_19_12_31_code.csv;

回滚

Git 支持对本地代码库或远程代码库回滚。
参考:git 远程分支回滚

工作流

首先 master 分支即发布的版本,在此上分出一个 develop(huang)版本,每次发布任务时,在 develop 的基础上分出 liu、du、zhou、shen,完成各自的任务,完成后合并到 develop 上。
若 master 上出 bug 了就直接分出一个 hotfix 分支,修复后马上合并到 master 和 develop 上。
开发完成后(即 liu、du、zhou、shen 等的分支都已合并进 develop)在 develop 上分出一个 release,进行测试,最后合并到 master 上。

参考

  1. git-for-computer-scientists
  2. git - Documentation
  3. 常用.gitignore
  4. Git 少用 Pull 多用 Fetch 和 Merge
  5. 闲谈 git merge 与 git rebase 的区别
  6. 解决git误commit大文件导致不能push问题