У вас есть несколько вариантов здесь. Некоторые методы лучше для некоторых целей, а другие методы лучше для других целей. Я собираюсь угадать , какой из них подходит вам, но это предположение . Я собираюсь показать вам некоторые варианты, но не все варианты.
Помните, что Git - это всего лишь коммитов , а не файлов. Коммиты удерживают файлы - каждый коммит имеет полный снимок каждого файла, который находится в этом коммите, но Git имеет значение о коммитах.
Каждый коммит имеет уникальный хэш-идентификатор, представляющий собой большую некрасивую строку букв и цифр. Этот идентификатор хеша означает , что commit. Ни один другой коммит не имеет такой же последовательности букв и цифр. Это то, что беспокоит Git - ветви имена , такие как master
или branch-A
, предназначены для вас , а не для Git. Git заботится о коммитах .
Однажды сделанный коммит не может быть изменен. Каждый заморожен на все времена. Но вы можете взять сделанный вами коммит или, конечно, еще кто-нибудь, и извлечь его в рабочую область, внести изменения в рабочую область и затем сказать Git сделать Новый коммит из рабочей области. На самом деле, это как вы используете Git.
В обычном процессе использования Git вы можете начать с:
git checkout master
Это говорит Git: Получите мне коммит, чей хеш-идентификатор хранится под моим именем master
. Помните, имя для вас; Git заботится о хеш-идентификаторе. Git берет все замороженные файлы из этого коммита, помещает их в рабочую область - ваше рабочее дерево - и теперь вы можете просматривать и использовать файлы, так как они больше не нужны заморожен в каком-то внутреннем Git-формате, который Git скрывает из виду.
Теперь вы выполняете некоторую работу и git add
некоторые файлы и так далее, и тогда вы обычно запускаете git commit
. Теперь пришло время наблюдать за другой особенностью каждого коммита.
Каждый коммит хранит необработанный хэш-идентификатор своего родительского коммита. Так что, если вы только что извлекли коммит, обозначенный master
, у этого коммита есть коммит parent , у которого есть другой родитель и т. Д. Если мы нарисуем эти коммиты, мы увидим что-то вроде этого:
... <-F <-G <-H <-- master
Извлеченный вами коммит имеет большой уродливый хэш-идентификатор H
, который хранится под вашим именем master
. Этот коммит - тот, который мы называем H
- запоминает хэш-идентификатор своего родительского коммита G
.
Вы только что внесли некоторые изменения и запустили git commit
. Git теперь упакует новый снимок из всех файлов и присвоит ему новый уникальный идентификатор хэша. Мы будем называть этот хэш-идентификатор I
для краткости. Новый коммит I
запомнит коммит H
в качестве родителя. Давайте нарисуем это:
... <-F <-G <-H <-- master
\
I
Теперь самое сложное! Хеш-идентификаторы слишком сложны для запоминания людьми, поэтому, чтобы выручить нас, Git будет хранить хеш-код I
под некоторым именем. имя , которое он будет использовать, - это то, которое мы дали git checkout
. Так что Git обновит наш master
, чтобы он указывал на новый коммит I
:
...--F--G--H
\
I <-- master
Это не то, что вы хотели, так что это не совсем то, что вы сделали. Сначала вы проверили master
. Тогда вы сказали Git: сделайте мне новое имя, branch-A
. Git сделал это, так что теперь у вас есть:
...--F--G--H <-- master, branch-A
Чтобы запомнить, какое из них вы дали git checkout
, Git прикрепляет специальное имя HEAD
(во всех таких заглавных буквах) к имени, которое вы использовали:
...--F--G--H <-- master, branch-A (HEAD)
Когда вы делаете новый коммит I
, Git знает, как обновить имя, к которому прикреплен HEAD
. Итак, теперь у вас есть:
...--F--G--H <-- master
\
I <-- branch-A (HEAD)
Теперь, я не знаю, как many фиксирует, что вы сделали в этой branch-A
ветви. Я нарисую три через мгновение. Но где-то по пути вы сказали Git: удалите некоторые файлы . Так что сделайте снимок I
, или, может быть, J
или K
, или, может быть, все три, полностью отсутствуют некоторые файлы:
...--F--G--H <-- master
\
I--J--K <-- branch-A (HEAD)
Вы caне изменять ни один из этих коммитов . Каким бы коммитам не хватало этих файлов, эти коммиты не имеют этих файлов навсегда .
Если вы не передали эти коммиты кому-либо еще, у вас есть отличный вариант здесь. Вы можете забыть все об этих коммитах . Вы можете сделать новые замены коммитов, которые являются новыми и улучшенными: они очень похожи на оригиналы, за исключением новых замен, вы не удаляете файлы.
Основным недостатком этой опции является необходимость замены каждого такого коммита. Тем не менее, есть простой способ сделать это. Другим основным недостатком этой опции является улов: , если вы не дали этих коммитов кому-либо еще . Помните, Git заботится о хэш-идентификаторах. Если вы передали эти коммиты кому-то другому, они имеют этих коммитов с этими хэш-идентификаторами в их хранилище.
Если у кого-то еще есть эти коммиты, это не совсем фатально для предложения здесь, это просто означает, что «кто-то еще» может также переключиться со старых коммитов на новые. Или вы можете полностью использовать другой вариант, который я не собираюсь здесь показывать.
В любом случае, давайте снова посмотрим на то, что у вас есть. Помни, я догадываюсь что у тебя есть. Вам придется взглянуть на свой - я предлагаю запустить git log --decorate --oneline --graph branch-A master
, чтобы сделать это - но вот мой рисунок снова:
...--F--G--H <-- master
\
I--J--K <-- branch-A (HEAD)
Допустим, вы удалили файлы в коммите J
или, возможно, I
. Теперь вы хотите их вернуть. В данный момент вы выполняете коммит K
, потому что вы все еще используете branch-A
, поэтому ваш HEAD
все еще привязан к branch-A
.
Сначала вы сделаете еще один новый коммит. Он получит новый хэш-идентификатор, который мы назовем L
(фактическим идентификатором, как обычно, будет какая-то большая уродливая строка букв и цифр). Чтобы сделать этот новый коммит, пока у вас есть K
, и все чисто, потому что все зафиксировано - так что git status
говорит: «на ветке-A, нечего коммитить» - вы изменяете свою рабочую область на put файлы обратно .
Помните также, что каждый коммит имеет полный снимок всех своих файлов. Таким образом, мы вернем файлы из коммита H
, который, в конце концов, остается точно таким же, как и всегда - все коммиты замораживаются во времени! Нам просто нужно посмотреть эти файлы.
Есть несколько способов сделать это. Один должен использовать git show
, как это:
git show master:path/to/file
Отображает содержимое path/to/file
как сохраненное в коммите H
- коммите, на который указывает ваше имя master
- на вашем экране. Конечно, вы не хотите видеть это, вы хотите сохранить его обратно в path/to/file
. Итак, вы можете сделать это:
git show master:path/to/file > path/to/file
git add path/to/file
Есть странная особенность git checkout
. У него есть режим, в котором он делает это для нас, без фактического переключения коммитов и / или ветвей. Таким образом, мы можем написать:
git checkout master -- path/to/file
Это эквивалентно git show
в том смысле, что создает или заменяет файл в нашей рабочей области, и на git add
в том смысле, что обновляет index , как и git add
.
(Мы не говорили об индексе. Индекс, который также называется промежуточной областью или иногда кеш , очень важен в Git. Но сейчас я у меня нет времени, чтобы вставить это в ответ.)
В любом случае, после того, как вы повторите это для всех удаленных файлов, которые вы не должны были удалять, вы можете запустить git commit
, чтобы сделать новый снимок:
...--F--G--H <-- master
\
I--J--K--L <-- branch-A (HEAD)
Commit L
возвращает файлы обратно.
Мы простосказал, что вы удалили их в J
.Вы должны будете убедиться, какой коммит действительно удалил их на этом этапе, потому что теперь мы собираемся использовать хитрый трюк.Мы ничего не можем изменить ни в одном из существующих коммитов - I
, J
, K
и L
все имеют эту форму навсегда, но мы можем извлекать коммиты, изменять рабочие областии сделать new commits.Так что теперь у нас есть Git extract commit I
, возможно, внесем некоторые изменения и сделаем коммит сноваЗатем у нас будет Git, работающий с J
, который мы облажали ранее, удаляя файлы.У нас будет Git , добавляющий изменения с K
, чтобы поместить файлы назад , на этом этапе, и затем мы сделаем так, чтобы Git сделал новый коммит.
Мы заставим Git сделать все это , начиная с коммита H
.Вот как должен выглядеть результат:
I'-J'-K' <-- HEAD
/
...--F--G--H <-- master
\
I--J--K--L <-- branch-A
I'
- это наша новая копия I
, возможно, с другой отметкой даты, если ничего больше.(Если нам действительно не нужно ничего менять, мы можем просто повторно использовать I
напрямую, и Git выяснит это для нас.) Тогда J'
- это наша копия J
, нос изменениями L
, чтобы положить файлы обратно, добавлено в .Тогда K'
является нашей копией J'
.K'
похоже на K
, но его родитель - J'
, и в нем также есть файлы, которые мы случайно удалили в J
.
Достигнув этой нирваны (или какой бы то ни было)
Чтобы достичь всего этого, мы запускаем git rebase
с опцией -i
, приказывая ему копировать коммиты из нашей текущей ветки (branch-A
), за исключением коммитов, которыетакже на master
, и поставить новые копии после окончания master
:
git rebase -i master
Git смотрит на наш график:
...--F--G--H <-- master
\
I--J--K--L <-- branch-A (HEAD)
и видит, что естьчетыре коммита, которые только на branch-A
здесь.(Коммиты F-G-H
включены branch-A
, но также включены master
.) Таким образом, Git теперь составит список работы:
pick <hash-of-I> <subject-line-from-I>
pick <hash-of-J> <subject-line-from-J>
pick <hash-of-K> <subject-line-from-K>
pick <hash-of-L> <subject-line-from-L>
Git откроет выбранный вами редактор- тот же, который вы выбрали для отправки сообщений о коммитах - для файла с этими инструкциями.Ваша задача состоит в том, чтобы изменить инструкции и выписать измененные инструкции и выйти из редактора.
Так как commit J
- это то, где мы облажались, удаляя файлы, что мы хотим сделать, эточтобы Git исправил J
на , добавив L
к нему.Таким образом, мы переупорядочиваем рабочий список, перемещая L
сразу после J
:
pick <hash-of-I> <subject-line-from-I>
pick <hash-of-J> <subject-line-from-J>
pick <hash-of-L> <subject-line-from-L>
pick <hash-of-K> <subject-line-from-K>
Затем мы заменяем слово pick
в строке о коммите L
на слово fixup
.(Вместо этого вы можете использовать здесь слово squash
, если хотите - это почти одно и то же, и если бы это было правильное руководство, мы бы сначала прошли здесь сквош, но в данном конкретном случае вы захотите fixup
).
Если ошибка была в I
, мы ставим fixup
, используя L
сразу после строки о выборе фиксации I
.Если ошибка была в L
, мы оставляем строки в их первоначальном порядке и просто меняем последний pick
на fixup
.Идея заключается в том, что коммит L
- тот, который возвращает файлы назад - это исправление для коммита, который сломал вещи путем удаления файлов впервое место.
В любом случае, мы теперь использовали наш редактор на этом листе инструкций.Мы закончили обновление инструкций, поэтому мы выписываем лист и покидаем редактор.Git now выполняет инструкции , давая нам:
I'-J'-K' <-- HEAD
/
...--F--G--H <-- master
\
I--J--K--L <-- branch-A
(имя HEAD
временно отсоединено от имени любой ветви, во время выбора и исправления.up.)
Если все пойдет хорошо, самый последний шаг этого процесса перебазирования произойдет сейчас.Git получает name branch-A
, что является способом, которым Git запоминает для нас идентификатор хеша коммитов, а не коммит L
.Он присоединяет имя ветви к последнему скопированному или исправленному файлу , который в данном случае является коммитом K'
.Затем он повторно присоединяет наш HEAD
.Так что теперь у нас есть это:
I'-J'-K' <-- branch-A (HEAD)
/
...--F--G--H <-- master
\
I--J--K--L [abandoned]
Мы ненужны оригинальные коммиты I-J-K-L
больше.Они были заменены новыми блестящими коммитами I'-J'-K'
. имя branch-A
помнит K'
для нас.Если мы запустим git log
, это будет выглядеть как , мы каким-то образом изменили плохие коммиты на хорошие.
Мы этого не сделали - оригинальные коммиты все еще есть, но мы -единственный человек, который знает, что branch-A
использовал для идентификации плохих коммитов, и у нас есть единственный репозиторий Git, в котором есть плохих коммитов.Мы никогда не отдаем их кому-либо еще, поскольку это касается остальной части вселенной, они даже не существуют.
Теперь у нас есть хорошие коммиты, и мы можем сделать наше слияниекак бы мы это делали, если бы мы никогда не ошиблись.