21

假设我的存储库中有两个版本......每个版本都被标记如下:

  • 标签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。

4

4 回答 4

18

这是一个简单的 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
于 2014-10-29T20:28:03.350 回答
17

您可以显示子模块更改,但仅在使用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 }
'
于 2012-08-02T23:16:12.417 回答
2

如果您使用 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 命令中失败(例如 in git --git-dir ./.git/modules/libA log --oneline f374fbf^!),因为它总是将工作目录更改为子模块根目录(无论出于何种原因)。

于 2018-11-03T16:36:35.370 回答
1

如果你在 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
于 2014-09-16T01:35:28.263 回答