как отменить git add --all, за которым следует несколько коммитов

Я случайно использовал git add --all ранее, затем сделал несколько коммитов, и он пытается добавить несколько больших файлов, которые следует игнорировать.

Теперь при любой фиксации он показывает, что «это превышает ограничение размера файла GitHub в 100,00 МБ». Я попробовал git --reset, но он показывает, что ваша ветка опережает «origin/master» на 2 коммита. Как снова вернуть git в нормальное состояние? Большое спасибо.


person santoku    schedule 08.04.2017    source источник
comment
Таких вопросов масса. здесь и здесь например. Посмотрите, не является ли один из них тем, что вам нужно.   -  person jplindgren    schedule 08.04.2017
comment
Я пробовал git --reset, это не сработало. когда я добавляю новый файл, он по-прежнему пытается отправить все и показывает сообщения типа «при дельта-сжатии с использованием до 4 потоков». Сжатие объектов: 75% (45/60)   -  person santoku    schedule 08.04.2017
comment
Вы делаете коммит с флагом -a?   -  person thesecretmaster    schedule 08.04.2017
comment
@thesecretmaster Я сделал git add all в прошлом, что сейчас вызывает проблему. когда я git add/git commit сейчас, он постоянно пытается загрузить все, даже после того, как я запустил git --reset.   -  person santoku    schedule 08.04.2017
comment
Вы не просто сделали git add --all, вы также сделали git commit. Это имеет большое значение.   -  person torek    schedule 08.04.2017


Ответы (4)


TL;DR:

См. различные ответы на странице Как отменить последние фиксации в Git? Или перейдите к последнему разделу, «последний пример».

Объяснение

Ошибка в вашем вопросе заключается в том, что вы неправильно описали свою ситуацию. Вы не только добавили различные файлы, но и зафиксировали их. Это означает, что теперь они постоянно хранятся в вашем репозитории.

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

Сами коммиты на самом деле хранятся в виде хэш-идентификаторов, этих больших уродливых вещей, которые Git показывает вам (иногда сокращенно), таких как a9b307c и так далее. Эти хеш-идентификаторы являются «настоящими именами» каждого коммита. Они кажутся случайными, и людям практически невозможно их запомнить, поэтому мы делаем так, чтобы Git прикреплял имя, например master, к какому-то конкретному коммиту. Мы называем эту фиксацию концом ветки. Этот коммит содержит внутри себя хэш-идентификатор предыдущего кончика ветки.

Рассмотрим этот рисунок репозитория всего с тремя фиксациями, все на master. Я собираюсь использовать однобуквенные имена для каждого коммита вместо хэш-идентификаторов:

A <--B <--C   <--master

Имя master хранит идентификатор хэша badf00d или любой другой, который является фактическим идентификатором хэша коммита C. Мы говорим, что master указывает на C. Тем временем C хранит хэш-идентификатор для B; мы говорим, что C указывает на B. Коммит B сохраняет идентификатор для A, поэтому B указывает на A.

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

Чтобы добавить новый коммит, Git просто записывает коммит со всеми его файлами — каждый коммит хранит каждый файл, связанный с этим коммитом, — и заставляет его указывать на подсказку текущей ветки:

A <--B <--C <--D

Коммит D получает новый уникальный хэш, основанный на всех данных D (включая хэш-идентификатор C, ваше имя, адрес электронной почты и текущее время). Но — вот секрет имен веток в Git — теперь Git записывает хэш-идентификатор нового коммита в имя ветки, так что имя master теперь указывает на коммит D:

A <--B <--C <--D   <--master

Так растут ветки. Как только фиксация сделана, имя указывает на нее, а сама фиксация постоянно сохраняется в вашем репозитории.

Плохие новости

Звучит как плохие новости: вы зафиксировали эти файлы, так что теперь вы застряли с ними. И это плохие новости, но не фатально плохие.

Хорошие новости

Однако коммиты можно в конечном итоге забыть, если вы усердно работаете над этим. Более того, когда вы собираетесь перенести свои коммиты в чужой репозиторий, т. е. git push ваши новые коммиты, Git будет отправлять только те коммиты, которые доступны из ветки или ветвей1 ты толкаешь.

Следовательно, здесь вам нужно «забыть» некоторые из ваших коммитов. Для этого нужно указать Git повторно указать имя ветки. Например, предположим, что сама фиксация D является проблемой, и мы просто хотим от нее избавиться. Предположим, мы могли бы сказать Git: «Эй, сделай так, чтобы master снова указывало на C» — вот так:

A--B--C   <-- master
       \
        D

Коммит D все еще существует (он вроде постоянный!), но он больше не достижим по имени master, потому что Git начинает с хэш-идентификатора коммита, который master называет, и работает назад. Это означает, что Git не «видит» коммит D: его больше нет в ветке master.


1Вы можете отправить сразу несколько названий веток. На самом деле это было действие по умолчанию для git push, хотя оно оказалось подверженным ошибкам, поэтому теперь по умолчанию используется только отправка имени текущей ветки.

Иногда в Git важно тщательно различать имена, такие как master и develop, и то, что я называю "DAGlets": части графа коммитов, подобные тем, которые мы нарисовали выше. График коммитов представляет собой Dнаправленный Aциклический Gграф или DAG. И git fetch, и git push принимают названия ветвей или любое другое имя. Они вызывают другой Git на другом конце интернет-телефонного звонка и разговаривают с ним: они дают друг другу некоторые из этих имен, а затем превращают их в соответствующие хэш-идентификаторы. Затем они решают, какие коммиты (и другие объекты Git) отправлять или получать на основе хэш-идентификаторов и тех родительских ссылок, о которых я упоминал ранее. Поскольку хэш-идентификаторы основаны исключительно на содержимом каждого коммита, если ваш Git и их Git имеют один и тот же коммит, эти два коммита будут иметь одинаковый хэш-идентификатор.


Что нужно знать о git reset

Основной способ перемещения указателей ветвей в Git — это git reset. К сожалению, git reset — сложная команда.

У Git есть еще одна важная пара функций: пока вы делаете коммиты с помощью git commit, вы делаете их из чего-то. Само «что-то» на самом деле является индексом Git. Индекс в основном там, где вы создаете следующую фиксацию.

Когда вы запускаете git add, Git берет файлы из вашего дерева работы — места, где вы выполняете свою работу, где файлы находятся в том виде, в котором вы действительно можете их использовать, — и копирует их в индекс. Когда вы запускаете git add --all, Git берет все файлы рабочего дерева2 и добавляет их в индекс.

На данный момент они находятся в индексе, но не зафиксированы, поэтому они не являются постоянными. Вы можете git reset вернуть их из из индекса. Это одна из задач git reset: переустановить индекс.

Но эти файлы также находятся в вашем рабочем дереве. Версии рабочего дерева также не фиксируются, поэтому они не являются постоянными. Вы тоже можете git reset их, и это еще одна работа git reset.

И, конечно же, git reset может переместить имя ветки, что нам и нужно в данном конкретном случае, потому что вы зафиксировали файлы, так что теперь они хранятся постоянно как часть нового коммита. Перемещение названий веток — третья из трех основных задач git reset.3 Эти три задачи расположены в порядке важности:

  1. git reset всегда перемещает (переустанавливает) имя ветки;
  2. git reset иногда сбрасывает индекс; а также
  3. git reset иногда сбрасывает рабочее дерево.

Вы управляете ими с помощью --soft (выполняет только задание №1), --mixed (выполняет задания №1 и №2) и --hard (выполняет все три задания).

Вы должны выбрать, сохранять ли свой индекс и/или рабочее дерево, когда выполняете git reset, которое перемещает вашу ветку. Если вы используете git reset --hard, Git выполнит все три действия. Это нормально, если вы готовы потерять временные данные в вашем индексе и рабочем дереве.

Коммиты являются постоянными, хотя если вы забудете их хэш-идентификаторы, их будет трудно найти. Так что можно сбросить их: вы можете вернуть их снова. Более того, вы можете сохранить хэш-идентификатор под новым именем, например, именем нового филиала, а затем легко вернуть их обратно. Давайте посмотрим на последний пример.


2Все, кроме файлов, которые (а) не уже в индексе и (б) перечислены в .gitignore или подобном "не добавлять автоматически" файл.

3Команда git reset также может выполнять несколько более специфических действий, в этом случае она может прекратить выполнение некоторых из своих основных задач, но мы не будем их здесь упоминать.


Последний пример

Допустим, вы находитесь на master и сделали много коммитов, а не один, в которых слишком много файлов. Вы хотели бы запомнить (сохранить) свою работу, но также выбросить индекс и рабочее дерево и снова синхронизировать master с вашим "вышестоящим" репозиторием, из которого вы клонировали, из которого вы ваш вызов Git origin.

В вашем графе коммитов есть некоторые вещи, которые выглядят так:

...--o--o--o   <-- origin/master
            \
             X--o--o--o--Y   <-- master (HEAD)

Вы находитесь в своей ветке master, и на ней есть все коммиты нижнего ряда плюс все коммиты верхнего ряда, которыми вы и другой Git в origin делитесь.

Вы допустили ошибку в самом первом коммите, отмеченном X. Таким образом, вы хотите полностью сбросить master, чтобы указать на ту же фиксацию, что и origin/master, и выбросить ваш текущий индекс и рабочее дерево, потому что они «чистые» (зафиксированы и соответствуют кончику вашего master, который является фиксацией Y). Для этого можно запустить git reset --hard, но тогда вы забудете все эти хеш-идентификаторы от X до Y включительно.

Таким образом, вы можете просто создать новую ветку, указывающую на коммит Y:

git branch save-my-mistake

Теперь ваша картинка выглядит так:

...--o--o--o   <-- origin/master
            \
             X--o--o--o--Y   <-- master (HEAD), save-my-mistake

Сейчас самое время запустить:

git reset --hard origin/master

который перемещает текущую ветвь — все еще master — так, чтобы она указывала на тот же коммит, что и origin/master, а также переустанавливает ваш индекс и рабочее дерево, чтобы они соответствовали этому коммиту:

...--o--o--o   <-- master (HEAD), origin/master
            \
             X--o--o--o--Y   <-- save-my-mistake

И теперь вы можете в любое время получить все, что захотите, из ветки save-my-mistake, потому что имя ветки это запоминает ваши коммиты.

В конце концов, когда вы закончите с этим, вы можете просто удалить эту ветку. Эти коммиты рано или поздно4 станут "сборщиком мусора" и действительно исчезнут из вашего репозитория. До этого момента вы их больше не увидите, так как имя, по которому вы (и Git) можете найти непонятные хэш-идентификаторы, исчезло.


4Срок действия довольно сложный. Это частично зависит от записей reflog, срок действия которых истекает через 90 дней для "достижимых" коммитов и 30 дней для "недостижимых" коммитов, причем достижимый и недостижимый определяется с точки зрения текущего значения ссылки. После истечения срока действия самой записи журнала ссылок базовые объекты Git становятся доступными для этой сборки мусора, если они глобально недоступны, т. е. недоступны для любого имени. Они по-прежнему имеют 14-дневный льготный период с момента их создания, хотя в большинстве случаев, если 30- или 90-дневный период времени уже истек, 14 дней были давно. Однако при удалении имени ветки удаляются все ее записи в журнале ссылок, что может привести к раскрытию более молодых коммитов, которые затем получат все, что осталось от их 14-дневного периода.

В любом случае, все это происходит из git gc --auto, который запускаются другими командами Git, когда они считают, что для этого есть веская причина. Таким образом, пока одна из тех команд Git не запустит git gc --auto, эти просроченные объекты могут остаться. Это очень хорошо соответствует неофициальному названию Git, «боргу контроля версий»: он объединяет все в свой коллектив, но может разрушить ваш мир. :-)

person torek    schedule 08.04.2017
comment
ты спасатель! - person santoku; 09.04.2017

Если вы уже сделали коммит, один из вариантов — удалить папку и клонировать ее снова. Новые изменения будут потеряны! Делайте это только в том случае, если вы работаете над этим репозиторием в одиночку и не сильно изменились.

person qräbnö    schedule 08.04.2017

Вы можете git reset вернуть 2 коммита, запустив git reset HEAD~2 --hard

person thesecretmaster    schedule 08.04.2017

Если вы еще не сделали коммит, запустите:

git тайник -у

person yorammi    schedule 08.04.2017