Как сбросить git --жесткий подкаталог?


обновление: это будет работать более интуитивно, как в Git 1.8.3, см. мой собственный ответ.

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

  • Я могу сделать git checkout ., но git checkout . добавляет каталоги, исключенные из sparse checkout

  • здесь git reset --hard, но это не позвольте мне сделать это для подкаталога:

    > git reset --hard .
    fatal: Cannot do hard reset with paths.
    

    опять же: почему git не может выполнять жесткие/мягкие сбросы по пути?

  • Я могу исправить текущее состояние с помощью git diff subdir | patch -p1 -R, но это довольно странный способ делать это.

какова правильная команда Git для этой операции?

скрипт, приведенный ниже, иллюстрирует эту проблему. Вставьте соответствующую команду под How to make files комментарий -- текущая команда восстановит файл a/c/ac, который должен быть исключен неполное извлечение. Обратите внимание, что я не хотите явно восстановить a/a и a/b, Я только "знаю" a и хотите восстановить все ниже. EDIT: и я тоже не "знаю"b, или какие другие каталоги находятся на том же уровне, что и a.

#!/bin/sh

rm -rf repo; git init repo; cd repo
for f in a b; do
  for g in a b c; do
    mkdir -p $f/$g
    touch $f/$g/$f$g
    git add $f/$g
    git commit -m "added $f/$g"
  done
done
git config core.sparsecheckout true
echo a/a > .git/info/sparse-checkout
echo a/b >> .git/info/sparse-checkout
echo b/a >> .git/info/sparse-checkout
git read-tree -m -u HEAD
echo "After read-tree:"
find * -type f

rm a/a/aa
rm a/b/ab
echo >> b/a/ba
echo "After modifying:"
find * -type f
git status

# How to make files a/* reappear without changing b and without recreating a/c?
git checkout -- a

echo "After checkout:"
git status
find * -type f
8 129

8 ответов:

Примечание (как прокомментировал by Dan Fabulich), что:

  • git checkout -- <path> не выполняет жесткий сброс: он заменяет содержимое рабочего дерева на поэтапное содержимое.
  • git checkout HEAD -- <path> выполняет жесткий сброс для пути, заменяя как индекс, так и рабочее дерево версией из HEAD совершал.

как ответил by Ajedi32, обе формы оформления заказа не удалять файлы которые были удалены в целевой редакции.
Если у вас есть дополнительные файлы в рабочем дереве, которые не существуют в голове, a git checkout HEAD -- <path> не будет удалять их.

но эта проверка может уважать a git update-index --skip-worktree (для тех каталогов, которые вы хотите игнорировать), как указано в "почему исключенные файлы продолжают появляться в моем git sparse checkout?".

По словам разработчика Git Duy Nguyen, который любезно реализовал функция и переключатель совместимости, следующие работы, как ожидалось начиная с Git 1.8.3:

git checkout -- a

(где a это каталог, который вы хотите жестко сбросить). К исходному поведению можно получить доступ через

git checkout --ignore-skip-worktree-bits -- a

попробуйте изменить

git checkout -- a

до

git checkout -- `git ls-files -m -- a`

начиная с версии 1.7.0, в Git ls-filesчествует флаг skip-worktree.

запуск тестового сценария (с некоторыми незначительными изменениями настроекgit commit... к git commit -q и git status до git status --short) выходы:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/c/ac
a/b/ab
b/a/ba

запуск тестового сценария с предлагаемым checkout изменить выходы:

Initialized empty Git repository in /home/user/repo/.git/
After read-tree:
a/a/aa
a/b/ab
b/a/ba
After modifying:
b/a/ba
 D a/a/aa
 D a/b/ab
 M b/a/ba
After checkout:
 M b/a/ba
a/a/aa
a/b/ab
b/a/ba

в случае просто отбрасывания изменений,git checkout -- path/ или git checkout HEAD -- path/ команды, предложенные другими ответами, отлично работают. Однако, когда вы хотите сбросить каталог в ревизию, отличную от HEAD, это решение имеет значительную проблему: оно не удаляет файлы, которые были удалены в целевой ревизии.

поэтому вместо этого я начал использовать следующую команду:

git diff--cachedcommit-- subdir|git apply-R --index

это работает, находя разницу между целевой фиксацией и индексом, а затем применяя эту разницу в обратном направлении к рабочему каталогу и индексу. В принципе, это означает, что содержимое индекса соответствует содержимому указанной вами ревизии. Дело в том, что git diff принимает аргумент path позволяет ограничить этот эффект определенным файлом или каталогом.

так как это команда довольно длинная, и я планирую использовать ее часто, я установил для нее псевдоним, который я назвал reset-checkout:

git config --global alias.reset-checkout '!f() { git diff --cached "$@" | git apply -R --index; }; f'

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

git reset-checkout 451a9a4 -- path/to/directory

или так:

git reset-checkout 451a9a4

сброс обычно меняет все, но вы можете использовать git stash, чтобы забрать то, что вы хотите сохранить. Как вы упомянули, stash не принимает путь напрямую, но он все еще может быть использован для сохранения определенного пути с --keep-index флаг. В вашем примере вы бы спрятали каталог b, а затем сбросили все остальное.

# How to make files a/* reappear without changing b and without recreating a/c?
git add b               #add the directory you want to keep
git stash --keep-index  #stash anything that isn't added
git reset               #unstage the b directory
git stash drop          #clean up the stash (optional)

это приведет вас к точке, где последняя часть вашего скрипта будет выводить это:

After checkout:
# On branch master
# Changes not staged for commit:
#
#   modified:   b/a/ba
#
no changes added to commit (use "git add" and/or "git commit -a")
a/a/aa
a/b/ab
b/a/ba

Я считаю, что это была цель результат (b остается измененным, a/* файлы возвращаются, a/c не воссоздается).

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

Если размер подкаталога не особенно огромен, и вы хотите держаться подальше от CLI, вот быстрое решение вручную сброс подкаталог:

  1. переключиться на ветку master и скопировать в поддиректорию, чтобы быть сброшены.
  2. теперь вернитесь к своей ветке функций и замените подкаталог на копию, которую вы только что создали на шаге 1.
  3. сохранить изменения.

Ура. Вы просто вручную сбросите подкаталог в вашей ветке функций должен быть таким же, как и в главной ветке !!

Ajedi32 ' s ответ это то, что я искал, но для некоторых коммитов я столкнулся с этой ошибкой:

error: cannot apply binary patch to 'path/to/directory' without full index line

может быть потому, что некоторые файлы каталога являются двоичными файлами. Добавление опции' --binary ' в команду git diff исправлено:

git diff --binary --cached commit -- path/to/directory | git apply -R --index

Я собираюсь предложить ужасный вариант здесь, так как я понятия не имею, как делать что-либо с git, кроме addcommit и push, вот как я "вернул" подкаталог:

Я запустил новый репозиторий на своем локальном ПК, вернул все это к фиксации, из которой я хотел скопировать код, а затем скопировал эти файлы в свой рабочий каталог,addcommitpush и вуаля. Не ненавидьте игрока, ненавидьте Мистера Торвальдса за то, что он умнее всех нас.