Как 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