Jade Dungeon

历史

查看提交记录 git log

查看提交的记录,每一次提交都有一个唯一的主键叫commit

$ git log

显示的结果类似于less的交互程序。

显示每次改动的内容

参数-p可以显示详细日志信息,如提交的改动:

$ git log -p
commit 995f8a9a0e77547a605a586364b795c1ffd07ccb
Author: Jade Shan <mymail@gmail.com>
Date:   Tue Mar 4 01:10:21 2014 +0800

    refactor MsgProcess as abstract member of Connection

diff --git a/build.sh b/build.sh
index 50ccfd6..2a4c83d 100755
--- a/build.sh
+++ b/build.sh
@@ -30,8 +30,8 @@ do
                        # mvn clean scala:compile scala:testCompile reso
                        # mvn clean compile test-compile resources:resou
                        # mvn clean compile test-compile resources:resou
-                       # mvn clean compile test-compile resources:resou
-                       mvn clean compile test-compile resources:resourc
+                       mvn clean compile test-compile resources:resourc
+                       # mvn clean compile test-compile resources:resou
                        ;;
                r)
                        mvn resources:resources scala:run -Dlauncher=foo
diff --git a/src/main/scala/utils/connection.scala b/src/main/scala/util
index 02d9c97..3aa30bd 100644
--- a/src/main/scala/utils/connection.scala
+++ b/src/main/scala/utils/connection.scala
@@ -51,21 +51,22 @@ class ProxyInfo(val proxyType: ProxyInfo.ProxyType.V
        val proxyAddress: String, val proxyPort: Int,
        val proxyUsername: String, val proxyPassword: String) extends Lo
:

嫌详细内容太多,可以--stat显示改支的概要:

$ git log --stat I..C
commit 0cd7f2ea245d90d414e502467ac749f36aa32cc4
Author: Jiang Xin <jiangxin@ossxp.com>
Date:   Thu Dec 9 14:29:09 2010 +0800

    commit C.

    Signed-off-by: Jiang Xin <jiangxin@ossxp.com>

 README    | 1 +
 doc/C.txt | 1 +
 2 files changed, 2 insertions(+)

commit beb30ca71b3374fad68585fc69cb3b8e7b1691fe
Merge: 634836c 3252fcc
Author: Jiang Xin <jiangxin@ossxp.com>
Date:   Thu Dec 9 14:11:01 2010 +0800

    Commit F: merge I with J

    Signed-off-by: Jiang Xin <jiangxin@ossxp.com>

commit 3252fcce40949a4a622a1ac012cb120d6b340ac8
Author: Jiang Xin <jiangxin@ossxp.com>
Date:   Thu Dec 9 14:00:33 2010 +0800

    commit J.

    Signed-off-by: Jiang Xin <jiangxin@ossxp.com>

 README           |  7 +++++++
 doc/J.txt        |  1 +
 src/.gitignore   |  3 +++
 src/Makefile     | 27 +++++++++++++++++++++++++++
 src/main.c       | 10 ++++++++++
 src/version.h.in |  6 ++++++
 6 files changed, 54 insertions(+)

根据提交次数

最近三次提交:

git log -3

根据时间

显示从两星期前到现在的所有历史记录。具体语法可查询git-ref-parse命令的帮助文件 。

git log –since=」2 weeks ago」

根据分支

将显示在experimental分支,但不在stable分支的历史记录:

git log stable..experimental 

将显示在stable分支但不在experimental分支的历史记录:

git log experimental..stable 

将显示在experimental分支但不在stable分支的历史记录:

git log stable ..experimental

格式化提交记录

查看记录时格式化显示:

git log --pretty=fromat:"%h - %an, %ar : %s"

具体说明:

参数 作用
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用-date=选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明

onelineformat时结合--graph选项,可以看到开头多出一些ASCII字符串表示的 简单图形,形象地展示了每个提交所在的分支及其分化衍合情况。

一个美化日志输出的模式:

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

效果是这样的:

提交、比较、回退

当然这样的命令太长了,所以给定义一个别名:

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

图形界面查看 gitk

gitk也可以定位具体的历史记录。将在GUI中显示从两星期前到现在为止的,且位于 drivers目录下的分支记录信息:

gitk –since=」2 weeks ago」 drivers/ 

版本号

对象

  • 当前分支最近提交:HEAD
  • 上一次提交,加上^,如:HEAD^a839435^
  • 上上次提交,加上^^,如:HEAD^^a839435^^
  • 向前第n次提交,加上^n,如:HEAD^2a839435^2
  • 向前第n次提交,加上~n,如:HEAD~2相当于HEAD^^^^^
  • 提交对应的tree对象,如:HEAD^{tree}a38293^{tree}
  • 提交对应的文件对象,如:HEAD:path/to/filea759382:path/to/file
  • 暂存区中的文件对象,如::path/to/file

取得对象的HASH值

git rev-parse HEAD                 # HEAD的
git rev-parse HEAD master          # 可以同时多个
git rev-parse 6652 776c            # 用ID的前现位
git rev-parse myTag                # 根据tag
git rev-parse myTag^               # 父提交
git rev-parse mytag~3              # 向前三个提交
git rev-parse myTag{tree}          # tag的树
git rev-parse myTag{tree}/aa.txt   # tag的树里的文件
git rev-parse :aa.txt HEAD:aa.txt  # 两个都是指暂存区里的文件
git rev-parse :/"Commit a"         # 根据提交备注的内容查
git rev-parse HEAD@{0} master@{0}  # reflog语法

指定范围

提交、比较、回退

调整一下rev-list命令的格式:

git config --global alias.revls "rev-list --pretty=format:'   -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset'"

从A开始的历史:

$ git revls A
commit 81993234fc12a325d303eccea20f6fd629412712
   - (tag: A) Commit A: merge B with C. (3 年 9 个月前) <Jiang Xin>
commit 0cd7f2ea245d90d414e502467ac749f36aa32cc4
   - (tag: C) commit C. (3 年 9 个月前) <Jiang Xin>
commit 776c5c9da9dcbb7e463c061d965ea47e73853b6e
   - (tag: B) Commit B: merge D with E and F (3 年 9 个月前) <Jiang Xin>
commit beb30ca71b3374fad68585fc69cb3b8e7b1691fe
   - (tag: F) Commit F: merge I with J (3 年 9 个月前) <Jiang Xin>
commit 212efce1548795a1edb08e3708a50989fcd73cce
   - (tag: D) Commit D: merge G with H (3 年 9 个月前) <Jiang Xin>
commit 634836c0e63c16e49d8c62e7e11f22e974db10ed
   - (tag: I) commit I. (3 年 9 个月前) <Jiang Xin>
commit 3252fcce40949a4a622a1ac012cb120d6b340ac8
   - (tag: J) commit J. (3 年 9 个月前) <Jiang Xin>
commit 83be36956c007d7bfffe13805dd2081839fd3603
   - (tag: E) commit E. (3 年 9 个月前) <Jiang Xin>
commit 2ab52ad2a30570109e71b56fa1780f0442059b3c
   - (tag: H) commit H. (3 年 9 个月前) <Jiang Xin>
commit e80aa7481beda65ae00e35afc4bc4b171f9b0ebf
   - (tag: G) commit G. (3 年 9 个月前) <Jiang Xin>

也可以分别指定两个点:

$ git revls D F
commit beb30ca71b3374fad68585fc69cb3b8e7b1691fe
   - (tag: F) Commit F: merge I with J (3 年 9 个月前) <Jiang Xin>
commit 212efce1548795a1edb08e3708a50989fcd73cce
   - (tag: D) Commit D: merge G with H (3 年 9 个月前) <Jiang Xin>
commit 634836c0e63c16e49d8c62e7e11f22e974db10ed
   - (tag: I) commit I. (3 年 9 个月前) <Jiang Xin>
commit 3252fcce40949a4a622a1ac012cb120d6b340ac8
   - (tag: J) commit J. (3 年 9 个月前) <Jiang Xin>
commit 2ab52ad2a30570109e71b56fa1780f0442059b3c
   - (tag: H) commit H. (3 年 9 个月前) <Jiang Xin>
commit e80aa7481beda65ae00e35afc4bc4b171f9b0ebf
   - (tag: G) commit G. (3 年 9 个月前) <Jiang Xin>

用「^」排除一个提交:

$ git revls  D ^G
commit 212efce1548795a1edb08e3708a50989fcd73cce
   - (tag: D) Commit D: merge G with H (3 年 9 个月前) <Jiang Xin>
commit 2ab52ad2a30570109e71b56fa1780f0442059b3c
   - (tag: H) commit H. (3 年 9 个月前) <Jiang Xin>
commit e80aa7481beda65ae00e35afc4bc4b171f9b0ebf
   - (tag: G) commit G. (3 年 9 个月前) <Jiang Xin>

用「..」连接两个点:

$ git revls  G..D
commit 212efce1548795a1edb08e3708a50989fcd73cce
   - (tag: D) Commit D: merge G with H (3 年 9 个月前) <Jiang Xin>
commit 2ab52ad2a30570109e71b56fa1780f0442059b3c
   - (tag: H) commit H. (3 年 9 个月前) <Jiang Xin>

用「...」排除两个点共同的提交:

$ git revls  B...C
commit 0cd7f2ea245d90d414e502467ac749f36aa32cc4
   - (tag: C) commit C. (3 年 9 个月前) <Jiang Xin>
commit 776c5c9da9dcbb7e463c061d965ea47e73853b6e
   - (tag: B) Commit B: merge D with E and F (3 年 9 个月前) <Jiang Xin>
commit 212efce1548795a1edb08e3708a50989fcd73cce
   - (tag: D) Commit D: merge G with H (3 年 9 个月前) <Jiang Xin>
commit 83be36956c007d7bfffe13805dd2081839fd3603
   - (tag: E) commit E. (3 年 9 个月前) <Jiang Xin>
commit 2ab52ad2a30570109e71b56fa1780f0442059b3c
   - (tag: H) commit H. (3 年 9 个月前) <Jiang Xin>
commit e80aa7481beda65ae00e35afc4bc4b171f9b0ebf
   - (tag: G) commit G. (3 年 9 个月前) <Jiang Xin>

用「^@」表示某个点的历史提交,不包含自身:




















查看提交的内容 git show

可以通过commit的值查看当次提交的详细信息:

git show 1e193e9a7bf1699392c6bbe3d53d786b93e6570b

上面那个好长一串啊!其实只要前几个就行了(长到正好可以区分开来):

git show 1e193e9

也可以指定查看分支的名字:

git show master
git show experimenal

查看本次提交的头信息。每一次commit都会有parent commit,可以使用^表示parent:

git show HEAD^  # 查看 HEAD 的父版本的信息 
git show HEAD^^ # 查看 HEAD 的父的父母的信息 
git show HEAD~4 # 查看 HEAD 上溯 4 代的信息

要注意的是git-merge是会产生双父版本的,这种情况这样处理:

git show HEAD^1 # 查看 HEAD 的第一个父代
git show HEAD^2 # 查看 HEAD 的第二个父代

打标签 git tag

可以用一个名字代替序列号(就是常说的「打TAG」啦~):

git tag V1.0 c9d52de78c132f6ea420644c2b7e422b225d258f
git show V1.0
git branch stalbe V1.0 # 建立一个叫stalbe的分支

定位具体的历史记录。显示 V3 之后直至 V7 的所有历史记录:

git log V3..V7 

显示所有 V3 之后的历史记录。

git log V3.. 

注意<since>..<until>中任何一个被省略都将被默认设置为HEAD。所以如果使用 ..<until>的话,git log在大部分情况下会输出空的。

查找文本 git grep

查找文本:

git grep "ibus"      # 在所有版本口查找
git grep "ibus" V1.0 # 在V3版本中查找

二分查找法找出错误的源头

从主干开始:

git bisect start

当前版本有问题,标记为坏:

git bisect bad

向前找一个好的比如找过标记为G的版本没有问题,标记为好。git会用二分法自动转到 中间位置C:

$ git bisect good G
Bisecting: 5 revisions left to test after this (roughly 2 steps)
[0cd7f2ea245d90d414e502467ac749f36aa32cc4] commit C.

可以用git describe确认现在就是在C:

$ git describe
C

测试有没有问题,重复以上操作:

$ git bisect good        # 转到D
$ git bisect good        # 转到B
$ git bisect bad         # 转到E
$ git bisect good        # 转到E

在版本E没有问题,那么就定位到了源头在B。通过引用refs/binsect/bad到该版本:

git checkout bisect/bad

找到以后清除查询状态,回到之前的版本:

git bisect reset



查找代码变化 git blame

通过文件名与行号范围,找出提交的人:

$ git blame -L 6,+5 README
81993234 (Jiang Xin         2010-12-09 14:30:15 +0800  6) * create node A.
0cd7f2ea (Jiang Xin         2010-12-09 14:29:09 +0800  7) * create node C.
776c5c9d (Jiang Xin         2010-12-09 14:27:31 +0800  8) * create node B.
00000000 (Not Committed Yet 2014-09-05 16:44:54 +0800  9) * create hahaahahahahah   node F.
^3252fcc (Jiang Xin         2010-12-09 14:00:33 +0800 10) * create node J.

查看某一行代码是何人何时提交的以及提交时的注释:

$ git show $(git blame example.js -L 4,4 | awk '{print $1}')

Fix animate() for elements just added to DOM

Activating CSS transitions for an element just added to the DOM won’t work in 
either Webkit or Mozilla. To work around this, we used to defer setting CSS 
properties with setTimeout (see 272513b).

This solved the problem for Webkit, but not for latest versions of Firefox. 
Mozilla seems to need at least 15ms timeout, and even this value varies.

A better solution for both engines is to trigger 「layout」. This is done here by 
reading clientLeft from an element. There are other properties and methods that 
trigger layout; see gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-webkit

使用工具查看

GitHub上任何文件的「Blame」视图。

一个非常有效的探索文件历史的方法是使用Vim和Fugitive插件 下载位置 :

  1. 在缓冲区里使用:Gblame打开blame视图;
  2. 如果你需要进一步探索,在blame面板那行按下Shift-P在那次提交的parent上重新 blame;
  3. 按下o打开一个拆分面板显示出blame面板当前选中的提交信息。
  4. 在提交分区使用:Gbrowse打开GitHub web接口的commit;
  5. 按下gq关闭blame面板返回到主缓冲区。

通过:help Gblame查看更多信息。

找到一次commit起源的pull request

用git blame你可以获得每次改动后提交的SHA值,但是提交信息并不总是携带足够的信息 或上下文来解释此次改动背后的原因和依据。无论如何,如果项目背后的团队练习过 GitHub Flow ,那么上下文就可能在pull request讨论里找到:

$ git log --merges --ancestry-path --oneline <SHA>..origin | tail
...
bc4712d Merge pull request #42 from sticky-sidebar
3f883f0 Merge branch 'master' into sticky-sidebar

这里,单次提交的SHA值就足以发现它起源于pull request #42。

git的pickaxe选项-S

有时你会试图找出已丢失的东西:

例如,一个函数过去的某一次调用,该函数如今不再被任何地方调用。找出是哪次提交 引进或删除某个关键字的最好方法就是使用git logpickaxe参数:

$ git log -S<string>

利用该方法你可以探索出一些提交信息,例如删除了对某个函数的调用或添加了某个 CSS类名。

统计改动 git churn

从一个项目的历史不仅要观察个人提交信息,通过整体分析改动集也有可能获得宝贵的见解 。例如,用来包装git loggit-churn是一个简单的却有价值的脚本,可以汇编统计出 哪些文件改动最多。脚本的内容如下:

#!/bin/bash
#
# Written by Corey Haines
# Scriptified by Gary Bernhardt
#
# Put this anywhere on your $PATH (~/bin is recommended). Then git will see it
# and you'll be able to do `git churn`.
#
# Show churn for whole repo:
#   $ git churn
#
# Show churn for specific directories:
#   $ git churn app lib
#
# Show churn for a time range:
#   $ git churn --since='1 month ago'
#
# (These are all standard arguments to `git log`.)

set -e
git log --all -M -C --name-only --format='format:' "$@" | sort | grep -v '^$' | uniq -c | sort | awk 'BEGIN {print "count\tfile"} {print $1 "\t" $2}' | sort -g

例如为了看一个app开发中哪个地方在过去的6个月里受到重点关注:

$ git churn --since='6 months ago' app/ | tail

顺便说一下,这样的分析也强调了在一个项目中因技术债可能出现的潜在问题。经常改动 的某个文件通常标注一个红旗,因为这可能意味着那个文件里的代码要么需要频繁的修复 bug,要么那个文件通常承载了太多的任务,应该被分割为更小的单元。

相似的历史分析方法可以被用于观察谁近来负责代码库某个部分的开发。例如,为了看谁 对一个应用的API部分贡献的最频繁:

$ git log --format='%an' --since='6 months ago' app/controllers/api/ | \
    sort | uniq -c | sort -rn | head
 
 109 Edmond Dantès
  13 Jonathan Livingston
   7 Ebanezer Scrooge

查看改的代码的原始作者是谁

既然你已经读到这里,我将奖励你一个额外的脚本。我称之为git-overwritten:

在github上的位置:https://github.com/mislav/dotfiles/blob/7ac8cbfcd56cfa6c39b5719ea183e87878ea6ed5/bin/git-overwritten

#!/bin/bash
# Usage: git overwritten [--[no-]color] [<head=HEAD>] [<base=origin>]
#
# Aggregates git blame information about original owners of lines changed or
# removed in the '<base>...<head>' diff.
#
# Each line of output represents a past commit, and consists of:
# - number of lines from the commit that this diff affects
# - commit date
# - commit sha
# - author name
# - commit subject message
#
# The commits are listed in the reverse chronological order.
#
# Author: Mislav Marohnić

set -e

unset colorize
unset head
unset base

abort() {
  "$0" --help | head -1 >&2
  exit 1
}

while [ "$#" -gt 0 ]; do
  case "$1" in
  --color ) colorize=1 ;;
  --no-color ) colorize= ;;
  -h | --help )
    sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0"
    exit 0
    ;;
  -* | "") abort ;;
  * )
    if [ -z "$head" ]; then head="$1"
    elif [ -z "$base" ]; then base="$1"
    else abort
    fi
    ;;
  esac
  shift 1
done

head="${head:-HEAD}"
base="${base:-origin}"

[ -t 1 ] && colorize="${colorize-1}"

color() {
  if [ -n "$colorize" ]; then
    printf "\e[0;%dm%s\e[m" "$1" "$2"
  else
    echo -n "$2"
  fi
}

git diff "${base}...${head}" --diff-filter=DM --no-prefix -w -U0 | grep -v '^+' | awk '
  function print_range() {
    printf "-L %d,%d -- %s\n", start, stop, file
  }
  /^--- / {
    sub(/^--- /, "")
    file = $0
    next
  }
  /^-/ {
    if (!start) start = stop = diffstart
    else stop += 1
    next
  }
  start {
    print_range()
    start = 0
  }
  /^@@ / {
    sub(/^-/, "", $2)
    diffstart = int($2)
    start = 0
  }
  END {
    if (start) print_range()
  }
' | xargs -L1 git blame --line-porcelain "$base" | awk -v OFS=$'\t' '
  BEGIN { num_lines = name_length = 0 }
  function val() { sub(/^[a-z-]+ /, ""); return $0 }
  /^[0-9a-f]{40} / { sha = $1 }
  /^author / {
    author = val()
    len = length(author)
    if (len > name_length) name_length = len
  }
  /^committer-time / { time = val() }
  /^summary / { msg = val() }
  /^\t/ {
    print time, sha, author, msg
    num_lines += 1
  }
  END { print num_lines, name_length }
' | sort -n | {
  read num_lines name_length
  if [ "$num_lines" -eq 0 ]; then
    color 31 "Warning: " >&2
    echo "no changed/removed lines found in the $base...$head diff" >&2
  fi

  while IFS=$'\t' read time sha author msg; do
    date -r $time "+%Y-%m-%d" | tr -d $'\n'
    printf ' '
    color 33 "${sha:0:7}"
    printf '  '
    color 32 "$(printf "%${name_length}s" "$author")"
    printf ': '
    echo "$msg"
  done
} | sort -r | uniq -c

它能够显示出指定分支处所改动或删除的行其原始作者的blame信息:

$ git overwritten feature origin/master
 
  28 2014-02-04 1fb2633  Mislav Marohni?: Add Makefile for building and testing
   1 2014-01-13 b2d896a  Jingwen Owen Ou: Add -t to mktemp in script/make
  17 2014-01-07 385ccee  Jingwen Owen Ou: Add script/make for homebrew build

当打开每个GitHub Flow的pull requests时,这是非常有用的;你要是想要你的pull request被同伴审查,但是你可能不太确定该ping谁,使用git-overwritten你可以获得 你刚刚改动代码行的原始作者名字,所以你就会知道当打开一个pull request时 @-mention谁了。