Копить только поэтапные изменения в git - возможно ли?

368

Есть ли способ, которым я могу спрятать только мои поэтапные изменения? Сценарий, с которым у меня возникают проблемы, заключается в том, что я работал над несколькими ошибками в данный момент времени и у меня есть несколько неустановленных изменений. Я хотел бы иметь возможность размещать эти файлы по отдельности, создавать свои файлы .patch и прятать их до тех пор, пока код не будет утвержден. Таким образом, когда он будет одобрен, я могу спрятать всю свою (текущую) сессию, вытащить эту ошибку и нажать код.

Я поступаю об этом неправильно? Я неправильно понимаю, как git может работать другими способами, чтобы упростить мой процесс?

MrDuk
источник
Да, вы, вероятно, делаете что-то не так, чтобы попасть в эту ситуацию. Еще полезный вопрос. Вы действительно должны спрятать или ветвиться, прежде чем приступать к следующему исправлению. Тангенциальный ответ stackoverflow.com/a/50692885 , вероятно, является лучшим способом справиться с этим в git. Игра с тайником часто делает странные вещи в моей рабочей зоне, если я вытащил коммиты из верхнего потока.
Самуэль Ослунд

Ответы:

472

Да, это возможно с DOUBLE STASH

  1. Подготовьте все свои файлы, которые вам нужно спрятать.
  2. Беги git stash --keep-index. Эта команда создаст тайник со ВСЕМИ вашими изменениями ( поэтапно и не поэтапно ), но оставит поэтапные изменения в вашем рабочем каталоге (все еще в состоянии стадии).
  3. Запустить git stash push -m "good stash"
  4. Теперь у вас "good stash"есть ТОЛЬКО постановочные файлы .

Теперь , если вам нужно unstaged файлы до тайника, просто нанесите первый тайник ( тот , созданный с--keep-index ) , и теперь вы можете удалить файлы , которые вы спрятали в "good stash".

наслаждаться

Бартломей Семанчик
источник
Он хранит изменения в подмодулях, даже если они не организованы. Есть ли способ обойти это?
rluks
1
это каким-то образом оставило все новые файлы (даже подготовленные) вне.
Ауримас
8
@ Aurimas, чтобы хранить новые файлы, вы должны использовать -uпереключатель.
Gyromite
2
когда вы повторно примените первый тайник и вернете все изменения, в то время как вас может заинтересовать только git stash apply --indexопция использования изменений unstages . Это попытается сохранить ваше состояние un (staged). Теперь легче удалить нежелательные изменения из рабочего дерева.
Отомо
Хотя мне не нужно было делать именно то, что сказал этот ответ, знание о флаге --keep-index было очень полезно.
Аарон Краусс
129

С последним git вы можете использовать --patchопцию

git stash push --patch  

git stash save --patch   # for older git versions

И git попросит вас добавить каждое изменение в ваши файлы или нет в stash.
Вы просто отвечаете yилиn

UPD
Псевдоним для DOUBLE STASH :

git config --global alias.stash-staged '!bash -c "git stash --keep-index; git stash push -m "staged" --keep-index; git stash pop stash@{1}"'

Теперь вы можете поставить свои файлы и запустить git stash-staged.
В результате ваши промежуточные файлы будут сохранены в тайнике .

Если вы не хотите хранить промежуточные файлы и хотите переместить их в тайник. Затем вы можете добавить другой псевдоним и запустить git move-staged:

git config --global alias.move-staged '!bash -c "git stash-staged;git commit -m "temp"; git stash; git reset --hard HEAD^; git stash pop"'
Евгений Коньков
источник
17
Технически не отвечает на вопрос - но действительно хорошая техника, которая достигает выборочной тайны.
alexreardon
6
Согласитесь, это нормально, но идея здесь с вопросом в том, что я УЖЕ сделал всю эту работу по постановке изменений, с которыми я хочу что-то сделать (якобы изначально для фиксации, но теперь хочу спрятать), а не просто делать все это снова.
Стивен Лу
4
не работает для вновь созданных файлов (работает только для измененных файлов)
Дерек Лянг
@DerekLiang: вновь созданные файлы вообще не отслеживаются. Вы, вероятно, должны проверить -u|--include-untrackedопциюgit-stash
Евгений Коньков
2
Из документации : « save : эта опция устарела в пользу git stash push . Она отличается от 'stash push' тем, что не может принимать pathspecs, и любые неопциональные аргументы формируют сообщение."
Боржовский
53

Я сделал скрипт, который прячет только то, что в данный момент ставится, и оставляет все остальное. Это замечательно, когда я начинаю вносить слишком много несвязанных изменений. Просто создайте то, что не связано с желаемым коммитом, и просто спрячьте это.

(Спасибо Bartłomiej за отправную точку)

#!/bin/bash

#Stash everything temporarily.  Keep staged files, discard everything else after stashing.
git stash --keep-index

#Stash everything that remains (only the staged files should remain)  This is the stash we want to keep, so give it a name.
git stash save "$1"

#Apply the original stash to get us back to where we started.
git stash apply stash@{1}

#Create a temporary patch to reverse the originally staged changes and apply it
git stash show -p | git apply -R

#Delete the temporary stash
git stash drop stash@{1}
Джо
источник
7
Я бы добавил, что вы можете превратить скрипт в команду git, следуя thediscoblog.com/blog/2014/03/29/custom-git-commands-in-3-steps
Петр Бела
3
Это замечательно! Я настроил его, чтобы запросить у пользователя описание тайника,
brookinc
1
Это не работает вообще с git 2.23.0.
thnee
Спасибо, я проголосовал и превратил его в псевдоним здесь: stackoverflow.com/a/60875067/430128 .
Раман
36

TL; DR Просто добавьте -- $(git diff --staged --name-only)для вашего <pathspec>параметра git

Вот простой однострочник:

git stash -- $(git diff --staged --name-only)

И добавить сообщение просто:

git stash push -m "My work in progress" -- $(git diff --staged --name-only)

Протестировано на v2.17.1 и v2.21.0.windows.1

Ограничения:

  • Помните, что это может спрятать все, если у вас нет подготовленных файлов.
  • Кроме того, если у вас есть файл, который является только частично подготовленным (то есть, только некоторые измененные строки, размещены, в то время как некоторые другие измененные строки нет), тогда весь файл будет сохранен (включая неформатированные строки).
Сомо С.
источник
6
Я думаю, что это лучший вариант в описанной ситуации: легко понять и не использовать черную магию!
Луис
1
Это довольно аккуратно. Я закончил тем, что создал псевдоним из этого!
Kalpesh Panchal
держать их голоса идут
S.
@KalpeshPanchal вы можете поделиться своим псевдонимом? Я не уверен, как избежать этого, так что это неправильно интерпретирует.
Игорь Надж
2
@IgorNadj Конечно! Вот оно: github.com/panchalkalpesh/git-aliases/commit/…
Kalpesh Panchal
15

Чтобы сделать то же самое ...

  1. Поместите только те файлы, над которыми вы хотите работать.
  2. git commit -m 'temp'
  3. git add .
  4. git stash
  5. git reset HEAD~1

Boom. Файлы, которые вы не хотите, спрятаны. Все файлы, которые вы хотите, готовы для вас.

Майкл
источник
3
Это легко лучший ответ и самый легкий для запоминания
Кевин
9

В этом сценарии я предпочитаю создавать новые ветки для каждой проблемы. Я использую префикс temp /, поэтому я знаю, что могу удалить эти ветви позже.

git checkout -b temp/bug1

Поместите файлы, которые исправляют ошибку 1, и зафиксируйте их.

git checkout -b temp/bug2

Затем вы можете выбрать коммиты из соответствующих веток по мере необходимости и отправить запрос на извлечение.

Shamps
источник
2
В то время как необычные звуки прятания приятно знать, на практике это похоже на подход, который я реже использую.
ryanjdillon
1
Используйте "git cherry-pick tmpCommit", чтобы получить временную фиксацию назад при слиянии или "git merge tmpCommit" + "git reset HEAD ^", чтобы получить изменения без фиксации.
Самуэль Ослунд
1
Как показывает этот ответ, иногда лучше спросить непосредственно о том, чего вы хотите достичь, а не о том, как этого добиться с помощью данной техники. Временные ветки и вишня полезны в сложных ситуациях.
Гуней Озсан
если вы разместили файл частично, вам нужно будет сохранить свои изменения, прежде чем вернуться к исходной ветке и вставить их снова
jan-glx
6

Почему бы вам не зафиксировать изменение для определенной ошибки и не создать патч из этого коммита и его предшественника?

# hackhackhack, fix two unrelated bugs
git add -p                   # add hunks of first bug
git commit -m 'fix bug #123' # create commit #1
git add -p                   # add hunks of second bug
git commit -m 'fix bug #321' # create commit #2

Затем, чтобы создать соответствующие патчи, используйте git format-patch:

git format-patch HEAD^^

Это создаст два файла: 0001-fix-bug-123.patchи0002-fix-bug-321.patch

Или вы можете создавать отдельные ветки для каждой ошибки, так что вы можете объединять или перебазировать исправления ошибок по отдельности или даже удалять их, если они не работают

knittl
источник
2

git stash --keep-index хорошее решение ... за исключением того, что оно не работало правильно на удаленных путях, которые были исправлены в Git 2.23 (Q3 2019)

См. Коммит b932f6a (16 июля 2019 г.) Томаса Гуммерера ( tgummerer) .
(Слиты Junio C Hamano - gitster- в фиксации f8aee85 , 25 июл 2019)

stash: исправлена ​​обработка удаленных файлов с помощью --keep-index

git stash push --keep-index Предполагается сохранить все изменения, которые были добавлены в индекс, как в индексе, так и на диске.

В настоящее время это не работает правильно, когда файл удаляется из индекса.
Вместо того, чтобы сохранять его на диске, ** - keep-index в настоящее время восстанавливает файл. **

Исправьте это поведение, используя ' git checkout' в режиме без наложения, который может точно восстановить индекс и рабочее дерево.
Это также упрощает код.

Обратите внимание, что это будет перезаписывать неотслеживаемые файлы, если неотслеживаемый файл имеет то же имя, что и файл, который был удален в индексе.

VonC
источник
2

Запоминание только индекса (поэтапные изменения) в Git сложнее, чем должно быть. Я нашел ответ @ Джо, чтобы он работал хорошо, и превратил небольшой вариант в этот псевдоним:

stash-index = "!f() { \
  git stash push --quiet --keep-index -m \"temp for stash-index\" && \
  git stash push \"$@\" && \
  git stash pop --quiet stash@{1} && \
  git stash show -p | git apply -R; }; f"

Он помещает как поэтапные, так и не поэтапные изменения во временный тайник, оставляя поэтапные изменения в покое. Затем он помещает поэтапные изменения в тайник, который мы хотим сохранить. Аргументы, переданные псевдониму, такие как --message "whatever"будут добавлены в эту команду stash. Наконец, он извлекает временный тайник для восстановления исходного состояния и удаления временного тайника, а затем, наконец, «удаляет» сохраненные изменения из рабочего каталога через приложение обратного патча.

Для противоположной проблемы сохранения только неустановленных изменений (псевдоним stash-working) см. Этот ответ .

Раман
источник
1

Обязательно ли нужно работать над несколькими ошибками одновременно? И под «сразу» я подразумеваю «иметь файлы, отредактированные для нескольких ошибок одновременно». Потому что, если вам это абсолютно не нужно, я буду работать только с одной ошибкой за раз в вашей среде. Таким образом, вы можете использовать локальные ветки и ребаз, что, на мой взгляд, гораздо проще, чем управлять сложным хранилищем / сценой.

Допустим, мастер находится на коммите B. Теперь поработайте над ошибкой # 1.

git checkout -b bug1

Теперь вы на ветке bug1. Внесите некоторые изменения, зафиксируйте, дождитесь проверки кода. Это локально, так что вы ни на кого не влияете, и это должно быть достаточно просто сделать патч из git diffs.

A-B < master
   \
    C < bug1

Теперь вы работаете над bug2. Вернуться назад к мастеру с git checkout master. Создать новую ветку git checkout -b bug2. Внесите изменения, зафиксируйте, дождитесь проверки кода.

    D < bug2
   /
A-B < master
   \
    C < bug1

Давайте представим, что кто-то еще отправляет E & F на master, пока вы ожидаете проверки.

    D < bug2
   /
A-B-E-F < master
   \
    C < bug1

Когда ваш код был утвержден, вы можете повторно включить его в мастер, выполнив следующие шаги:

git checkout bug1
git rebase master
git checkout master
git merge bug1

Это приведет к следующему:

    D < bug2
   /
A-B-E-F-C' < master, bug1

Затем вы можете нажать, удалить свою локальную ветку bug1, и все готово. Одна ошибка за раз в вашей рабочей области, но с использованием локальных веток ваш репозиторий может обрабатывать несколько ошибок. И это позволяет избежать сложного сценического танца.

Ответ на вопрос ctote в комментариях:

Ну, вы можете вернуться к копированию для каждой ошибки и работать только с одной ошибкой за раз. По крайней мере, это спасает вас от постановки вопроса. Но попробовав это, я лично нахожу это неприятным. Тайники немного запутаны в графике git log. И что более важно, если вы что-то напортачили, вы не сможете вернуться. Если у вас грязный рабочий каталог и вы открываете тайник, вы не можете «отменить» этот треск. Гораздо сложнее испортить уже существующие коммиты.

Так git rebase -i.

Когда вы перемещаете одну ветку на другую, вы можете сделать это в интерактивном режиме (флаг -i). Когда вы делаете это, у вас есть возможность выбрать, что вы хотите делать с каждым коммитом. Pro Git - это потрясающая книга, которая также онлайн в формате HTML, и имеет хороший раздел о перебазировании и сжатии:

http://git-scm.com/book/ch6-4.html

Я украду их пример дословно для удобства. Представьте, что у вас есть следующая история коммитов, и вы хотите перебазировать и раздавить bug1 на master:

    F < bug2
   /
A-B-G-H < master
   \
    C-D-E < bug1

Вот что вы увидите при вводе git rebase -i master bug1

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

Чтобы раздавить все коммиты ветви до одного коммита, оставьте первый коммит как «pick» и замените все последующие записи «pick» на «squash» или просто «s». Вы также получите возможность изменить сообщение коммита.

pick f7f3f6d changed my name a bit
s 310154e updated README formatting and added blame
s a5f4a0d added cat-file
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit

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

Майк Монкевич
источник
1
Спасибо за подробный пост! Это наверняка решает многие мои проблемы - единственная проблема, которую я вижу, - наша текущая команда попросила, чтобы все поставки были сохранены в одном коммите. :(
MrDuk
1
Если им не нужна или не нужна ваша рабочая история в производственном репозитории, это нормально: сделайте свой мастер отслеживания без истории, применяя различия, а не объединяя ветви. Вы можете решить, как сохранить ветвь украшенного мастера, которая имеет реальную историю слияния, и выполнять свою реальную работу из этого, таким образом будет легко автоматизировать создание правильных различий.
13
2
Обратите внимание, что git checkout master; git checkout -b bug2можно сократить до git checkout -b bug2 master. То же относится и к тому git checkout bug1; git rebase master; git checkout master; git merge bug1, что идентично git rebase master bug1; git push . bug1:master(предоставлено, pushуловка не очевидна)
knittl
1
Я дал пошаговое руководство для хранения выше в основном ответе, чтобы я мог использовать необычное форматирование
Майк Монкевич
6
Я проголосовал, потому что это не отвечает на первоначальный вопрос. Я в какой-то ветке работаю над чем-то, и я только что внес изменение, которое, по моему мнению, должно быть привязано к ветке интеграции отдельно. Все, что я хочу сделать, - это этап, на котором нужно изменить и спрятать его, чтобы я мог переключиться на другую ветвь и зафиксировать ее отдельно вместо текущей ветки «в процессе». (Предупреждение, мерзавец впереди.) Абсурдно, что это так трудно сделать; Я должен представить, что это обычное явление. (Работая в одной ветке и обнаруживая быстрое изменение, которое нужно сделать, и забывая сначала переключиться.)
jpmc26
0

Из ваших комментариев к ответу Майка Монкевича я предлагаю использовать более простую модель: используйте обычные ветки разработки, но используйте опцию сквоша слияния, чтобы получить один коммит в вашей основной ветке:

git checkout -b bug1    # create the development branch
* hack hack hack *      # do some work
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
* hack hack hack *
git commit
git checkout master     # go back to the master branch
git merge --squash bug1 # merge the work back
git commit              # commit the merge (don't forget
                        #    to change the default commit message)
git branch -D bug1      # remove the development branch

Преимущество этой процедуры в том, что вы можете использовать обычный рабочий процесс git.

Rudi
источник
Я не вижу, как этот ответ может помочь. Это не связано с оригинальным вопросом.
frapen
0

TL; DR ;git stash-staged

После создания псевдонима:

git config --global alias.stash-staged '!bash -c "git stash -- \$(git diff --staged --name-only)"'

Здесь git diffвозвращает список --stagedфайлов --name-only
и затем мы передаем этот список , как pathspecв git stashcommad.

От man git stash:

git stash [--] [<pathspec>...]

<pathspec>...
   The new stash entry records the modified states only for the files
   that match the pathspec. The index entries and working tree
   files are then rolled back to the state in HEAD only for these
   files, too, leaving files that do not match the pathspec intact.

Евгений Коньков
источник
-1

Чтобы удалить случайное изменение, особенно удаление нескольких файлов, сделайте следующее:

git add <stuff to keep> && git stash --keep-index && git stash drop

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

Протестировано в git версии 2.17.1

Wmax
источник
отрицательное голосование без комментариев не поможет ни мне, ни следующему читателю ... zaenks grumpy anon. Хотя я могу представить одну проблему с этим однострочным: нужно быть очень осторожным, чтобы не забыть добавить все необходимые изменения в индекс, иначе эти важные изменения также будут удалены. Но опять же, неосторожное использование любого инструмента Cli может быть очень опасным для драгоценного времени и работы в худшем случае.
wmax