Команда Git для сохранения тайника без изменения рабочего дерева?

Я хотел использовать команду git, которая сохраняет тайник без изменения моего рабочего дерева, в качестве облегченного резервного копирования, безопасного от любых сбросов git или чего-то еще, что я мог бы сделать, чтобы испортить свой индекс. По сути, это функциональный эквивалент «git stash save && git stash apply» за исключением того, что рабочая копия никогда не трогается, так как это может сделать некоторые текстовые редакторы / IDE капризными.

Что-то вроде этого приближается к тому, что я хочу, но не совсем:

git update-ref refs/stash `git stash create "Stash message"`

Это работает функционально, но проблема, с которой я сталкиваюсь, заключается в том, что в "git stash list" не отображается никакого сообщения тайника, даже если в фактическом коммите тайника есть мое сообщение. Учитывая, насколько большим может стать тайник, сообщения тайника очень важны.


person Eliot    schedule 11.06.2011    source источник


Ответы (5)


Благодаря совету Чарльза я создал сценарий bash, который делал именно то, что хотел (у меня возникали проблемы с реализацией этого только как псевдонима). Требуется дополнительное сообщение тайника, как и в случае с git stash save. Если ничего не указано, будет использоваться сообщение по умолчанию, сгенерированное git stash.

#!/bin/sh
#
# git-stash-snap
# Save snapshot of working tree into the stash without modifying working tree.
# First argument (optional) is the stash message.
if [ -n "$1" ]; then
        git update-ref -m "$1" refs/stash "$(git stash create \"$1\")"
else
        HASH=`git stash create`
        MESSAGE=`git log --no-walk --pretty="tformat:%-s" "$HASH"`
        git update-ref -m "$MESSAGE" refs/stash "$HASH"
fi

Изменить: как указано в комментарии ниже, сохранения этого скрипта как git-stash-snap где-нибудь на вашем пути достаточно, чтобы иметь возможность вызвать его, набрав git stash-snap.

Приятно то, что даже если вы удалите тайник, созданный этим методом, вы все равно сможете увидеть сообщение тайника, используя git log [commit-hash] о зависшей фиксации!

Изменить: начиная с git 2.6.0 вы можете добавить --create-reflog в update-ref, а затем git stash list покажет это, даже если git stash не использовался раньше.

Изменить: Git представил новую подкоманду stash под названием stash push, поэтому я обновил свою рекомендацию по присвоению имени этому скрипту с git-stash-push на git-stash-snap.

person Eliot    schedule 11.06.2011
comment
Если вы вызываете этот файл git-stash-push и помещаете его где-нибудь в свой PATH, нет необходимости создавать для него псевдоним. git stash-push найдет (и позвонит) git-stash-push - person Stefan Näwe; 12.06.2011
comment
Действительно? Интересно, попробую. - person Eliot; 13.06.2011
comment
Одно предостережение в отношении приведенного выше сценария: похоже, он не работает должным образом, когда тайник пуст. - person Eliot; 24.06.2011
comment
@ Элиот, это потрясающий материал, спасибо, что собрал его! - person Dan Rosenstark; 06.07.2011
comment
Основываясь на этом ответе, я добавил следующее в раздел [alias] моего ~/.gitconfig, который должен обеспечивать такое же поведение: stash-push = "!f() { if (( $# > 0)); then branch=$(git branch | sed -n 's/^\\* //p'); git update-ref -m \"On ${branch}: $*\" refs/stash $(git stash create \"On ${branch}: $*\"); else hash=$(git stash create); msg=$(git log --no-walk --pretty=\"tformat:%-s\" $hash); git update-ref -m \"$msg\" refs/stash $hash; fi;}; f" - person me_and; 02.07.2012
comment
stash-push - плохая репутация для этого сценария по двум причинам. (1) Это не зеркало stash-pop. Если вы используете это для добавления в свой стек, ваша рабочая копия останется неизменной. Затем, когда вы попытаетесь stash-pop удалить его из своего стека, он попытается повторно применить те же изменения, которые у вас уже были / были в вашем рабочем дереве. (2) stash-push теперь существует как команда git, которая заменила теперь устаревшую команду stash save. git-scm.com/docs/git-stash - person cp.engr; 13.03.2018
comment
@ cp.engr Спасибо за внимание. Я просмотрел документацию по новому git stash push, но, похоже, нет возможности добиться поведения моего скрипта. У вас есть предложения по новому имени? stash-backup может быть? Рад обновить ответ ... - person Eliot; 14.03.2018
comment
@Eliot, я думаю, stash-save было бы хорошим именем, если бы не git, который уже использовал его, когда они должны были использовать push для соответствия pop. ;) Может stash-snapshot, а для краткости stash-snap? - person cp.engr; 15.03.2018

git stash store "$(git stash create)"

Будет создана запись в тайнике, аналогичная той, что вы получили бы с git stash, не касаясь и не очищая рабочий каталог и индекс.

Если вы проверите список тайников или посмотрите на весь график фиксации (включая тайник), вы увидите, что он аналогичен результату, который вы получили бы при обычном вызове git stash. Просто сообщение в списке тайников отличается (обычно это что-то вроде stash @ {0}: WIP on master: 14e009e init commit, здесь мы получим stash @ {0}: Created via git stash store)

$ git status --short
M file.txt
A  file2.txt

$ git stash list

$ git stash store "$(git stash create)"

$ git stash list
stash@{0}: Created via "git stash store".

$ git stash show 'stash@{0}'
 file.txt  | 2 +-
 file2.txt | 2 ++
 2 files changed, 3 insertions(+), 1 deletion(-)

$ git log --oneline --graph --all
*   85f937b (refs/stash) WIP on master: 14e009e init commit
|\
| * 26295a3 index on master: 14e009e init commit
|/
* 14e009e (HEAD -> master) init commit

$ git status
M file.txt
A  file2.txt

Еще немного объяснения:

Запись git stash представлена ​​обычными коммитами с определенной структурой. По сути, это обычный объект фиксации, у которого есть 2 родителя (или 3, если вы используете параметр --include-untracked) (подробнее 1, 2).

git stash create создает этот коммит, который представляет запись в тайнике и возвращает вам имя объекта (SHA-1) объекта фиксации (тот, у которого есть 2 или 3 родителя). Это зависшая фиксация (вы можете проверить это, вызвав git fsck после git stash create). Вам нужно указать refs/stash на эту висящую фиксацию, и вы сделаете это git stash store (или git update-ref, как в других ответах, потому что git stash store использует git update-ref для выполнения своей работы).

Хорошо бы взглянуть на реальный исходный код git stash push и увидеть, что он в основном вызывает _ 15_ и git stash store, а затем выполняет некоторая логика для очистки файлов (которая зависит от того, какие параметры вы использовали в git stash push).

person Mariusz Pawelski    schedule 15.11.2018
comment
Отличный ответ и объяснение. Определенно новая часть моего рабочего процесса. Он отлично работает, когда я хочу сделать sed -i 's/oldname/newname/'. Не нужно беспокоиться о достаточном резервном копировании или о чем-то подобном. - person Nathan Chappell; 03.07.2020

Вам нужно передать сообщение update-ref, а не stash create, поскольку stash create не принимает сообщения (он не обновляет никакие ссылки, поэтому у него нет записи журнала ссылок для заполнения).

git update-ref -m "Stash message" refs/stash "$(git stash create)"
person CB Bailey    schedule 11.06.2011
comment
Спасибо, это то, что я искал! Однако вы ошибаетесь в том, что git stash create не принимает сообщение. Предоставленное ему сообщение становится сообщением фиксации. Итак, чтобы идеально имитировать фиксацию тайника, созданную git stash save, я думаю, что следующая команда выполнит свою работу: git update-ref -m "Stash message" refs/stash "$(git stash create 'Stash message')". Можете ли вы придумать умный способ добавить это прямо как псевдоним git? В противном случае я могу сделать это простым сценарием bash. - person Eliot; 12.06.2011
comment
@Eliot: Сообщение, которое появляется в git stash list, исходит из журнала ссылок, а не из сообщения фиксации. Это то, о чем я имел в виду. Вы правы, что create действительно принимает сообщение, но это не задокументированная функция, поэтому я не знаю, преднамеренно ли это предназначено для работы или это просто артефакт реализации. - person CB Bailey; 12.06.2011
comment
Да, хорошее замечание. Я обнаружил это случайно. Я нашел несколько документов в git wiki об использовании аргументов в псевдонимах, так что все готово! - person Eliot; 12.06.2011
comment
Спасибо @Charles Bailey и +1, вы помогли мне решить еще одну проблему с git. Я немного переделал все это stackoverflow.com/questions/6589050/. - person Dan Rosenstark; 06.07.2011

Вдохновленный решением Элиота, я немного расширил его сценарий:

#!/bin/sh
#
# git-stash-push
# Push working tree onto the stash without modifying working tree.
# First argument (optional) is the stash message.
#
# If the working dir is clean, no stash will be generated/saved.
#
# Options:
#   -c "changes" mode, do not stash if there are no changes since the
#      last stash.
if [ "$1" == "-c" ]; then
        CHECK_CHANGES=1
        shift
fi


if [ -n "$1" ]; then
        MESSAGE=$1
        HASH=$( git stash create "$MESSAGE" )
else
        MESSAGE=`git log --no-walk --pretty="tformat:%-s" "HEAD"`
        MESSAGE="Based on: $MESSAGE"
        HASH=$( git stash create )
fi

if [ "$CHECK_CHANGES" ]; then
        # "check for changes" mode: only stash if there are changes
        # since the last stash

        # check if nothing has changed since last stash
        CHANGES=$( git diff stash@{0} )
        if [ -z "$CHANGES" ] ; then
                echo "Nothing changed since last stash."
                exit 0
        fi
fi

if [ -n "$HASH" ]; then
        git update-ref -m "$MESSAGE" refs/stash "$HASH"
        echo "Working directory stashed."
else
        echo "Working tree clean, nothing to do."
fi

Я внес следующие изменения в сценарий Элиота:

  1. Когда рабочий каталог чист, скрипт завершится корректно.
  2. При использовании переключателя -c, если нет изменений по сравнению с последним тайником, сценарий завершится. Это полезно, если вы используете этот скрипт как «машину времени», создавая автоматическое хранилище каждые 10 минут. Если ничего не изменилось, новый тайник не создается. Без этого переключателя вы можете получить n одинаковых последовательных тайников.

Не то чтобы для правильной работы переключателя -c должен существовать хотя бы один тайник, иначе сценарий выдаст ошибку на git diff stash@{0} и ничего не сделает.

Я использую этот сценарий как «машину времени», делая снимки каждые 10 минут, используя следующий цикл bash:

 while true ; do date ; git stash-push ; sleep 600 ; done
person andimeier    schedule 09.03.2016

Следующее объединяет ответ @Mariusz Pawelski и аналогичный ответ на связанный вопрос, позволяющий удобно спрятать сообщение.

Using git stash store with a message

  1. Используйте git stash create для создания фиксации тайника, а затем сохраните его в тайнике с помощью git stash store. Это не изменит никаких файлов в вашем рабочем дереве. И вы можете добавить полезное сообщение, чтобы позже снова найти нужную версию.

    git stash store -m "saving intermediate results: my note 1" $(git stash create)
    
  2. Когда вы решите, что хотите выбросить текущую работу и восстановить ранее сохраненное состояние, сначала сделайте еще git stash. Это отбрасывает любые незафиксированные изменения, скрывая их в тайнике, так что не будет конфликтов слияния, когда вы примените состояние anoter stashed на следующих шагах.

    git stash
    
  3. Теперь посмотрите, что вы сохранили в своем тайнике ref:

    $ git stash list
    
    stash@{0}: saving intermediate results: my note 1
    stash@{1}: saving intermediate results: my note 2
    
  4. И, наконец, чтобы восстановить сохраненное предыдущее рабочее состояние, отбросив текущее состояние рабочего дерева, вы должны сделать это, используя номер индекса изнутри stash@{…} в выходных данных выше, чтобы идентифицировать фиксацию тайника для восстановления:

    git stash apply 1
    
  5. Если вы обнаружите, что хотите вернуть свою выброшенную работу: она также сохраняется в тайнике и может быть восстановлена ​​с помощью той же техники, что и выше.

Wrapping as a Git alias

Команду, использованную на шаге 1 выше, можно использовать с псевдонимом git, чтобы сделать ее более удобной. Чтобы создать псевдоним (обратите внимание на последний # метод):

git config --global alias.stash-copy '!git stash store -m "saving intermediate results: $1" $(git stash create) #'

Отныне вы можете использовать его так:

git stash-copy "my note"
person tanius    schedule 13.06.2020