Как git reset - записать подкаталог?
ОБНОВЛЕНИЕ. Это будет работать более интуитивно с Git 1.8.3, см. мой собственный ответ.
Представьте себе следующий прецедент: я хочу избавиться от всех изменений в отдельном подкаталоге моего рабочего дерева Git, оставив все остальные подкаталоги неповрежденными.
Какова правильная команда Git для этой операции?
Ниже приведена script. Вставьте правильную команду под комментарием 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
Ответы
Ответ 1
По словам разработчика Git Дуи Нгуена (Duy Nguyen), который любезно реализовал функцию и переключатель совместимости, следующее работает, как и ожидалось, с Git 1.8.3:
git checkout -- a
(где a
- каталог, который вы хотите выполнить с полной перезагрузкой). К исходному поведению можно получить доступ через
git checkout --ignore-skip-worktree-bits -- a
Ответ 2
В Git 2.23 (август 2019 г.) у вас есть новая команда git restore
git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore [email protected] -SW -- aDirectory
Это заменило бы и индекс, и рабочее дерево содержимым HEAD
, как reset --hard
, но для определенного пути.
Оригинальный ответ (2013)
Обратите внимание (как прокомментировал Дан Фабулич), что:
git checkout -- <path>
не выполняет полный сброс: он заменяет содержимое рабочего дерева поэтапным содержимым.
git checkout HEAD -- <path>
выполняет полный сброс пути, заменяя индекс и рабочее дерево версией из коммита HEAD
.
Как ответил Ajedi32, обе формы проверки не удаляют файлы, которые были удалены в целевой ревизии.
Если в рабочем дереве есть дополнительные файлы, которых нет в HEAD, git checkout HEAD -- <path>
не удалит их.
Примечание. С помощью git checkout --overlay HEAD -- <path>
(Git 2.22, Q1 2019) файлы, которые появляются в индексе и рабочем дереве, но не в <tree-ish>
, удаляются, чтобы они точно соответствовали <tree-ish>
.
Но эта проверка может соответствовать git update-index --skip-worktree
(для тех каталогов, которые вы хотите игнорировать), как упоминалось в "Почему исключенные файлы продолжают появляться в моей git sparse checkout?".
Ответ 3
Попробуйте изменить
git checkout -- a
к
git checkout -- `git ls-files -m -- a`
Начиная с версии 1.7.0, Git ls-files
награды флаг skip-worktree.
Запуск теста script (с некоторыми незначительными изменениями, изменяющимися с 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
Запуск теста script с предлагаемыми выходами изменения 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
Ответ 4
В случае простого отказа от изменений команды git checkout -- path/
или git checkout HEAD -- path/
, предлагаемые другими ответами, отлично работают. Однако, если вы хотите reset создать каталог для другой версии, отличной от HEAD, это решение имеет значительную проблему: оно не удаляет файлы, которые были удалены в целевой версии.
Поэтому вместо этого я начал использовать следующую команду:
git diff
--cached
commit
--
subdir
|
git apply
-R --index
Это работает, обнаруживая разницу между целевым фиксацией и индексом, а затем применяя этот diff в обратном направлении к рабочему каталогу и индексу. В основном это означает, что он делает содержимое индекса совпадающим с содержимым указанной вами ревизии. Тот факт, что git diff
принимает аргумент path, позволяет ограничить этот эффект определенным файлом или каталогом.
Поскольку эта команда довольно длинная, и я планирую ее часто использовать, я установил для нее псевдоним, который я назвал reset-checkout
:
git config --global alias.reset-checkout '!f() { git diff --cached "[email protected]" | git apply -R --index; }; f'
Вы можете использовать его следующим образом:
git reset-checkout 451a9a4 -- path/to/directory
Или просто:
git reset-checkout 451a9a4
Ответ 5
A reset обычно меняет все, но вы можете использовать git stash
, чтобы выбрать то, что вы хотите сохранить. Как вы уже упоминали, stash
не принимает путь напрямую, но он все равно может использоваться для сохранения определенного пути с флагом --keep-index
. В вашем примере вы запустили бы каталог b, а затем reset все остальное.
# 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)
Это приведет вас к точке, где последняя часть вашего script будет выводить это:
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/c не воссоздается).
Этот подход имеет дополнительное преимущество - быть очень гибким; вы можете получить как можно более мелкие, как вы хотите добавить определенные файлы, но не другие, в каталог.
Ответ 6
Я собираюсь предложить ужасный вариант здесь, так как я понятия не имею, как сделать что-то с git, кроме add
commit
и push
, вот как я "перевернул" подкаталог:
Я запустил новый репозиторий на своем локальном компьютере, вернул все это к коммиту, из которого я хотел скопировать код, а затем скопировал эти файлы в мой рабочий каталог, add
commit
push
и вуаля. Не ненавидь игрока, ненавидь мистера Торвальдса за то, что он умнее всех нас.
Ответ 7
Если размер подкаталога не особо огромен, и вы хотите держаться подальше от CLI, здесь быстрое решение для вручную reset подкаталога:
- Переключитесь на главную ветвь и скопируйте подкаталог reset.
- Теперь вернитесь к ветки вашей функции и замените подкаталог копией, только что созданной на шаге 1.
- Зафиксируйте изменения.
Приветствия. Вы просто вручную reset подкаталог в ветки вашей функции должны быть такими же, как у ветки мастера!!
Ответ 8
Ajedi32 answer - это то, что я искал, но для некоторых коммитов я столкнулся с этой ошибкой
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