Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Include submodule commit messages with "git log"

Tags:

git

Suppose I have two versions in my repository... each has been tagged as follows:

  • Tag1
  • Tag2

Now suppose that a commit updated a submodule reference to point to a new submodule commit between Tag1 and Tag2. I run the following command, and get this:

# 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.

In this case, the only change was an update of the submodule. How do I get the submodule commits to be interleaved with the parent repository commits?

Specifically, in this example, suppose that the parent repository points to the SubTag5 tag in the submodule. Two commits later in the submodule is a SubTag6 tag. The commit shown updated the submodule pointer to point to SubTag6 instead of SubTag5. What I would like to do is have git log, in addition to the commit it already printed, print the two submodule commits as well that brought the submodule from SubTag5 to SubTag6.

like image 401
James Johnston Avatar asked May 24 '12 16:05

James Johnston


3 Answers

Here's a simple bash command that creates an ASCII commit graph (similar to gitk) that interleaves the relevant submodule commits when a submodule gets changed in the superproject. It prints out the full patch for every commit and then uses grep to filter out the patch contents leaving only the summary lines and submodule changes.

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

It produces output similar to this:

* 854407e Update submodule
| Submodule SUB 8ebf7c8..521fc49:
|   > Commit C
* 99df57c Commit B
* 79e4075 Commit A
like image 182
Vynce Avatar answered Nov 08 '22 09:11

Vynce


You can display the submodule changes, but only when using git log -p. The following command shows the full diff of each commit and submodule changes.

git log -p --submodule=log

Submodule commit messages will be listed like this:

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

If you are not interested in reading the full diff of each commit, you can match and filter out those parts:

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 }
'
like image 44
iblue Avatar answered Nov 08 '22 08:11

iblue


If you are using bash you can use the following script to show submodule commit log embedded to superproject log.

#!/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

The script is similar to the PowerShell script listed above but resolves some issues and outputs in a more dense format. It can handle new submodules and removed submodules.

To properly show log information for submodules that aren't part of the superproject anymore (removed submodule), at least the submodule root directory (can be empty) has to remain in the repository. Otherwise Git (tested with version 2.19.0 on Windows) would fail in the log command (for example in git --git-dir ./.git/modules/libA log --oneline f374fbf^!) as it always changes working directory to the submodule root directory (for whatever reason).

like image 2
famoses Avatar answered Nov 08 '22 09:11

famoses