Як git reset - має підкаталог?


197

UPDATE² : З Git 2.23 (серпень 2019 року) є нова команда, git restoreяка робить це, дивіться прийняту відповідь .

ОНОВЛЕННЯ : Це буде працювати більш інтуїтивно, як у Git 1.8.3, дивіться мою власну відповідь .

Уявіть наступний випадок використання: Я хочу позбутися всіх змін у певному підкаталозі свого робочого дерева Git, залишивши всі інші підкаталоги недоторканими.

Яка правильна команда 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
що з a git stash && git stash drop?
CharlesB

1
про що git checkout -- /path/to/subdir/?
iberbeu

3
@CharlesB: git stashне приймає аргумент шляху ...
krlmlr

@iberbeu: Ні. Також додаватимуться файли, виключені з розрідженої каси.
krlmlr

1
@CharlesBailey: Тоді чому є перемикач з надписом "Шукаю відповідь на основі достовірних та / або офіційних джерел". у діалозі щедрості? Я цього не вводив! Спробуйте також googling "git reset subdirectory" (без лапок) і подивіться, що знаходиться на перших 3 позиціях. Напевно, повідомлення до списку розсилки kernel.org буде складніше знайти. - Також мені ще не зрозуміло, чи така поведінка - помилка чи особливість.
krlmlr

Відповіді:


164

З Git 2.23 (серпень 2019 року) у вас є нова командаgit restore

git restore --source=HEAD --staged --worktree -- aDirectory
# or, shorter
git restore -s@ -SW  -- aDirectory

Це замінило б і індекс, і робоче дерево HEADзмістом, як reset --hardби, але для певного шляху.


Оригінальна відповідь (2013)

Примітка (як зауважив по Dan Fabulich ) , що:

  • 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? ".


1
Поясніть будь ласка. Після цього git checkout HEAD -- .файли, вилучені з розрідженої каси, знову з’являються. Що git update-index --skip-worktreeробити?
krlmlr

@krlmlr пропуском worktree або припустимо, незмінними є два способи намагаються зробити запис в індекс «невидимий» для мерзотника: fallengamer.livejournal.com/93321.html , stackoverflow.com/q/13630849/6309 і StackOverflow. com / a / 6139470/6309
VonC

@krlmlr ці посилання - це лише покажчики, на які ви можете перевірити, чи все ще відновить ці записи, коли вони будуть позначені як "пропущено робоче дерево".
VonC

Вибачте, але це занадто складне завдання для вас. Я хочу інклюзивного скидання, а не ексклюзивного. Чи справді немає гарного способу зробити це в Git?
krlmlr

@krlmlr ні: найкраще зробити git checkout HEAD -- <path>, а потім видалити каталоги, які були відновлені (але вони все ще оголошені в розрідженій касі).
VonC

126

За словами розробника Git Duy Nguyen, який люб'язно реалізував функцію та комутатор сумісності , наступні роботи, як очікується, станом на Git 1.8.3 :

git checkout -- a

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

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

5
Дякуємо за ваші зусилля, які ви працювали з командою розвитку Git, в результаті чого Git змінився.
Дан Крус

13
І зауважте, що "а" в цьому випадку означає каталог, який ви хочете відновити, тому якщо ви знаходитесь в каталозі, який ви хочете відновити, команда повинна бути git checkout -- .там, де .означає поточний каталог.
TheWestIsThe ...

5
Один коментар з мого боку полягає в тому, що спочатку потрібно видалити папку git reset -- a(де a - каталог, який ви хочете скинути)
Боян

Чи не правда це також, якщо ви хочете на git reset --hardвсю репо?
krlmlr

І якщо ви до цього каталогу додали якісь нові файли, зробіть це rm -rf aраніше.
Tobias Feil

30

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

git checkout -- a

до

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

Починаючи з версії 1.7.0, Git вшановує прапор, який пропускає .ls-files

Запуск тестового сценарію (з незначними налаштуваннями, що змінюються 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в першу чергу поважати біт пропуску?
krlmlr

Швидкий огляд checkout.cта tree.cне виявляє, що використовується прапор пропускного робочого дерева.
Дан Крус

Це досить просто, щоб бути корисним на практиці, навіть якщо мені доведеться встановити псевдонім bash для цієї команди. Duy Nguyen відповів на моє повідомлення до списку розсилки Git, давайте подивимось, чи скоро з’явиться більш зручна альтернатива.
krlmlr

18

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

Тому замість цього я почав використовувати таку команду:

git diff --cached commit -- subdir | git apply -R --index

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

Оскільки ця команда досить довга, і я планую її часто використовувати, я створив псевдонім для неї, який я назвав 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

Я вчора побачив ваш коментар і сьогодні його експериментував. Ваш псевдонім корисний. +1
VonC

Як цей варіант порівнюється з git checkout --overlay HEAD -- <path>командою, яку @VonC згадує у своїй відповіді?
Ехтеш Чудхурі

1
@EhteshChoudhury Зауважте, що git checkout --overlay HEAD -- <path>ще не випущено (Git 2.22 вийде у другому кварталі 2019 року)
VonC

5

Я збираюся запропонувати жахливий варіант тут, так як я поняття не маю , як зробити що - небудь з мерзотником , за винятком , add commitі push, ось, як я «повернувся» підкаталог:

Я запустив нове сховище на своєму локальному ПК, повернув це все до зобов'язання, з якого я хотів скопіювати код, а потім скопіював ці файли у свою робочу директорію та add commit pushін. Voila. Не ненавиджу гравця, ненавиджу пана Торвальда за розумніший за всіх нас.


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

Я вважаю, що це був цільовий результат (b залишається модифікованим, а / * файли повертаються, a / c не відтворюється).

Цей підхід має додаткову перевагу - бути дуже гнучким; ви можете отримати так само дрібнозернисту, як хочете додати певні файли, але не інші, в каталог.


Це приємно, але я мав би git addвсе, крім так a, правда? На практиці звучить важко.
krlmlr

1
@krlmlr Не дуже. git add .Потім ви можете git reset aдодати все, крім a.
Джонатан Врен

1
@krlmlr Також варто зазначити, що git addвидалені файли не додаються. Отже, якщо ви відновляєте лише видалені файли, git add .додасте всі змінені файли, але не видалені.
Джонатан Врен

3

Якщо розмір підкаталогу не особливо великий, і ви хочете триматися подалі від CLI, ось швидке рішення для ручного скидання підкаталогу:

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

Ура. Ви просто вручну скиньте підкаталог у своїй гілці функцій таким же, як у головній гілці !!


Я не бачив голосів за цю відповідь, але іноді це найпростіший гарантований шлях до успіху.
Сью Спенс

1

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

0

А як на рахунок

subdir=thesubdir
for fn in $(find $subdir); do
  git ls-files --error-unmatch $fn 2>/dev/null >/dev/null;
  if [ "$?" = "1" ]; then
    continue;
  fi
  echo "Restoring $fn";
  git show HEAD:$fn > $fn;
done 
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.