'git stash apply' в интерактивном режиме

У меня есть серия файлов в тайнике (stash{0}), и я хотел бы git apply только некоторые части / фрагменты этих файлов (обычно известные как интерактивный режим).

Является ли это возможным?

Я видел, что можно

git stash save -p 'Stash name'

но это кажется невозможным

git stash apply -p 'Stash name'

Вы знаете, как этого добиться?


person Kamafeather    schedule 28.01.2015    source источник
comment
Релевантно: stackoverflow.com/a/1105666/1959808   -  person Ioannis Filippidis    schedule 06.06.2021
comment
Релевантно: stackoverflow.com/a/59231358/1959808   -  person Ioannis Filippidis    schedule 06.06.2021


Ответы (4)


Является ли это возможным?

Да, это так!

git checkout -p stash@{0}

Где вы можете заменить 0 в stash@{0} индексом тайника, который хотите применить.

Используйте git stash list и git show -p stash@{n}, если не уверены, какой n тайник вы хотите применить.

Не забудьте git stash drop stash@{n}, когда знаете, что этот тайник вам больше не понадобится, поскольку git checkout, очевидно, не сбросит его за вас.

Почему это работает?

Ключ в том, чтобы понять, что тайники, по сути, являются ссылками в фиксирует точно так же, как теги и ветки.

В самом деле, они хранятся в .git/refs/stash, по одной строке на хеш тайника.

Предостережения

Как упоминалось в комментариях @mgadda, git checkout -p пытается применить всю разницу между фиксацией и текущей рабочей областью.

В случае git stash, если тайник, который вы пытаетесь применить, был создан для другого коммита, тогда git checkout -p stash@{n} попытается интерактивно применить все различия между фиксацией stash@{n} и фиксацией текущей рабочей области, включая все их родительские коммиты отличаются.

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

И наоборот, если вы пытаетесь применить тайник из будущего, то есть в ветку, которая представляет собой количество коммитов, сделанных до фиксации, на которой основан тайник, тогда git checkout -p stash@{n} попытается также применить все другие изменения, которые произошли между текущая фиксация и фиксация из будущего, помимо изменений из самого тайника.

(Если вам интересно, git checkout -p stash@{n} тайник из параллельной ветки попытается отменить все изменения между текущей фиксацией и исходной точкой ветвления и также применить все изменения между точкой ветвления и другой ветвью , кроме сдачи в заначку).

Обходные пути

Есть несколько обходных путей, ни один из них не идеален для каждой ситуации:

    1. Be really careful with the patches you accept when you do git checkout -p stash@{n}
    1. Do a git stash pop, then git stash again before doing git checkout -p .... But if you wanted to do a partial apply of your stash to avoid conflicts, this won't really help. In that case, see solution 4 below.
    1. If you have a graphical diff tool supported by git (like meld), you can use git difftool and "apply left" only the changes you're interested in:
    • git difftool -d stash@{n} для сравнения всего тайника и всех его файлов

    • git difftool stash@{n} -- path/to/file для сравнения одного файла

    1. (Based on @andrew's answer) On a detached head, go back to the "parent" commit of the stash you're interested in, apply the stash, re-stash interactively only the parts you're interested in, go back and reapply the smaller stash.

Шаг за шагом:

git checkout stash@{n}^  # notice the "^". 

# Now you're in a detached head in the parent commit of the stash.
# It can be applied cleanly:
git stash apply stash@{n}

# Now save only the diffs you're interested in:
git stash -p

# remove the rest of the old stash
git checkout -- .  # be careful or you could remove unrelated changes

# go back to the branch where you want to apply the smaller stash
git checkout <my previous branch>

# apply the smaller stash
git stash pop
person LeoRochael    schedule 28.04.2017
comment
Здесь есть предостережение, о котором следует упомянуть: поскольку тайники - это просто коммиты, это означает, что у них также есть родительские коммиты, которые не гарантируют, что они будут такими же родительскими коммитами, как тот, к которому вы хотите интерактивно применить изменения. Эмпирическое правило: если вы спрятали из какой-то другой фиксации, отличной от текущей извлеченной фиксации, этот метод не будет делать то, что вы ожидаете. Обходной путь: примените весь набор изменений из вашего тайника (с помощью git stash pop), затем снова сохраните (git stash). Теперь вы можете использовать git checkout -p по своему желанию. - person mgadda; 01.08.2017

Что я часто делаю (в git bash), так это

git stash show -p 'stash@{0}' >tmp.patch

Затем я редактирую файл и удаляю ненужные части. Наконец я говорю

<tmp.patch git apply

or

<tmp.patch patch -p1

Однако это не работает для двоичных файлов, но и для них не работает принятый ответ (с использованием checkout -p).

person user829755    schedule 18.12.2019
comment
Хорошая альтернатива, спасибо! - Я думаю, что в любом случае, как и в принятом ответе, перед созданием патча необходимо будет проверить родительский коммит, на который ссылается тайник; в противном случае он также будет включать множество изменений из промежуточных коммитов (между текущим проверенным коммитом и родительским коммитом тайника), как указано в комментарий mgadda. - person Kamafeather; 21.12.2019
comment
нет, содержимое tmp.patch не зависит от того, что вы проверили при его создании. Будет затронут только git apply, и он сообщит о конфликтах слияния, если затронутые строки кода тем временем изменились. - person user829755; 28.12.2019
comment
Извините, я пропустил Я редактирую файл и удаляю ненужные части. Это также, IMHO, раздражающая часть, поскольку редактирование вручную подвержено ошибкам. - person Kamafeather; 02.01.2020

Я не думаю, что есть способ вносить изменения по кускам (или даже по файлам). Вам нужно будет применить тайник, а затем спрятать ненужные изменения в интерактивном режиме (с помощью git stash save -p). Если вас беспокоят конфликты, вы можете сначала спрятать любые незафиксированные изменения, применить свой тайник, спрятать любые конфликтующие блоки, а затем применить другой тайник.

person Andrew    schedule 28.01.2015
comment
Да, в основном я спрашиваю, потому что хочу избежать конфликтов. Цель состоит в том, чтобы получить некоторые изменения (скажем) из branch_A и иметь возможность поместить их в branch_B, имея возможность напрямую избежать конфликтов, которые могут возникнуть в этих двух ветвях. Ваше решение работает, но это именно тот сложный способ, которого я хотел избежать ;-P - person Kamafeather; 28.01.2015
comment
Обратный подход, чтобы просто git stash save интерактивно фрагменты, которые я действительно хочу, а затем восстановить их в нужной ветке, вместо git stash apply интерактивно фрагментов, которые я действительно хочу, кажется лучшим подходом. . - person Kamafeather; 28.01.2015

Один из возможных способов - сбросить индекс, а затем использовать интерактивное добавление.

# 0. ensure there are no uncommitted changes
git status

# 1. apply a changeset as is
git stash apply stash@{n}
# ... fix or discard conflicts if any

# 2. reset the index 
git reset

# 3. interactively add the required chunks (except new files)
git add -p

# 4. stash all other changes
git stash save --keep-index "comment"
# 4. or just discards all other changes in the working tree
git checkout-index -f -a

# 5. commit
git commit -m "comment"

Другой способ - использовать интерактивный сброс вместо интерактивного добавления.

# 0. ensure the working tree does not have unstaged changes
git status

# 1. apply a changeset as is
git stash apply stash@{n}
# ... fix or discard conflicts if any

# 2. interactively exclude the unneeded chunks from the index 
git reset -p

# 3. stash all other changes
git stash save --keep-index "comment"
# 3. or just discards all other changes in the working tree
git checkout-index -f -a

# 4. commit
git commit -m "comment"
person ruvim    schedule 05.09.2018