Git различать все локальные изменения, независимо от того, какая фиксация или постановка / неустановка - PullRequest
0 голосов
/ 05 мая 2020

Одна из вещей, которые мне не нравятся в Git, - это изменение команды diff в зависимости от того, является ли она незастроенной, постановочной, зафиксированной и т. Д. c.

У меня есть ветка и только git push ed для просмотра. Ничего не было отправлено в главную ветку. Тем не менее, я хотел бы сделать последнюю проверку каждой измененной мной строки, даже если она в настоящее время не поставлена.

У меня есть эта команда:

git difftool @{upstream}

но похоже, что разница только с момента последнего коммита.

1 Ответ

1 голос
/ 05 мая 2020

TL; DR

Ваша существующая git diff или git difftool команда вероятно должна дать вам все, что вы хотели бы знать. Если это не так ... ну, как j6t прокомментировал , мы не сможем читать ваши мысли. Может быть, вам еще нужно здесь git diff origin/master @{upstream} или git difftool origin/master @{upstream}.

Long

Там - это проблема или две, но это сложно описать точно . Как ни странно, проблема обычно не является проблемой. Короче говоря: git diff сравнивает только две вещи (ну, вроде как: детали станут немного липкими, позже). Иногда мы хотим сравнить более двух вещей, и для этого нам нужно несколько git diff команд.

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

Фон ключа (длинный)

Помните, что Git - это все о коммитах:

  • У коммитов есть ha sh ID, и это их «настоящие имена», так сказать. Git фактически находит коммиты по их идентификаторам ha sh.

  • Каждый коммит имеет полный и полный снимок всех ваших файлов. Этот снимок - его основные данные. Каждая фиксация также имеет некоторые метаданные, такие как имя человека, который ее сделал, когда они это сделали (дата и время) и почему они это сделали (их сообщение журнала). Используя git log без дополнительных параметров, вы увидите только идентификатор ha sh и метаданные.

  • Файлы внутри каждого коммита полностью, полностью, на 100% доступны только для чтения. Они хранятся в специальном Git - только замороженном формате, сжимаются и дедуплицируются (так что не имеет значения, что каждая фиксация хранит каждый файл: в любом случае есть только одна копия каждой версии).

  • Фактически, все, что касается фиксации, включая все ее метаданные, доступно только для чтения, заморожено на все время. И в этих метаданных каждый коммит перечисляет ha sh ID его родительского коммитов. Обычно здесь всего один ha sh ID. (У коммитов слияния как минимум два, а обычно ровно два. Наличие более одного - это то, что заставляет их объединять коммиты.)

  • Имена веток, имена тегов, необычные вещи вроде @{upstream}, и так далее - это в основном просто способы обозначения конкретных c коммитов. Имена ветвей имеют специальное свойство, которое вы можете получить «на» них (git status говорит on branch master), и когда вы это сделаете, новый фиксация заставляет имя ветки идентифицировать новую фиксацию, а не идентификация совершенного вами коммита с помощью git checkout.

  • Следовательно, по определению, имя ветки идентифицирует последний коммит в ветке. Более ранние коммиты также содержатся в этой ветке, которая работает, следуя родительским идентификаторам ha sh в метаданных каждой фиксации.

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

Эти обычные повседневные файлы хранятся в том, что Git называется рабочим деревом или work- дерево . Но на самом деле их вообще нет в репозитории ! Это становится очень важным очень быстро.

(Мы вернемся к "постановке" через мгновение.)

У меня есть ветка, и у меня есть только git push ed для обзор.

Когда вы используете git push, ваш Git вызывает другой Git. Ваш Git затем отправляет этот другой Git любой коммит , который у вас есть, чего у них нет. Все это работает по идентификаторам ha sh, потому что идентификатор ha sh любой фиксации совпадает в каждые Git. Все это позволяет уникальность идентификаторов ha sh. Два Git могут совместно использовать коммит, а один Git может определить, есть ли у него коммит другого Git, просто по идентификатору ha sh. Ваш Git говорит Я предлагаю вам, другой Git, га sh ID ______ (заполните поле), и они смотрят на это и смотрят, есть ли у них. Если нет, они просят об этом, и ваш Git предлагает свои родительские коммиты, и все повторяется снова; или, если он у них есть, ваш Git закончен с частью подношения.

Итак, ваш Git знает, какие коммиты послать им: те, которых у них нет, которые вы сделали и являетесь теперь отправляем с вашим git push, отправляются именно те. Затем, в конце, ваш Git просит их Git установить одно из их имен веток, чтобы идентифицировать последний коммит в цепочке, которую вы только что отправили. 1

Все это означает, что вы можете только pu sh совершает . Вы не можете удалить sh какие-либо файлы из вашего рабочего дерева, которых вообще нет в репозитории. И когда мы охватываем постановку какого-то файла (ов) за мгновение, вы также не можете создать sh постановочные файлы. Вы можете только pu sh совершает .


1 Фактически, вы можете попросить их установить несколько имен в одном git push. Единственное начальное требование здесь состоит в том, чтобы попросить их установить какое-то имя N на ha sh ID H , они должны иметь объект с ha sh ID H сейчас. Для коммитов это означает, что ваш Git должен сначала предложить отправить им коммит H , а затем фактически отправить его, если они скажут да, пожалуйста , или нет, если они скажут нет спасибо, у меня это уже есть .

Как только они получат запрос на установку _____ (имя) на _____ (ha sh ID), они могут проверить, разрешают ли они sh это имя быть настроенным таким образом, или нет. Ваш pu sh либо завершается успешно, либо получает ошибку (! rejected ...) в зависимости от их ответа на этот запрос.


Различия коммитов

Коммит содержит снимок - полная копия всех ваших файлов.

В большинстве случаев мы не хотим смотреть на снимок в целом . Это слишком много. Если мы действительно хотим посмотреть на снимок в целом, мы используем git checkout для извлечения его файлов в наше рабочее дерево, а затем мы можем использовать для них все наши инструменты просмотра / редактирования файлов. Эта фиксация становится текущей фиксацией . Git всегда знает, какая фиксация у вас текущая - последняя, ​​которую вы git checkout -ed (или git switch -ed, в Git 2.23 и позже).

Однако мы хотели бы сравнить один снимок с другим.

Команды git diff и git difftool могут сделать это:

git diff <left-side-commit> <right-side-commit>

(или то же с git difftool). Что это значит:

  • извлечь всю левую фиксацию во временную область
  • извлечь всю правую фиксацию во временную область
  • сопоставьте левый и правый файлы:
    • для каждого файла, который тот же , ничего не делать
    • для каждого файла, который отличается , показать разницу (git diff) или запустить другую команду, чтобы показать разницу (git difftool)

Когда Git сам показывает разницу, он использует свой собственный в варианте XDiff для вычисления рецепта: внесите эти изменения в левый файл, и вы получите правый файл. Это очень ориентировано на строку: даже если вы используете функции word-diff, Git начинает с поиска измененных строк , а затем обрабатывает результат для получения измененных слов.

Рецепт, который получается из git diff не обязательно означает то, что на самом деле редактировал файлы . Это просто рецепт, который дает тот же результат . Конечно, если вы используете git difftool, это зависит от какой-то другой программы - выбранного вами инструмента - чтобы показать вам, что произошло, независимо от того, каким образом этот инструмент выполняет эту работу.

Коммиты существуют только тогда, когда они зафиксированы

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

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

git diff <left-side-commit>

: мы называем левую часть фиксации, как нам нравится - ha sh ID или по имени, например master, branch или @{upstream} - и Git извлекает эту фиксацию во временную область, а затем сравнивает эту фиксацию с файлами рабочего дерева.

А как насчет поэтапных файлов?

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

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

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

Индекс начинается с совпадения с текущей фиксацией. То есть каждый файл, который находится в фиксации, теперь тоже находится в индексе, а файлы, которые не в фиксации, не находятся в индексе.

Запуск git add <em>path</em> берет указанный файл и копирует его в индекс. Любая копия, которая была там раньше, теперь заменена. Если раньше копии не было, то теперь она есть. В любом случае люди называют это постановкой файла для фиксации . Но обычно там раньше был файл. Мы только что заменили .

Когда вы запускаете git status, Git выполняет две git diff операции. Оба запускаются с флагом, который говорит: не найти рецепт изменения, просто узнайте, есть ли изменения . 3

Первый git diff сравнивает текущая фиксация в индекс. Везде, где здесь одинаковые файлы, Git ничего не говорит. Для разных файлов Git говорит staged for commit .

Второй git diff сравнивает индекс с вашим рабочим деревом. Везде, где здесь одинаковые файлы, Git ничего не говорит. Для файлов, которые отличаются, Git говорит, что не используется для фиксации .

Когда вы все-таки приступите к запуску git commit, Git упаковывает все файлы, которые находятся в index - они уже в замороженном формате, что упрощает эту задачу - и записывает новую фиксацию. Родителем нового коммита является текущий коммит. Новый коммит получает новый уникальный идентификатор ha sh. За исключением родительского (текущая фиксация) и отметок времени («сейчас»), метаданные берутся из предоставленных вами данных: вашего имени, адреса электронной почты и сообщения журнала. И теперь новый коммит с его новым идентификатором ha sh заморожен навсегда, и если git status говорит on branch xyz, Git вставляет ha sh ID нового коммита в ваше имя ветки xyz.


2 Технически то, что находится в индексе Git, не является копией файла, а скорее информацией о копии файла . Скопированное содержимое файла сохраняется как внутренний Git объект blob . Вам не нужно знать об этом или заботиться об этом, если вы не углубитесь в Git и не используете git ls-files --stage или git update-index. Вы можете думать об индексной копии файла как о «копии», и все это просто работает.

3 Вы можете использовать этот флаг самостоятельно: git diff --name-status. Формат вывода немного отличается: git status пытается представить результаты в более удобной для пользователя форме.


Четыре основных способа запуска git diff или git difftool

Чтобы заставить промежуточную область играть роль в git diff или git difftool, вам может понадобиться опция --cached или --staged. Оба означают одно и то же.

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

  • git diff <em>commit1 commit2</em>: если мы хотим сравнить два коммита друг с другом, это выполнит эту работу. Git помещает первую фиксацию (по имени или ha sh ID) слева, а вторую - справа.

  • git diff <em>commit</em>: сравнивает фиксацию с текущей дерево работы. Git помещает указанную фиксацию слева, а дерево работ - справа.

  • git diff --cached <em>commit</em>: сравнивает фиксацию с индексной / промежуточной областью. Именованная фиксация идет слева, а файлы индекса - справа.

  • git diff --cached: сравнивает текущую фиксацию с индексной / промежуточной областью, как если бы мы запустили git diff --cached HEAD. (Это пятый способ подсчета маркеров, но на самом деле он точно такой же, как и четвертый способ.)

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

Несколько особых случаев

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

Команда git show очень удобна. Это похоже на git log, но с включенным режимом исправления для одной фиксации: 4

git show <commit>

показывает сообщение журнала, а затем diff. Команда diff сравнивает родительский фиксации с фиксацией. Поскольку родительский объект содержит снимок «до», а сама фиксация содержит снимок «после», результатом является рецепт: сделайте это с содержимым до-фиксации, и вы получите содержимое указанного коммита.

Однако, когда вы используете git show для слияния коммита, не существует только одного родителя. Это вызывает git diff особым образом, где git diff запускает более одного различия. Git diffs каждый родительский коммит против дочернего коммита, по одному. Затем он объединяет эти различия в то, что он называет комбинированным различием .

Комбинированные различия немного разочаровывают, по крайней мере, для меня, потому что здесь правила выбросить любой файл, который соответствует от любому родительскому к дочернему. То есть, если вы взяли файл без изменений с одной стороны слияния, отбросив всю работу, проделанную кем-то на другой ветке, git show не покажет вам этого . Иногда это была ошибка, которую вы искали, а git show просто не показывает ее. Вы можете заставить git show разбить это на несколько отдельных git diff s - используйте git show -m - и это часто способ найти эту проблему, когда это проблема.

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


4 git show может делать гораздо больше. Прочтите его документацию для подробностей.


Заключение

Пока вас интересует, что произошло в коммитах , способ выяснить, это использовать форму с двумя фиксациями:

git diff <commit-1> <commit-2>

или использовать git show для автоматического сравнения родительского и дочернего:

git show <commit>

Вы можете использовать форма git diff <em>commit</em>, которая сравнивает данную фиксацию с деревом работ, если содержимое дерева работ совпадает с желаемым commit-2 содержимым. Но если вы хотите сравнить два коммита, вам нужны два имени, или два ha sh идентификаторов, или, например, сочетание каждого из них.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...