Bash функция, чтобы найти все Git фиксации, в которых файл (чье имя совпадает с регулярным выражением) имеет * измененный *

У меня следующая функция bash, которая ищет все файлы в репозитории, имя файла которого соответствует регулярному выражению. В настоящее время он находит все коммиты, в которых существует файл. Как это можно изменить, чтобы он просматривал только файлы, которые были отредактированы (созданы, изменены или удалены) в каждой фиксации?

Это было мое первоначальное намерение для этой функции. Я был удивлен, увидев, что результаты намного шире, чем ожидалось. Причина, по которой я пытаюсь это сделать: я создал файл давным-давно, и в какой-то момент между ним и потом я случайно удалил из него важный раздел. Мне нужен список всех точек (коммитов), в которых этот файл был изменен, поэтому я могу быстро вернуться к версии, содержащей недостающий раздел, и вставить ее обратно в текущую версию.

:<<COMMENT
    Searches all commits in the current git repository containing a file whose name matches a regular expression.

    Usage: gitf <regex>

    Parameter is required, and must be at least one non-whitespace character.

    The original version of this function was based on the GitHub gist
    - https://gist.github.com/anonymous/62d981890eccb48a99dc
    written by Qaru user Handyman5
    - https://stackoverflow.com/users/459089/handyman5
    which is based on this SO question:
    - https://stackoverflow.com/info/372506/how-can-i-search-git-branches-for-a-file-or-directory/372654#372654

    The main section of this function was authored by Qaru user
    SwankSwashbucklers.
    - https://stackoverflow.com/users/2615252/swankswashbucklers
    - /info/22704/bash-function-to-search-git-repository-for-a-filename-that-matches-regex/166158#166158

    Short description: Stored in GITF_DESC
COMMENT
#GITF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
GITF_DESC="gitf [searchterm]: Searches the current git repository for the file name that matches a regular expression.\n"

Тело

gitf()  {
    #Exit if no parameter is provided (if it the empty string)
        param=$(echo "$1" | trim)
        echo "$param"
        if [ -z "$param" ]  #http://tldp.org/LDP/abs/html/comparison-ops.html
        then
          echo "Required parameter missing. Cancelled"; return
        fi

    wasFound="0";
    LOC=refs/remotes/origin # to search local branches only: 'refs/heads'
    ref="%(refname)"
    for branch in `git for-each-ref --format="$ref" $LOC`; do
        for commit in `git rev-list $branch | grep -oP ^.\{7\}`; do
            found=$(git ls-tree -r --name-only $commit | grep "$param")
            if [ $? -eq 0 ]; then
                echo "${branch#$LOC/}: $commit:"
                while read line; do
                    echo "  $line"
                done < <(echo "$found")
                wasFound="1";
            fi
        done
    done

    if [ "$wasFound" -eq "0" ]; then
        echo "No files in this repository match '$param'."
    fi
}

Ответ 1

Если вы можете жить с шаблоном glob shell, а не с полномасштабным регулярным выражением, рассмотрите

git log -p --diff-filter=AMD --branches --tags -- "foo*bar.sh"

С -p вы видите дельта вместе с сообщением фиксации, автором, SHA1 и т.д. Параметр --diff-filter=AMD выбирает только те коммиты, в которых файлы, о которых идет речь, были A M офицированный, или D eleted. Для поиска пультов, а также локальных ветвей и тегов используйте --all, а не --branches --tags. Наконец, обратите внимание на --, который вводит шаблоны пути, которые вы хотите процитировать, чтобы позволить git выполнять сопоставление glob.

Ответ 2

Используйте git diff-tree -r --name-only --no-commit-id (возможно, с --stdin) вместо git ls-tree -r --name-only. Используйте -m или -c, если вас интересуют слияния, -m или -c, если вы хотите принять во внимание соответственно, переименовать и скопировать обнаружение.

Или лучше разобрать вывод git diff-tree -r.

Nb. данный код серьезно субоптимален (среди прочего, вы проверяете несколько раз те же самые коммиты).

Ответ 3

Вы можете пройти и использовать git diff, чтобы посмотреть, что изменилось между каждым из коммитов. Что-то вроде этого:

for branch in `git for-each-ref --format="$ref" $LOC`;
do
    previous_commit=""
    for commit in `git rev-list $branch | grep -oP ^.\{7\}`;
    do
        if [ "$previous_commit" != "" ];
        then
            found=$(git diff --name-only $previous_commit $commit | grep "$param")
            if [ $? -eq 0 ];
            then
                echo "${branch#$LOC/}: $commit:"
                while read line;
                do
                    echo "  $line"
                done < <(echo "$found")
                echo
                wasFound="1";
            fi
        fi
        previous_commit="$commit"
    done
done

Ответ 4

Я придумал эту функцию, основанную на ответе Грега Бэкона. Мне изначально требовалось регулярное выражение, но глобусы хорошо подходили к счету. Я также ожидал, что потребуется функция циклирования, но единственная строка git log - это все, что нужно.

Во-первых, функция полезности:

#https://stackoverflow.com/info/369758/how-to-trim-whitespace-from-bash-variable#comment21953456_3232433
alias trim="sed -e 's/^[[:space:]]*//g' -e 's/[[:space:]]*\$//g'"

Заголовок документации:

:<<COMMENT
   Searches all commits in the current git repository containing a file
   that has *changed*, whose name matches a glob. If the glob does not
   contain any asterisks, then it is surrounded by them on both sides.


   Usage:
      gitf "05"     #Equivalent to "*05*"
      gitf "05_*"

   Parameter is required, and must be at least one non-whitespace character.

   See:
   - https://stackoverflow.com/info/28119379/bash-function-to-find-all-git-commits-in-which-a-file-whose-name-matches-a-rege/28120305
   - https://stackoverflow.com/info/28094136/bash-function-to-search-git-repository-for-a-filename-that-matches-regex/28095750
   - https://stackoverflow.com/info/372506/how-can-i-search-git-branches-for-a-file-or-directory/372654#372654

   The main "git log" line is based on this answer
   - https://stackoverflow.com/a/28119940/2736496
   by Qaru user Greg Bacon
   - https://stackoverflow.com/users/123109/greg-bacon

   With thanks to SwankSwashbucklers
   - https://stackoverflow.com/users/2615252/swankswashbucklers

   Short description: Stored in GITF_DESC
COMMENT
#GITF_DESC: For "aliaf" command (with an 'f'). Must end with a newline.
GITF_DESC="gitf [glob]: Searches all commits in the current git repository containing a file that has *changed*, whose name matches a glob.\n"

Тело

gitf()  {
   #Exit if no parameter is provided (if it the empty string)
      param=$(echo "$1" | trim)
      echo "$param"
      if [ -z "$param" ]  #http://tldp.org/LDP/abs/html/comparison-ops.html
      then
        echo "Required parameter missing. Cancelled"; return
      fi

   #https://stackoverflow.com/info/229551/string-contains-in-bash/229606#229606
   if [[ $param != *"*"* ]]
   then
     param="*$param*"
   fi

   echo "Searching for \"$param\"..."

   git log -p --name-only --oneline --diff-filter=AMD --branches --tags -- "$param"
}

Пример вывода:

$ gitf 05_
05_
Searching for "*05_*"...
14e5cdd Quick save (no message): 01-21-2015__14_36_11
non_django_files/wordpress_posts/templates/05_login_remember_me.html
2efdeb1 Part four final. Changed auth/tests in post to auth/tests_login_basic.
non_django_files/wordpress_posts/templates/05_login_remember_me.html
526ca01 Part four final. Renamed auth/tests to test_basic_login, so Java doesn't need to parse the py file in future par
non_django_files/wordpress_posts/templates/05_login_remember_me.html
7c227f3 Escaped unescaped dollar-signs in initial_script_sh snippet, and added delete-all-but-.git command in comment at
non_django_files/wordpress_posts/templates/05_login_remember_me.html
e68a30a Part four final, moved post output folder into wordpress_posts.
non_django_files/wordpress_posts/templates/05_login_remember_me.html
3c5e4ec Part two final. Corrections/minor changes to all posts.
non_django_files/wordpress_posts/templates/05_login_remember_me.html
3a7dac9 Finished part one.
non_django_files/wordpress_posts/templates/05_login_remember_me.html
f87540e Initial commit
non_django_files/wordpress_posts/templates/05_login_remember_me.html