git агония часть 2: что это значит, когда git не будет фиксировать, извлекать или извлекать sh файл? - PullRequest
1 голос
/ 01 апреля 2020

Базовая онтология c git остается иллюзорной и глубоко разочаровывающей.

У нас есть главный репозиторий на github.com. Допустим, у нас есть 1 файл, R-файл, в этом хранилище.

  1. Я клонирую.

  2. Мои сотрудники-клоны.

  3. Она редактирует. Она совершает. Она толкает

  4. Я редактирую. Я обязуюсь. Я пу sh. Я не могу, поскольку она уже подтолкнула.

  5. Так что я тяну. Я сливаю код. У меня все работает нормально. Я снова sh. Я не могу

! [отклонено] HEAD -> основная (не ускоренная перемотка вперед) ошибка: не удалось вывести sh некоторые ссылки на 'https://github.com/PeterLeopold/myRepo' подсказка: обновления были отклонены, поскольку кончик вашей текущей ветви находится позади подсказка: его удаленный аналог. Интегрируйте удаленные изменения (например, подсказка: «git pull ...») перед повторным нажатием. Подсказка: подробности см. в «Примечании о быстрой перемотке вперед» в «git pu sh --help».

Я снова тяну. Я не могу

ошибка: вы не завершили свое слияние (MERGE_HEAD существует). Подсказка: пожалуйста, передайте изменения перед слиянием. фатальный: выход из-за незавершенного объединения.

Я повторяю снова. Я не могу

ошибка: фиксация невозможна, потому что у вас есть файлы без объединения. подсказка: исправьте их в рабочем дереве, а затем используйте подсказку 'git add / rm': при необходимости отметьте разрешение и сделайте коммит. фатальный: выход из-за неразрешенного конфликта.

U ksUpgrade.R

[конфликт прекрасно разрешен!]

Итак. Теперь у меня отлично работает объединенный код и нет способа отправить его обратно в хранилище. Поэтому я отправляю письмо своему сотруднику. , ,

Чего мне не хватает? Дай угадаю. , , git все еще в альфе?

PS В предыдущем вопросе я узнал, что 100% сопутствующего рабочего продукта (файлы, рисунки, таблицы) не могут быть версионированы / распространены github, так как эти файлы генерируются при каждом выполнении кода и git всегда должен пытаться объединить байты и не может просто наложить их на слои (если только они не помечены как двоичные, а это не так, поэтому для максимизации производительности нужно обмануть git. Да, обман git - это подсказка и это задокументировано!)

Мой соавтор и я еще не используем ветки или вилки. Эти режимы отказов остаются за углом.

(Этот опыт смутно напоминает принципиально неразрешимые несовместимости путей классов в java.)

Скажите, что это становится лучше. , , : <</p>


Вот вывод «git status». Вы заметите, что я был занят попыткой удалить каждый файл, который не является файлом R или Rmd. Я в значительной степени, но не совсем преуспел.

Изменения, не подготовленные для фиксации:

(используйте "git add / rm ...", чтобы обновить то, что будет зафиксировано)

(используйте "git restore ..." для отмены изменений в рабочем каталоге)

   deleted:    README.html

   deleted:    README.md

   deleted:    SourceRawData.csv

   modified:   age_dcu.png

   modified:   bar.af.png

   modified:   bar.all.png

   modified:   bar.ca.png

   deleted:    ksPrepForSSSSS.csv

   deleted:    prepForTTTTT.csv

   deleted:    senxxx.csv

   deleted:    timaaaa.csv

Не отслеживаемые файлы:

(используйте "git add. .. "включить в то, что будет совершено)

   tables for manuscript_2.docx

   tables for manuscript_3.docx

   ~$bles for manuscript_2.docx

   ~$bles for manuscript_3.docx

Ответы [ 2 ]

2 голосов
/ 01 апреля 2020

Распределенная совместная работа - сложная задача.

Git пытается упростить задачу. Git терпит неудачу. Не вините Git ... ну, вините его некоторые , потому что его попытка облегчить это имеет кучу fl aws. :-) Но здесь все пошло не так:

Так что я тяну. Я сливаю код. У меня все работает нормально. Я снова sh. Я не могу.

Это потому, что вы еще не совершили ваше слияние.

Вы говорите, что разрешили их (и я поверю вы) но теперь вы должны сказать Git, что вы их решили. Это отдельный шаг. Вы должны использовать git add или git rm до , чтобы сообщить Git об этом.

У меня очень длинный ответ ниже; читайте все, что вам нравится.

Что нужно знать о коммитах

Вам нужна хорошая ментальная модель, чтобы не потеряться. Начните с этого: Git на самом деле не о файлах или ветвях. Git на самом деле о коммитах . Чтобы выполнить работу с Git, вам нужны коммиты. Хранилище - это в основном большая коллекция коммитов. Итак, вам нужно знать: что именно является коммитом?

Коммит состоит из двух частей:

  • Он содержит снимок все файлы. Это не изменения для файлов, а сами файлы. Снимок - это основные данные коммита.

  • И он содержит некоторые метаданные , или информацию о коммите: кто, например, когда это сделал и когда.

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

имя коммита какой-то большой уродливый га sh ID, такой как 9fadedd637b312089337d73c3ed8447e9f0aa775. Эти вещи совершенно бесполезны для потребления человеком; самое большее, вы можете вырезать и вставить один или использовать сокращенный при необходимости. Но важно помнить, что это истинные имена Git для каждого коммита. Каждый коммит получает уникальный: после того, как вы сделаете новый коммит, никакой другой коммит, нигде и в любое время, с этого момента не может иметь этого имени. В некотором смысле, это имя sh было зарезервировано для этого коммита даже за до того, как вы его сделали, но ха sh зависит от точной секунды, в которую вы его сделали, поэтому в принципе невозможно предсказать идентификатор будущего.

Ключевым элементом метаданных внутри каждого коммита является необработанный идентификатор ha sh его предыдущего коммита. Это делает каждый коммит своего рода жемчужиной на цепочке, за исключением того, что каждая жемчужина соединяется только назад . Git не может записать идентификатор ha sh для future commit, потому что невозможно предсказать, и когда он сделан, никакой коммит никогда не может измениться, поэтому эти ссылки могут только go в обратном направлении.

Когда у нас есть что-то с коммитом ha sh ID, мы говорим, что эта вещь указывает на данного коммита. Таким образом, каждый коммит указывает на своего предшественника:

... <-F <-G <-H

, где я использовал заглавные буквы для замены реальных идентификаторов ha sh. последний один в цепочке равен H, поэтому H указывает на G, что указывает на F, и так далее. Если мы будем следовать цепочке достаточно долго, мы в конечном итоге найдем первый коммит, который когда-либо был сделан, который - будучи первым - просто не указывает назад в конце концов.

Что нужно знать о ветке Имена

На самом деле, есть кое-что, что нужно знать, но давайте начнем с простой части. Каждый Git репозиторий - каждый клон - имеет свои имена ветвей. Каждое имя ветви содержит только га sh ID один коммит.

Давайте нарисуем простую цепочку из трех коммитов:

A--B--C   <-- master

Имя master указывает на C; C указывает на B; и B указывает на A, который является первым коммитом, поэтому на этом мы остановимся. Между тем, я стал слишком ленивым (хорошо ...), чтобы рисовать стрелки между коммитами. Мы знаем, что они go только в одну сторону - назад - и этого достаточно; как только они сделаны, они не могут изменить , поэтому мы можем просто игнорировать направление здесь, когда это удобно. Стрелка в ветке name , тем не менее, может измениться.

Давайте добавим второе имя ветви сейчас, dev для разработки. Мы также укажем на C, например:

A--B--C   <-- master, dev

Теперь нам нужно знать: какое имя ветки мы используем? Git запоминает это, прикрепляя специальное имя HEAD, написанное заглавными буквами, как это, 1 одному из названий ветвей. Имя , к которому присоединено HEAD, - текущая ветвь , а фиксация , на которую указывает это имя , - текущая фиксация , Поэтому, к какому бы имени мы ни прикрепили HEAD, текущий commit будет C. Давайте прикрепим HEAD к dev:

A--B--C   <-- master, dev (HEAD)

Теперь давайте сделаем коммит new обычным способом not-make-any-merges (которым вы уже пользуетесь). знаком с). Этот новый коммит получит какой-то большой уродливый идентификатор sh, но мы просто назовем его D:

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

Имя HEAD все еще прикреплено к имени dev, но теперь имя dev указывает на новый коммит D. Новый коммит указывает на старый коммит C в качестве его родителя, потому что мы использовали коммит C, когда мы сделали D.

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


1 В Windows и MacOS часто можно набрать head в нижнем регистре. Это плохая привычка, потому что (а) он обычно не работает на Linux, где Git просто не понимает его, и (б) он перестает работать, когда вы начинаете использовать git worktree add, в четном хуже, чем он не работает на Linux: он начинает выбирать неправильный коммит .


Индекс и ваше дерево работы

Время пришло говорить об индексе и вашем рабочем дереве. Раньше мы об этом молчали, потому что вы уже знаете, как сделать новый коммит, например D, но сейчас важно разобраться со всеми деталями, потому что они нам понадобятся для слияния.

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

Используемые файлы go в вашем рабочем дереве , где вы можете их видеть и работать над ними. Это подводит нас к первому пункту о файлах против коммитов: Файлы внутри коммита - это не те файлы, которые вы можете видеть и работать с ними. Вы должны извлечь эти файлы из коммита first.

Команда git checkout делает это. Существует простой, но неправильный способ описать , как git checkout делает это: мы выбираем коммит, например C или D, используя имя ветви, например master или dev. Git затем извлекает файлы из этого коммита.

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

Вместо этого Git сохраняет третью копию каждого файла, что-то среднее между подтвержденной копией и используемой. 2 Эта промежуточная копия входит в Git index . Git заполняет его, когда вы впервые делаете git checkout, из выбранного вами коммита. Таким образом, три копии каждого файла всегда: замороженная копия в коммите, копия индекса и ваша копия рабочего дерева, которую вы можете видеть и работать с ней.

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

Файл был там раньше! Что ж, так и было, если не было - если это совершенно новый файл, его сейчас нет в HEAD: теперь он только в индексе и рабочем дереве.

Итак, файл ставится для фиксации , если индексная копия не совпадает с HEAD копией. Если файл полностью новый, конечно, это не соответствует; в противном случае он совпадает или не совпадает очевидным образом.

Поскольку ваше рабочее дерево представляет собой обычный набор файлов и каталогов / папок на вашем компьютере, вы также можете создавать файлы рабочего дерева, которые вы не копировать в указатель. Файл, который находится в рабочем дереве, но не в индексе, является неотслеживаемым файлом . Git часто будет жаловаться на неотслеживаемые файлы: git status будет постоянно информировать вас о том, что они существуют. (Вы можете помолчать об этом, но мы оставим это на потом.)

Это подводит нас ко второму пункту о файлах и фиксирует: Файлы, которые будут go в Следующим коммитом являются файлы, которые находятся в Git index , а не те, которые вы видите в своем рабочем дереве. Когда вы запускаете git commit, Git просто упаковывает все находится в индексе в этой точке. Это , что станет следующим снимком.

Есть много тонкостей в отношении индекса и рабочего дерева, которые мы здесь просто не рассмотрим. Индекс Git, пожалуй, самая хитрая часть, особенно потому, что вы не можете увидеть его. На самом деле вы не можете видеть также все хорошо, но по крайней мере git log говорит вам о них. Не существует никакой пользовательской команды, которая бы много говорила вам об индексе, за исключением git status ... и git status сообщает вам только путем сравнения вещей (подробнее о status ниже).


2 Технически, в индексе есть mode , имя файла и ссылка на внутренний Git объект blob . Но если вы не начнете работать с некоторыми внутренними командами Git, вы можете просто рассматривать индекс как отдельную копию.


Ветви со временем расходятся ... или нет

Когда у нас был пример репозитория с тремя фиксациями, мы сделали четвертый коммит на dev и получили:

A--B--C   <-- master
       \
        D   <-- dev (HEAD)

Теперь мы можем git checkout master:

A--B--C   <-- master (HEAD)
       \
        D   <-- dev

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

Теперь мы можем внести некоторые изменения, git add их - которые, как вы теперь знаете, копируют обновленные файлы рабочего дерева обратно в индекс, делая их готовыми к фиксации. - и git commit чтобы сделать новый коммит E:

        E   <-- master (HEAD)
       /
A--B--C
       \
        D   <-- dev

Когда это продолжается некоторое время, вы получаете материал, который выглядит следующим образом:

          o--o--o   <-- branch1
         /
...--o--o
         \
          o--o   <-- branch2

и так далее , Но иногда у вас просто есть:

A--B--C   <-- master (HEAD)
       \
        D   <-- dev

В такой ситуации , когда у вас есть имя ветки, которое указывает на коммит, который также является частью более ранней ветки - commit C находится на обеих ветвях, что является еще одной особенностью Git - и дивергенции нет, git merge dev обнаружит этот случай и фактически не будет выполнять слияние. Он просто перемещает name master вперед и проверяет другой коммит:

git merge dev

приводит к:

A--B--C
       \
        D   <-- master (HEAD), dev

(после чего мы можем просто снова нарисуйте их все в одну строку).

Git называет этот тип не-слияния ускоренным слиянием . Иногда это то, что вы хотите. Иногда это не так. Иногда это просто невозможно в любом случае.

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

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

, вам необходимо выполнить реальное слияние.

Реальное слияние

Когда Git выполняет реальное слияние, ему нужно перезаписать свой индекс и ваше рабочее дерево. Это означает, что они должны быть «чистыми» (соответствовать текущему коммиту) при запуске, по крайней мере, в общем случае. Если они не являются чистыми, вы получите ошибку, и объединение даже не начнется, за исключением некоторых особых случаев.

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

Объединение называется трехсторонним объединением , возможно, потому, что оно имеет три входа. Три входа:

  • a база слияния коммит, который Git находит самостоятельно;
  • ваш текущий коммит, как найдено через HEAD и текущее имя ветви; и
  • еще один коммит, который вы выбираете командой git merge.

В этом случае, допустим, вы делаете git checkout branch1; git merge branch2, чтобы у вас было:

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2

база слияния - это лучший общий коммит: коммит, который находится на обеих ветвях, это не слишком далеко от каждой ветви ветки , Советы по ветвлению - это коммиты J и L, и здесь ясно, что лучший общий коммит - это коммит H. 3

Итак, что теперь делает Git, так это для каждого файла в снимке для H скопируйте этот файл в слот № 1 в индексе. Если файлы имеют имена README.md и bar.all.png, индекс теперь имеет README.md # 1 и bar.all.png # 1. Для каждого файла в снимке для J скопируйте этот файл в слот № 2 в индексе. Если J имеет те же два файла, эти go в README.md слот 2 и bar.all.png слот 2. Затем Git считывает файлы из L в индекс, в слот 3.

Теперь, когда есть копия каждого файла из каждого коммита в каждом слоте индекса, Git запускает разрешения файлов. Разрешенные копии go в «нулевом слоте», что является нормальным состоянием индекса без слияния.

  • Если все три копии совпадают, подойдет любая копия. Разорвите все три из трех пронумерованных слотов и поместите один в нулевой слот. (С копией рабочего дерева все в порядке, оставьте ее в покое.)

  • Если ваша копия отличается, но база и их копия совпадают, возьмите вашу копию , Удалите их и поместите свой в нулевой слот. (Копия рабочего дерева все еще в порядке, потому что мы использовали вашу.)

  • Если их копия отличается, но база и ваша совпадают, возьмите их копировать. (Замените копию рабочего дерева на их.)

  • Если все три различны , Git действительно должен усердно работать.

Когда три копии в трех промежуточных слотах все разные, Git фактически попытается объединить файлы. Может ли и когда Git это сделать, немного сложно. Если Git считает, что самостоятельно слил их правильно, Git запишет полученный файл как в ваше рабочее дерево, так и в свой собственный нулевой слот и удалит остальные три копии. Если Git считает, что не не слил их правильно, Git оставит свою беспорядочную попытку слияния в вашем рабочем дереве - если только он не думает, что файл является двоичным, в этом случае он не касается файл вообще - и оставляют все три копии в трех пронумерованных слотах.

Эти пронумерованные слоты, если они еще используются, помечают объединение как конфликтующий, Ваша задача состоит в том, чтобы как угодно - как угодно - придумать объединенный файл right и скопировать его в нулевой слот индекса, очистив слоты 1, 2 и 3. Один простой способ сделать это - по крайней мере, обычно - чтобы отредактировать беспорядочное слияние, которое находится в вашем рабочем дереве, затем запустите git add, который копирует в индекс и выполняет весь сброс слотов.

Существуют и другие способы получения конфликтов слияния , которые мы будем только слегка коснуться. Предположим, вы удалили файл, а они изменили его, так что базовая копия и их копия отличаются, но у вас просто нет копии вообще. Это также вызывает конфликт слияния, но на этот раз слот № 2 - копия --ours - просто пуст. У вас все еще есть файлы без слияния, но теперь вам нужно объединить это другим способом. Вы, вероятно, по-прежнему будете использовать git add, который по-прежнему работает так же, или, может быть, git rm до удалить файл из любых слотов, в которых он находится.

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

Как только у вас есть коммит слияния, у вас есть это:

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2

Новый коммит слияния имеет снимок как обычно - он основан на том, что вы положили в индекс, в нулевую позицию обычного слота - и имеет двух родителей. Он указывает на существующий коммит J, как и обычный коммит, но он также указывает на коммит L, тот, который вы выбрали для слияния.

Вы можете продолжить добавление обычного (без слияния) commitits:

          I--J
         /    \
...--G--H      M--N--O--P   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2

и график просто растет нормально, а имя ветви продолжает указывать на last commit в ветви. Обратите внимание, что коммиты K-L, которые раньше были только на branch2, теперь находятся в обеих ветвях, но коммиты I-J-M-N-O-P все только на branch1: вы можете начать с P и работать в обратном направлении ко всем коммитам, следуя обеим ссылкам с M, но вы не можете работать в обратном направлении с L и достигать M или J или I.


3 В более запутанных На графиках не всегда понятно, где находится база слияния. В некоторых случаях существует более одного «лучших» коммитов, и все становится немного сложнее. Мы не будем беспокоиться о них здесь.

4 Есть несколько причин, чтобы узнать об индексе, но тот факт, что вам приходится манипулировать им при объединении, является, пожалуй, самым важным один. Но неотслеживаемые файлы невозможно объяснить правильно, не зная об индексе.


git fetch и git push

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

Это не так, как Git работает внутри. В Git каждый репозиторий является одноранговым: нет более мастерского или менее мастерского Git репо. Но легко притвориться , что репозиторий Git, скажем, на GitHub, является "настоящим". В этом нет ничего плохого. Просто помните, что репозиторий GitHub - это просто еще один клон, и он работает так же, как и любой клон Git, с точки зрения извлечения, отправки и имен ветвлений и т. Д.

Теперь, опять же, каждый Git репозиторий имеет свои имена веток. То, что разделяет Gits: совершает . У коммитов есть уникальные идентификаторы ha sh, поэтому каждый Git может определить, есть ли у него все коммиты, которые есть у другого, или нет.

Когда вы и кто-то еще клонируете центральный репозиторий, вы оба начинаются с одного и того же набора коммитов. Ваши Gits также берут имена ветвей центрального репозитория и копируют их в свои имена для удаленного отслеживания , такие как origin/master и origin/develop. Эти имена также являются частными для ваших собственных репозиториев: просто каждый раз, когда вы подключаете ваш Git к центральному, ваш Git может считывать их branch names и обновлять ваш remote -tracking names.

Создав имена для удаленного слежения, ваш репозиторий Git теперь создает для вас одно ответвление , обычно master. Это имя указывает на тот же коммит, что и ваш origin/master, который Git настроил так, чтобы он указывал на тот же коммит, с идентификатором ha sh, как их Git:

...--G--H   <-- master (HEAD), origin/master

Посмотрим, что будет дальше.

  1. Я клонирую [из центрального Git].
  2. Мои соавторы клонируют [из центрального Git].
  3. Она редактирует. Она совершает. Она толкает.

Когда она бежала git commit, она получила новый уникальный идентификатор ha sh, отличный от всех других идентификаторов ha sh. Мы просто назовем это I. Ее Git обновил ее master:

...--G--H   <-- origin/master
         \
          I   <-- master (HEAD)

Затем она побежала git push.

Ее Git теперь вызывает центральный Git. Ее Git говорит этому главному Git: Эй, мне сказали предложить вам коммит с га sh I. Его родитель H. Вы хотите I? Они говорят: Я не видел I, дай мне! У меня уже есть H, хотя. Просто дайте мне совершить коммит I.

Они вставляют коммит I временно в свой репозиторий без имени:

...--G--H   <-- master
         \
          I   [no name]

Помните, это представление в центральное хранилище. Я пропустил HEAD, потому что нам все равно, какую ветку они проверили, если они есть, и если это GitHub, у них вообще ничего не проверено. 5 Последняя часть ее git push состоит в том, что ее Git спрашивает их Git: Пожалуйста, если все в порядке, установите master на I сейчас.

У них нет Причина, чтобы возразить, и она имеет разрешение сделать запрос, поэтому они делают это. Их Git теперь имеет:

...--G--H--I   <-- master

, а ее Git обновляет ее origin/master так, что она имеет:

...--G--H--I   <-- master (HEAD), origin/master

У вас, с другой стороны, все еще есть это :

...--G--H   <-- master (HEAD), origin/master

Ваш Git не связан с центральным Git, поэтому ваш Git не знает, что существует новый коммит I.

Я редактирую. Я обязуюсь. I pu sh.

Вы редактируете и фиксируете, и делаете новый коммит с новым уникальным идентификатором ha sh. Давайте нарисуем это:

...--G--H   <-- origin/master
         \
          J   <-- master (HEAD)

Вы запускаете git push, поэтому ваш Git вызывает центральный Git и предлагает коммит J (с родителем H). Они берут J и помещают его во временный карантин:

...--G--H--I   <-- master
         \
          J   [no name]

Теперь ваш Git запрашивает их Git: Пожалуйста, если все в порядке, установите master, чтобы указать, что совершить J. Но на этот раз они говорят нет! Они говорят: Если я это сделаю, я потеряю способность находить коммит I. Гитс работает задом наперед , Начиная с I, он находит H, а затем G и так далее. С J он может найти H, но H только точки назад . Их Git может сказать, что если они переместят свои master, чтобы указать на J, они потеряют коммит I. Поэтому они просто говорят «нет»: не ускоренная перемотка вперед .

Этот термин ускоренной перемотки соответствует тому, который мы видели ранее, с операциями git merge, которые фактически не объединяются. Git требует, чтобы результат изменения имени ветви, предложенный git push, был одной из этих операций ускоренной перемотки вперед.

Вот почему ваш pu sh был отклонен . Теперь вам нужно предпринять корректирующие действия.


5 На серверах обычно находятся голые репозитории, у которых нет рабочего дерева. Это необходимо, потому что изменение имени ветви, пока кто-то работает над веткой, портит все. Несмотря на это, серверные репозитории по-прежнему имеют HEAD, который управляет функцией, о которой мало кто заботится, и мы не будем go сюда.


О git pull

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

...--G--H--I   <-- origin/master
         \
          J   <-- master (HEAD)

Ваш Git добавил общий коммит I в вашу коллекцию и обновили ваши origin/* имена, чтобы они соответствовали их именам. Ваши имена ветвей все неизменны.

Теперь вам нужно каким-то образом объединить то, что вы сделали, в коммите J, с тем, что она сделала, в коммите I. Этот шаг объединения - или может быть в любом случае - простой старый обычный git merge.

Вы можете выполнить:

git merge origin/master

, который сообщает вашему Git: Найдите общая база слияния между моим коммитом J, найденным моим HEAD, и коммитом I, найденным origin/master. Затем поместите файлы из H в слот 1, файлы из J в слот 2 и файлы из I в слот 3. Затем попытайтесь объединить.

Команда git pull это удобная команда, которая просто комбинирует run git fetch со второй командой Git, обычно git merge. Если Git может объединить вашу работу и ее работу самостоятельно, Git подтвердит результат.

(Примечание: объединение не только * * Опция 1710 *. Вы также можете использовать git rebase. Однако ребаз на самом деле более сложен, чем слияние, и этот ответ уже слишком длинный ...)

Когда именно Git может комбинировать работу?

Чтобы ответить на этот вопрос, go вернемся к тому факту, что коммиты снимки . У нас есть:

       J   <-- master (HEAD)
      /
...--H
      \
       I   <-- origin/master

Git должен сравнить снимок в H, базу слияния, со снимком в J, вашем коммите. Что бы вы не изменили , это то, что Git должен делать с файлами в коммите H. Но Git также должен сравнивать снимок в H со снимком в I: ее коммит. Что бы она не изменила, это то, что Git должен делать с файлами в коммите H.

Когда вы вносите массовые изменения в свой коммит - например, " удалить файл "- и она сделана по частям, как" изменить тот же файл ", Git не сможет объединить их вообще. Единственные изменения, которые Git может объединить, - это те изменения, которые сделаны в строках (или, конечно, в совершенно отдельных файлах). Git очень ориентирован на линию. Git будет запускать внутренний эквивалент:

git diff --find-renames <hash-of-H> <hash-of-J>    # what you changed
git diff --find-renames <hash-of-H> <hash-of-I>    # what she changed

Если эти различия показывают, что она изменила строку 47 из bar.all.png, вам лучше не менять строку 47, или Git не будет знаете, как их объединить.

Конечно, *.png файлы не имеют строк . Так что Git никогда не сможет объединить их! Git здесь не тот инструмент. Если у вас есть инструмент слияния изображений, рассмотрите возможность извлечения всех трех файлов PNG из трех слотов индекса и запуска инструмента слияния изображений для этих трех файлов.

Наблюдение графика фиксации

Один ключ чтобы добиться чего-либо в Git, нужно соблюдать график 1755 * коммитов, чтобы вы могли видеть, где ваши коммиты расходятся с коммитами других и где они объединяются. Чтобы просмотреть график, рассмотрите модные графические средства просмотра (которые могут рисовать хорошие) или используйте git log --decorate --oneline --graph. Опции D, O и G дают вывод, который выглядит следующим образом:

*   9fadedd637 (HEAD -> master, origin/master, origin/HEAD) Merge branch 'ds/default-pack-use-sparse-to-true'
|\  
| * 2d657ab95f pack-objects: flip the use of GIT_TEST_PACK_SPARSE
| * de3a864114 config: set pack.useSparse=true by default
* | 3bab5d5625 The second batch post 2.26 cycle
[snip]

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

Подробнее о git status

Команда git status имеет два различных режима, которые она выберет автоматически в зависимости от того, есть ли у вас необработанные файлы.

Если у вас есть нет необработанных файлов - если все в индексе находится на стадии ноль - в выводе git status будут перечислены только подготовленные файлы и неустановленные файлы . Однако, если у вас есть необработанные файлы, Git покажет неотмеченные файлы вместо любых неотмеченных.

Когда все находится на нулевой стадии, git status запускает внутренне два быстрых git diff с:

  • Первое сравнение HEAD против индекса. Что бы здесь ни было отличается , Git говорит организовано для коммита . Для файлов, которые совпадают, Git ничего не говорит.
  • Второй сравнивает индекс с вашим рабочим деревом. Что бы не отличалось здесь, Git говорит, что не подготовлено для коммита . Для файлов, которые совпадают, Git ничего не говорит.

Так что это позволяет вам увидеть, что в индексе, вроде: вы видите, как сравнивает , а не то, что на самом деле в это.

О git stash

Другой ответ рекомендует git stash. Я вообще избегаю git stash. Он выполняет коммиты, а затем использует git reset --hard, чтобы стереть все, что вы сделали в индексе и в своем рабочем дереве (хотя то, что вы сделали, сохраняется в коммитах, которые он сделал). Коммиты, которые он делает, не относятся к любой ветке , и их трудно увидеть и использовать даже по Git стандартам. Поэтому я думаю, что лучше просто делать обычные коммиты.

Обратите внимание, что поскольку Git делает коммиты из индекса, git stash становится беспомощным перед лицом конфликтующего слияния. Git буквально не может совершить конфликтующее слияние. Вероятно, он должен быть в состоянии - должен быть способ приостановить и возобновить слияние - но он не может.

2 голосов
/ 01 апреля 2020

Вы можете попробовать следующие шаги:

  • Обратите внимание на <commit-id> коммита, который вы сделали в пункте 4, используя git log.
  • Сбросить состояние репозитория до с фиксацией git reset --hard <commit-id> ( вы потеряете все незафиксированные изменения )
  • Выполните операцию выборки в случае появления новых изменений
  • Выполните операцию слияния (возможно, с использованием --no-commit --no-ff )
  • Зафиксировать изменения
  • Pu sh на удаленном
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...