2013-03-14 2 views
107

UPDATE: This will work more intuitively as of Git 1.8.3, see my own answer .Как сбросить git --hard в подкаталоге?

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

  • я могу сделать git checkout ., но git checkout . adds directories excluded by sparse checkout

  • Существует git reset --hard, но он не позволит мне сделать это для поддиректории:

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

    снова: Why git can't do hard/soft resets by path?

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

Что такое надлежащая команда 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 
+3

как насчет 'git stash && git stash drop'? – CharlesB

+0

как насчет 'git checkout -/path/to/subdir /'? – iberbeu

+2

@CharlesB: 'git stash' не принимает аргумент пути ... – krlmlr

ответ

68

По словам разработчика Git Duy Nguyen, любезно предоставленного the feature and a compatibility switch, следующие работы, как ожидалось as of Git 1.8.3:

git checkout -- a 

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

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

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

+11

Обратите внимание, что «a» в этом случае означает каталог, который вы хотите вернуть, поэтому, если вы находитесь в каталоге, который хотите вернуть, команда должна быть 'git checkout - .' где' .' означает текущий каталог , –

+1

Один комментарий с моей стороны заключается в том, что сначала необходимо отключить папку с помощью 'git reset - a' (где a - это каталог, который вы хотите сбросить) – Boyan

83

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

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

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

Но эта проверка может уважать git update-index --skip-worktree (для тех каталогов, которые вы хотите игнорировать), как указано в «Why do excluded files keep reappearing in my git sparse checkout?».

+0

Просьба уточнить. После 'git checkout HEAD - .' снова исчезают файлы, исключенные изредка. Что должен делать 'git update-index -skip-worktree'? – krlmlr

+0

@krlmlr skip-worktree or accept-unchanged - это два способа попытаться сделать запись в индексе «invisible» для git: http://fallengamer.livejournal.com/93321.html, http://stackoverflow.com/q/13630849/6309 и http://stackoverflow.com/a/6139470/6309 – VonC

+0

Как это помогает мне решить проблему? – krlmlr

4

Сброс обычно меняет все, но вы можете использовать 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 

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

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

+0

Это хорошо, но мне нужно было бы «git add» все, кроме 'a', правильно? Звучит сложно на практике. – krlmlr

+1

@krlmlr Не совсем. Вы можете «git add.», Затем «git reset a», чтобы добавить все, кроме 'a'. –

+1

@krlmlr Кроме того, стоит отметить, что 'git add' не добавляет удаленные файлы. Итак, если вы только восстанавливаете удаленные файлы, 'git add .' добавит все измененные файлы, но не удаленные. –

22

Try изменения

git checkout -- a 

в

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

Начиная с версии 1.7.0, Git и ls-fileshonors the skip-worktree flag.

Запуск тестового сценария (с некоторыми незначительными твиков изменения 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 
+0

Звучит неплохо. Но не следует ли «git checkout» уважать бит «skip-worktree» в первую очередь? – krlmlr

+0

Быстрый обзор ['checkout.c'] (https://github.com/git/git/blob/master/builtin/checkout.c) и [' tree.c'] (https: // github. com/git/git/blob/master/tree.c) не показывает, что используется флаг skip-worktree. –

+0

Это достаточно просто, чтобы быть полезным на практике, даже если мне нужно будет настроить псевдоним bash для этой команды. Duy Nguyen [ответил] (http://permalink.gmane.org/gmane.comp.version-control.git/218904) к моему сообщению в список рассылки Git, давайте посмотрим, появится ли более удобная для пользователя альтернатива скоро. – krlmlr

12

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

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

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

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

Так как эта команда довольно долго, и я планирую использовать его часто, я создал псевдоним для него, который я назвал 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 
+0

Я вчера увидел ваш комментарий и экспериментировал с ним сегодня. Ваш псевдоним полезен. +1 – VonC

1

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

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

Может быть потому, что некоторые файлы в каталоге бинарные файлы. Добавление «--binary» вариант мерзавец команды Различия зафиксировал его:

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

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

  1. Переключитесь на главную ветку и скопируйте подкаталог, подлежащий сбросу.
  2. Теперь вернитесь к ветви вашей функции и замените подкаталог копией, только что созданной на шаге 1.
  3. Зафиксируйте изменения.

Cheers. Вы просто вручную переустановите подкаталог в своей ветке свойств так же, как и в ветви мастера!

+0

Я не видел ни одного голоса за этот ответ, но иногда это самый простой гарантированный путь к успеху. –

Смежные вопросы