簡體   English   中英

包含帶有“git log”的子模塊提交消息

[英]Include submodule commit messages with “git log”

假設我的存儲庫中有兩個版本......每個版本都被標記如下:

  • 標簽1
  • 標簽2

現在假設提交更新了子模塊引用以指向 Tag1 和 Tag2 之間的新子模塊提交。 我運行以下命令,並得到這個:

# show commits between these two tags
git log Tag1..Tag2


commit be3d0357b93322f472e8f03285cb3e1e0592eabd
Author: James Johnston <snip>
Date:   Wed Jan 25 19:42:56 2012 +0000

    Updated submodule references.

在這種情況下,唯一的變化是子模塊的更新。 如何讓子模塊提交與父存儲庫提交交錯?

具體來說,在這個例子中,假設父存儲庫指向子模塊中的 SubTag5 標簽。 子模塊中稍后的兩次提交是一個 SubTag6 標簽。 顯示的提交更新了子模塊指針以指向 SubTag6 而不是 SubTag5。 我想要做的是git log ,除了它已經打印的提交之外,還打印兩個子模塊提交,將子模塊從 SubTag5 帶到 SubTag6。

這是一個簡單的 bash 命令,它創建一個 ASCII 提交圖(類似於 gitk),當超級項目中的子模塊發生更改時,它會交錯相關的子模塊提交。 它打印出每次提交的完整補丁,然后使用 grep 過濾掉補丁內容,只留下摘要行和子模塊更改。

git log --graph --oneline -U0 --submodule Tag1..Tag2 | grep -E '^[*| /\\]+([0-9a-f]{7} |Submodule |> |$)'

它產生與此類似的輸出:

* 854407e Update submodule
| Submodule SUB 8ebf7c8..521fc49:
|   > Commit C
* 99df57c Commit B
* 79e4075 Commit A

您可以顯示子模塊更改,但僅限於使用git log -p 以下命令顯示每個提交和子模塊更改的完整差異。

git log -p --submodule=log

子模塊提交消息將如下列出:

Submodule <submodule-name> <starting-commit>..<ending-commit>:
> Commit message 1
> Commit message 2
...
> Commit message n

如果您對閱讀每個提交的完整差異不感興趣,您可以匹配並過濾掉這些部分:

git log -p --submodule=log | awk '
/^commit/ { add=1 } # Start of commit message
/^diff --git/ { add=0 } # Start of diff snippet
{ if (add) { buf = buf "\n" $0 } } # Add lines if part of commit message
END { print buf }
'

如果您使用 bash,您可以使用以下腳本來顯示嵌入到超級項目日志的子模塊提交日志。

#!/bin/bash 

# regular expressions related to git log output
# when using options -U0 and --submodule=log
kREGEXP_ADD_SUBMODLE='0+\.\.\.[0-9a-f]+'
kREGEXP_REM_SUBMODLE='[0-9a-f]+\.\.\.0+'

# --------------------------------------------------------------------
# function submodule_log
# --------------------------------------------------------------------
# 
# print a log of submodule changes for a range of commits
#
# arguments : see start of function body for details  
# 
function submodule_log {

    sm_present=$1; # presence 0: no, 1: yes
    sm_status=$2   # status   0: as is, 1: added submodule, 2: removed submodule 
    sm_name=$3     # name
    sm_id_base=$4  # base commit id added changes
    sm_id_now=$5   # final commit id added changes

    cur_dir=`pwd`

    # commits cannot be accessed if sbumodule working tree was removed, 
    # show submodule commits in details only if directory exists
    #
    # note: As of git 1.9, in .git/modules/<submodule-name>
    #       still the entire gitdir is present, just git won't successfully
    #       run something like 'git --git-dir .git/modules/<submodule-name> log f374fbf^!'
    #       from the superproject root dir. It fails as it want's to change directory to
    #       to the submodule working tree at '../../../<submodule-name>' to get the log.
    #       If one just creates it as an empty directory the command succeeds, but
    #       we cannot force the user to leave an empty directory. So just a hint
    #       is output to suggest creation of directory to get full log.

    #echo " $submod_entry"

    if [ -e $sm_name ]  
    then    
        cd $sm_name

        # if submodule not present in current version of superproject
        # can retrieve git log info only by using option '--git-dir'
        # -> use always option --git-dir

        git_dir_opt="--git-dir $cur_dir/.git/modules/$sm_name"
        git_cmd_base="git $git_dir_opt log --format=\"  %Cred%h %s%Creset\""

        if [ $sm_status -eq 0 ]
        then
            # modified module: output info on added commit(s)
            eval "$git_cmd_base ${sm_id_base}..${sm_id_now}"
        fi

        if [ $sm_status -eq 1 ]
        then
            # new module: output only info on base commit    
            eval "$git_cmd_base ${sm_id_now}^!"
        fi

        if [ $sm_status -eq 2 ]
        then
            # removed module: output only info on last commit  
            eval "$git_cmd_base ${sm_id_base}^!"
        fi

        cd $cur_dir 
    else
        echo " Skip info on submodule $sm_name (not present in current working tree)"
        echo " For full log, please add empty directory $sm_name for full log."
    fi 
}

# --------------------------------------------------------------------
# main script 
# --------------------------------------------------------------------

# Get the log of the parent repository (only SHA1 and parent's SHA1), 
# use files as amount of data might be huge in older repos 

# get commit ids as array
readarray -t log_commitids < <(git log --format="%H")

# get commit ids of parent commits 
readarray -t log_parents < <(git log --format="%P")

for ((c_idx=0; $c_idx<${#log_commitids[@]}; c_idx=$c_idx+1))
do
    # Can only be one commit id, but remove trailing newline and linefeed
    commit="${log_commitids[$c_idx]//[$'\r\n']}"

    # Can be more than one parent if it's a merge
    # remove trailing newline and linefeed
    parents="${log_parents[$c_idx]//[$'\r\n']}"    
    parents_a=($(echo $parents))
    num_parents=${#parents_a[@]}

    # check if merge commit, prefix next commit with M as they are merge
    merge_prefix=""
    if [ $num_parents -ge 2 ] 
    then
        merge_prefix="M$num_parents" 
    fi

    # Print the two-line summary for this commit
    git log --format="%Cgreen%h (%cI %cN)%Creset%n %Cgreen$merge_prefix%Creset %s" $commit^!

    #echo "found $num_parents parents"

    if [ "$parents" = "" ]
    then
       unset parents
    else

        for parent in $parents
        do
            # Find entires like 
            #  "Submodule libA 0000000...f374fbf (new submodule)"      or
            #  "Submodule libA e51c470...0000000 (submodule deleted)"  or 
            #  "Submodule libA f374fbf..af648b2e:"
            # in supermodules history in order to determine submodule's
            # name and commit range describing the changes that 
            # were added to the supermodule. Two regular expressions
            # kREGEXP_ADD_SUBMODLE and kREGEXP_REM_SUBMODLE are used
            # to find added and removed submodules respectively.

            readarray -t submod < <(git log -U0 --submodule=log ${parent}..${commit} \
            | grep -U -P '^Submodule \S+ [0-9a-f]+')

            for ((s_idx=0; $s_idx<${#submod[@]}; s_idx=$s_idx+1))
            do
                # remove trailing newline and linefeed
                submod_entry="${submod[$s_idx]//[$'\r\n']}"

                #echo mainly unfiltered as to show submod name and its
                #commit range stored in repo's log
                echo " $submod_entry"

                # remove preceding info 'Submodule ' as we already know that :-)
                submod_entry="${submod_entry/Submodule }"

                # if viewing repository version for which submodules do not exist
                # they are reported with correct commit ids but trailing text
                # is different, first assume it is present then check submod_entry
                submod_present=1
                if [[ "$submod_entry" =~ "commits not present" ]]
                then
                   submod_present=0

                   # remove trailing info about deleted submodule, if any
                   submod_entry="${submod_entry/'(commits not present)'}"                 
                fi

                # find with submodule got added/modified/removed by this superproject commit
                # assume 'modified' submodule, then check if commit range indicates
                # special cases like added/removed submodule
                sub_status=0                
                if [[ "$submod_entry" =~ $kREGEXP_ADD_SUBMODLE ]]
                then
                   sub_status=1

                   # remove trailing info about new submodule, if any
                   submod_entry="${submod_entry/'(new submodule)'}" 
                fi

                if [[ "$submod_entry" =~ $kREGEXP_REM_SUBMODLE ]]
                then
                   sub_status=2

                   # remove trailing info about deleted submodule, if any
                   submod_entry="${submod_entry/'(submodule deleted)'}"
                fi

                # create log output for submod_entry 
                # - pass contents in submod_entry as separate arguments
                #   by expanding variable and using eval to execute resulting code

                #replace dots by spaces as to split apart source and destination commit id
                submod_entry="${submod_entry//./ }"
                #remove colon behind last commit id, if any
                submod_entry="${submod_entry//:/}"

                eval "submodule_log $submod_present $sub_status $submod_entry"
            done    
        done
    fi
done

該腳本類似於上面列出的 PowerShell 腳本,但以更密集的格式解決了一些問題和輸出。 它可以處理新的子模塊刪除的子模塊

為了正確顯示不再屬於超級項目的子模塊(刪除的子模塊)的日志信息,至少子模塊根目錄(可以為空)必須保留在存儲庫中。 否則 Git(在 Windows 上使用 2.19.0 版測試)將在 log 命令中失敗(例如在git --git-dir ./.git/modules/libA log --oneline f374fbf^! ),因為它總是改變工作目錄到子模塊根目錄(無論出於何種原因)。

如果您使用的是 Windows,則可以使用此 PowerShell 腳本:

function Parse-SubmoduleDiff($rawDiffLines) {
    $prefix = "Subproject commit "
    $oldCommitLine = $($rawDiffLines | where { $_.StartsWith("-" + $prefix) } | select -First 1)
    $newCommitLine = $($rawDiffLines | where { $_.StartsWith("+" + $prefix) } | select -First 1)

    if ($newCommitLine -eq $null) {
        return $null
    }

    $oldCommit = $null
    if ($oldCommitLine -ne $null) {
        $oldCommit = $oldCommitLine.Substring($prefix.Length + 1)
    }
    $newCommit = $newCommitLine.Substring($prefix.Length + 1)
    return @{ OldCommit = $oldCommit; NewCommit = $newCommit }
}

# Get the paths of all submodules
$submodulePaths = $(git submodule foreach --quiet 'echo $path')
if ($submodulePaths -eq $null) {
    $submodulePaths = @()
}

# Get the log of the parent repository (only SHA1)
$log = $(git log --format="%H %P" $args)
foreach ($line in $log) {

    $parts = $line.Split()
    $commit = $parts[0]
    $parents = $parts[1..$parts.Length]

    # Print the summary for this commit
    git show --format=medium --no-patch $commit
    echo ""

    # Can be more than one parent if it's a merge
    foreach ($parent in $parents) {
        # List the paths that changed in this commit
        $changes = $(git diff --name-only $parent $commit)

        if ([System.String]::IsNullOrWhiteSpace($parent)) {
            continue;
        }

        foreach ($path in $changes) {

            if ($submodulePaths.Contains($path)) {
                # if it's a submodule, the diff should look like this:
                # -Subproject commit 1486adc5c0c37ad3fa2f2e373e125f4000e4235f
                # +Subproject commit a208e767afd0a51c961654d3693893bbb4605902
                # from that we can extract the old and new submodule reference

                $subDiff = $(git diff $parent $commit -- $path)
                $parsed = Parse-SubmoduleDiff($subDiff)
                if ($parsed -eq $null) {
                    continue;
                }

                # Now get the log between the old and new submodule commit
                $oldCommit = $parsed.OldCommit
                $newCommit = $parsed.NewCommit
                echo "Submodule '$path'"
                if ($oldCommit -ne $null) {
                    $range = $($oldCommit + ".." + $newCommit)
                } else {
                    $range = $newCommit
                }

                git --git-dir $path/.git log $range | foreach { "  |  " + $_ }
                echo ""
            }
        }
    }
}

顯然它可以被翻譯成 bash 以在 Linux 上使用。 一般原則是這樣的:

for each commit in the parent repo
    print the commit summary
    for each submodule that has changed in this commit
        get the old and new commit hashes of the submodule
        print the log of the submodule between those commits
    end for
end for

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM