На гитхабе код открыт тыща строк как этот бит
Перейти к содержимому

На гитхабе код открыт тыща строк как этот бит

  • автор:

Wzsound — Podvoram

По дворам
По дворам гуляю в хлам
Инстаграм, телеграм — не сейчас
По району в ночи час
Рассекаю бормоча
Мне луна свет несла
Ебнул боттл пополам
Моросил звон по рукам
Раздробилась по кускам
Не связать стог из слов
Сто столбов, тень рябит
Я ей всё толкую час
Это наша часть
Не придет месть для нас
Не уйдет шиш на нас
Не зайдет этот бит
Для всех вас
На репит
Ставлю фит
Скоро meet
Собрал shit
Двинул на taxi
Прямо к точке “Си”
Блант ты загаси
На айфон насоси

SoundCloud:

Comments: 0

More Wzsound lyrics

Wzsound — Podvoram
По дворам По дворам гуляю в хлам Инстаграм, телеграм — не сейчас По району в ночи час Рассекаю бормоча Мне луна свет несла Ебнул боттл пополам Моросил звон по

Wzsound — Github
На гитхабе код открыт Тыща строк как этот бит Белый холст и масло знаков В голове кипит, в голове визжит Соло раммов, пара граммов ещё действует на ритм Слово, знак, точка,

Wzsound — Github

Пост потрет но весь трилл не уйдет
Тот анон на линк ткнет и вся ночь напролёт
Ролл за роллом дабл за даблом
В третьем рейхе дряблом
Ты сидишь как артист
Чат *бешь не поймешь
Что случилось когда утром ты зайдешь

SoundCloud:

Comments: 0

More Wzsound lyrics

Wzsound — Podvoram
По дворам По дворам гуляю в хлам Инстаграм, телеграм — не сейчас По району в ночи час Рассекаю бормоча Мне луна свет несла Ебнул боттл пополам Моросил звон по

Wzsound — Github
На гитхабе код открыт Тыща строк как этот бит Белый холст и масло знаков В голове кипит, в голове визжит Соло раммов, пара граммов ещё действует на ритм Слово, знак, точка,

В самом популярном фрагменте кода за всю историю StackOverflow ошибка!

Недавнее исследование «Использование и атрибуция сниппетов кода Stack Overflow в проектах GitHub» внезапно обнаружило, что чаще всего в опенсорсных проектах встречается мой ответ, написанный почти десять лет назад. По иронии судьбы, там баг.

Давным-давно…

Еще в 2010 году я сидел в своём офисе и занимался ерундой: развлекался код-гольфингом и накручивал рейтинг на Stack Overflow.

Моё внимание привлёк следующий вопрос: как вывести количество байт в удобочитаемом формате? То есть как преобразовать что-то вроде 123456789 байт в «123,5 МБ».

Старый добрый интерфейс 2010 года, спасибо The Wayback Machine

Неявно подразумевалось, что результатом будет число между 1 и 999,9 с соответствующей единицей измерения.

Уже был один ответ с циклом. Идея простая: проверять все степени с самой большой единицы (ЭБ = 10 18 байт) до самой маленькой (Б = 1 байт) и применить первую, которая меньше числа байт. В псевдокоде это выглядит примерно так:

suffixes = [ "EB", "PB", "TB", "GB", "MB", "kB", "B" ] magnitudes = [ 10^18, 10^15, 10^12, 10^9, 10^6, 10^3, 10^0 ] i = 0 while (i < magnitudes.length && magnitudes[i] >byteCount) i++ printf("%.1f %s", byteCount / magnitudes[i], suffixes[i])

Обычно при наличии правильного ответа с положительной оценкой его трудно догнать. На жаргоне Stack Overflow это называется проблемой самого быстрого стрелка на Западе. Но здесь у ответа было несколько недостатков, поэтому я всё равно надеялся его превзойти. По крайней мере, код с циклом можно значительно сократить.

Это ж алгебра, всё просто!

Тут меня осенило. Приставки кило-, мега-, гига-,… — ни что иное, как степени 1000 (или 1024 в стандарте МЭК), так что правильную приставку можно определить с помощью логарифма, а не цикла.

Основываясь на этой идее, я опубликовал следующее:

public static String humanReadableByteCount(long bytes, boolean si) < int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); >

Конечно, это не очень читабельно, и log/pow уступает по эффективности другим вариантам. Но никакого цикла и почти нет ветвлений, так что результат получился довольно красивым, на мой взгляд.

Математика тут нехитрая. Количество байт выражается как byteCount = 1000 s , где s представляет степень (в двоичной нотации база 1024.) Решение s дает s = log1000(byteCount).

В API нет простого выражения log1000, но мы можем выразить его в терминах натурального логарифма следующим образом s = log(byteCount) / log(1000). Затем преобразуем s в int, так что если у нас, например, более одного мегабайта (но не полный гигабайт), то в качестве единицы измерения будет использоваться МБ.

Получается, что если s = 1, то используется размерность килобайт, если s = 2 — мегабайт и так далее. Делим byteCount на 1000 s и шлёпаем соответствующую букву в префикс.

Оставалось только подождать и посмотреть, как сообщество воспримет ответ. Я подумать не мог, что этот фрагмент кода станет самым тиражирумым в истории Stack Overflow.

Исследование по атрибуции

Перенесёмся в 2018 год. Аспирант Себастьян Балтес публикует в научном журнале Empirical Software Engineering статью под названием «Использование и атрибуция сниппетов кода Stack Overflow в проектах GitHub». Тема его исследования — насколько соблюдается лицензия Stack Overflow CC BY-SA 3.0, то есть указывают ли авторы ссылки на Stack Overflow как источник кода.

Для анализа из дампа Stack Overflow были извлечены сниппеты кода и сопоставлены с кодом в публичных репозиториях GitHub. Цитата из реферата:

Представляем результаты крупномасштабного эмпирического исследования, анализирующего использование и атрибуцию нетривиальных фрагментов кода Java из ответов SO в публичных проектах GitHub (GH).

(Спойлер: нет, большинство программистов не соблюдает требования лицензии).

В статье есть такая таблица:

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

Быстрый поиск на GitHub действительно выдаёт тысячи репозиториев с кодом humanReadableByteCount .

Поиск этого фрагмента в своём репозитории:

$ git grep humanReadableByteCount

Забавная история, как я узнал об этом исследовании.

Себастьян нашёл совпадение в репозитории OpenJDK без какой-либо атрибуции, а лицензия OpenJDK не совместима с CC BY-SA 3.0. В списке рассылки jdk9-dev он спросил: это код Stack Overflow скопирован из OpenJDK или наоборот?

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

Почему бы не спросить напрямую у автора этого сообщения на SO (aioobe)? Он является участником OpenJDK и работал в Oracle, когда этот код появился в исходных репозиториях OpenJDK.

Oracle очень серьёзно относится к таким вопросам. Я знаю, что некоторые менеджеры вздохнули с облегчением, когда прочитали этот ответ и нашли «виновника».

Затем Себастьян написал мне, чтобы прояснить ситуацию, что я и сделал: этот код добавили ещё до моего прихода в Oracle и я не имею отношения к коммиту. С Oracle лучше не шутить. Через пару дней после открытия тикета этот код был удалён.

Баг

Держу пари, вы уже задумались об этом. Что же за ошибка в коде?

public static String humanReadableByteCount(long bytes, boolean si) < int unit = si ? 1000 : 1024; if (bytes < unit) return bytes + " B"; int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); >

После эксабайтов (10 18 ) идут зеттабайты (10 21 ). Может, действительно большое число выйдет за границы kMGTPE? Нет. Максимальное значение 2 63 -1 ≈ 9,2 × 10 18 , поэтому никакое значение никогда не выйдет за пределы экзабайт.

Может, путаница между единицами СИ и двоичной системой? Нет. В первой версии ответа была путаница, но её исправили довольно быстро.

Может, exp в конечном итоге обнуляется, вызывая сбой charAt(exp-1)? Тоже нет. Первый if-оператор охватывает этот случай. Значение exp всегда будет не менее 1.

Может, какая-то странная ошибка округления в выдаче? Ну вот наконец…

Много девяток

Решение работает до тех пор, пока не приблизится к 1 МБ. Когда в качестве входных данных задано 999 999 байт, результат (в режиме СИ) — «1000,0 kB» . Хотя 999 999 ближе к 1000 × 1000 1 , чем к 999,9 × 1000 1 , сигнификант 1000 запрещён спецификацией. Правильный результат — «1.0 MB» .

В своё оправдание могу сказать, что на момент написания такая ошибка была во всех 22 опубликованных ответах, включая Apache Commons и библиотеки Android.

Как это исправить? Прежде всего, отметим, что показатель степени (exp) должен измениться с ‘k’ на ‘M’, как только число байт ближе к 1 × 1,000 2 (1 МБ), чем к 999,9 × 1000 1 (999,9 k). Это происходит на 999 950. Точно так же следует переключиться с ‘M’ на ‘G’, когда мы проходим 999 950 000 и так далее.

Вычисляем этот порог и увеличиваем exp , если bytes больше:

if (bytes >= Math.pow(unit, exp) * (unit - 0.05)) exp++;

С этим изменением код работает хорошо до тех пор, пока количество байт не приблизится к 1 ЭБ.

Ещё больше девяток

При расчёте 999 949 999 999 999 999 код выдаёт 1000.0 PB , а правильный результат 999.9 PB . Математически код точен, так что же здесь происходит?

Теперь мы столкнулись с ограничениями double .

Введение в арифметику с плавающей запятой

Согласно спецификации IEEE 754, у близких к нулю значений с плавающей запятой очень плотное представление, а у больших значений — очень разреженное. На самом деле, половина всех значений находится между -1 и 1, а когда речь идёт о больших числах, значение размером Long.MAX_VALUE ничего не значит. В прямом смысле.

double l1 = Double.MAX_VALUE; double l2 = l1 - Long.MAX_VALUE; System.err.println(l1 == l2); // prints true

Подробнее см. «Биты значения с плавающей запятой».

Проблему представляют два вычисления:

  • Деление в аргументе String.format и
  • Порог для наращивания exp
Уменьшение промежуточных значений

Для решения первой проблемы можем уменьшить значение bytes до нужного диапазона, где точность лучше, и соответственно настроить exp . Конечный результат в любом случае округляется, поэтому неважно, что мы выбрасываем наименее значимые разряды.

if (exp > 4) < bytes /= unit; exp--; >
Настройка наименее значимых битов

Для решения второй проблемы нам важны наименее значимые биты (у 99994999. 9 и 99995000. 0 должны быть разные степени), поэтому придётся найти иное решение.

Сначала отметим, что существует 12 различных пороговых значений (по 6 для каждого режима), и только одно из них приводит к ошибке. Неправильный результат можно однозначно идентифицировать, потому что он заканчивается на D0016. Значит, можно исправить его напрямую.

long th = (long) (Math.pow(unit, exp) * (unit - 0.05)); if (exp < 6 && bytes >= th - ((th & 0xFFF) == 0xD00 ? 52 : 0)) exp++;

Поскольку в результатах с плавающей запятой мы полагаемся на определённые битовые шаблоны, то применяем модификатор strictfp для гарантии, что код работает независимо от аппаратного обеспечения.

Отрицательные значения на входе

Неясно, при каких обстоятельствах может иметь смысл отрицательное количество байт, но поскольку в Java нет беззнакового long , лучше обработать такой вариант. Прямо сейчас ввод вроде -10 000 выдаёт -10000 B .

long absBytes = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);

Выражение такое многословное, потому что -Long.MIN_VALUE == Long.MIN_VALUE . Теперь мы выполняем все вычисления exp , используя absBytes вместо bytes .

Окончательная версия

Вот окончательная версия кода, сокращённая и уплотнённая в духе оригинальной версии:

// From: https://programming.guide/the-worlds-most-copied-so-snippet.html public static strictfp String humanReadableByteCount(long bytes, boolean si) < int unit = si ? 1000 : 1024; long absBytes = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes); if (absBytes < unit) return bytes + " B"; int exp = (int) (Math.log(absBytes) / Math.log(unit)); long th = (long) (Math.pow(unit, exp) * (unit - 0.05)); if (exp < 6 && absBytes >= th - ((th & 0xfff) == 0xd00 ? 52 : 0)) exp++; String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); if (exp > 4) < bytes /= unit; exp -= 1; >return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); >

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

Для обновлённой версии продакшн-качества см. отдельную статью: «Форматирование размера байт в удобочитаемый формат».

Ключевые выводы

  • В ответах на Stack Overflow могут быть ошибки, даже если у них тысячи плюсиков.
  • Проверьте все граничные случаи, особенно в коде со Stack Overflow.
  • Арифметика с плавающей запятой сложна.
  • Обязательно указывайте правильную атрибуцию при копировании кода. Кто-то может вывести вас на чистую воду.
  • Java
  • округление
  • код-гольфинг
  • byteCount
  • humanReadableByteCount

7.1 Инструменты Git — Выбор ревизии

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

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

Выбор ревизии

Git позволяет различными способами указать коммиты или их диапазоны. Эти способы не всегда очевидны, но их полезно знать.

Одиночные ревизии

Конечно, вы можете ссылаться на коммит по его SHA-1 хешу, но существуют более удобные для человека способы. В данном разделе описываются различные способы обращения к одному коммиту.

Сокращённый SHA-1

Git достаточно умен, чтобы понять какой коммит имеется ввиду по нескольким первым символам его хеша, если указанная часть SHA-1 имеет в длину по крайней мере четыре символа и однозначна — то есть в текущем репозитории существует только один объект с таким частичным SHA-1.

Например, предположим, чтобы найти некоторый коммит, вы выполнили команду git log и нашли коммит, в которой добавили определённую функциональность:

$ git log commit 734713bc047d87bf7eac9674765ae793478c50d3 Author: Scott Chacon Date: Fri Jan 2 18:32:33 2009 -0800 Fix refs handling, add gc auto, update tests commit d921970aadf03b3cf0e71becdaab3147ba71cdef Merge: 1c002dd. 35cfb2b. Author: Scott Chacon Date: Thu Dec 11 15:08:43 2008 -0800 Merge commit 'phedders/rdocs' commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b Author: Scott Chacon Date: Thu Dec 11 14:58:32 2008 -0800 Add some blame and merge stuff

Предположим, что в нашем примере это коммит 1c002dd…​. . Если вы хотите выполнить для него git show , то следующие команды эквиваленты (предполагается, что сокращения однозначны):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b $ git show 1c002dd4b536e7479f $ git show 1c002d

Git может вычислить уникальные сокращения для ваших значений SHA-1. Если вы передадите опцию —abbrev-commit команде git log , в выводе будут использоваться сокращённые значения, сохраняющие уникальность; по умолчанию используется семь символов, но для сохранения уникальности SHA-1 могут использоваться более длинные значения.

$ git log --abbrev-commit --pretty=oneline ca82a6d Change the version number 085bb3b Remove unnecessary test code a11bef0 Initial commit

Обычно от восьми до десяти символов более чем достаточно для сохранения уникальности значений в проекте.

Например, в ядре Linux, который является довольно большим проектом с более чем 450 тыс. коммитов и 3.6 млн. объектов, отсутствуют объекты, чьи SHA-1 совпадают более чем в 11 первых символах.

Примечание
Небольшое замечание о SHA-1

Большинство людей в этом месте начинают беспокоиться о том, что будет, если у них в репозитории случайно появятся два объекта с одинаковыми значениями SHA-1. Что тогда?

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

Однако, вы должны осознавать, насколько маловероятен такой сценарий. Длина SHA-1 составляет 20 байт или 160 бит. Количество случайно хешированных объектов, необходимых для достижения 50% вероятности возникновения коллизии, равно примерно 2 80 . (формула для определения вероятности возникновения коллизии p = (n(n-1)/2) * (1/2^160)) . 2 80 — это 1.2 × 10 24 , или 1 миллион миллиардов миллиардов, что в 1200 раз больше количества песчинок на земле.

Приведём пример, чтобы дать вам представление, чего будет стоить получение коллизии SHA-1. Если бы все 6.5 миллиардов человек на Земле были программистами, и ежесекундно каждый из них производил количество кода, эквивалентное всей истории ядра Linux (3.6 миллиона Git-объектов), и отправлял его в один огромный Git репозитории, то потребовалось бы около 2 лет, пока этот репозиторий накопил бы количество объектов, достаточное для 50% вероятности возникновения SHA-1 коллизии. Более вероятно, что каждый член вашей команды в одну и туже ночь будет атакован и убит волками в несвязанных друг с другом происшествиях.

Если выделить на это несколько тысяч долларов вычислительной мощности, можно будет синтезировать два файла с одним и тем же хешем, что было доказано проектом https://shattered.io/ в феврале 2017 года. Git движется к использованию SHA256 в качестве алгоритма хеширования по умолчанию, который намного более устойчив к атакам с коллизиями и имеет код, помогающий смягчить эту атаку (хотя он не может полностью её устранить).

Ссылки на ветки

Для наиболее простого способа указать коммит требуется существование ветки, указывающей на этот коммит. Тогда вы можете использовать имя ветки в любой команде Git, которая ожидает коммит или значение SHA-1. Например, если вы хотите просмотреть последний коммит в ветке, то следующие команды эквивалентны (предполагается, что ветка topic1 указывает на коммит ca82a6d ):

$ git show ca82a6dff817ec66f44342007202690a93763949 $ git show topic1

Если вы хотите узнать SHA-1 объекта, на который указывает ветка, или увидеть к чему сводятся все примеры в терминах SHA-1, то вы можете воспользоваться служебной командой Git, называемой rev-parse . Служебные команды подробно рассмотрены в главе Git изнутри; в основном, команда rev-parse существует для низкоуровневых операций и не предназначена для ежедневного использования. Однако она может быть полезна, когда вам нужно увидеть, что в действительности происходит. Теперь вы можете выполнить rev-parse для вашей ветки.

$ git rev-parse topic1 ca82a6dff817ec66f44342007202690a93763949

RefLog-сокращения

Одна из вещей, которую Git делает в фоновом режиме, является ведение журнала ссылок, в котором сохраняется то, куда указывали HEAD и ветки за последние несколько месяцев.

Для просмотра этого журнала используется команда git reflog :

$ git reflog 734713b HEAD@: commit: Fix refs handling, add gc auto, update tests d921970 HEAD@: merge phedders/rdocs: Merge made by the 'recursive' strategy. 1c002dd HEAD@: commit: Add some blame and merge stuff 1c36188 HEAD@: rebase -i (squash): updating HEAD 95df984 HEAD@: commit: # This is a combination of two commits. 1c36188 HEAD@: rebase -i (squash): updating HEAD 7e05da5 HEAD@: rebase -i (pick): updating HEAD

Каждый раз когда по каким-то причинам изменяется вершина вашей ветки, Git сохраняет информацию об этом в эту временную историю. И вы можете указывать старые коммиты, используя эти данные. Например, чтобы посмотреть, куда ссылался указатель HEAD пять шагов назад, используйте ссылку @, которую можно увидеть в выводимых данных команды reflog:

$ git show HEAD@

Этот синтаксис используется и в случае, когда требуется посмотреть, в каком состоянии пребывала ветка некоторое время назад. В частности, чтобы увидеть где была ветка master вчера, следует написать:

$ git show master@

Вы увидите, что было на вершине ветки вчера. Такой способ работает только для данных, которые всё ещё содержатся в вашем журнале ссылок, поэтому вы не можете использовать её для коммитов, которые старше нескольких месяцев.

Для просмотра журнала ссылок в формате, похожем на вывод git log , вы можете выполнить git log -g :

$ git log -g master commit 734713bc047d87bf7eac9674765ae793478c50d3 Reflog: master@ (Scott Chacon ) Reflog message: commit: Fix refs handling, add gc auto, update tests Author: Scott Chacon Date: Fri Jan 2 18:32:33 2009 -0800 Fix refs handling, add gc auto, update tests commit d921970aadf03b3cf0e71becdaab3147ba71cdef Reflog: master@ (Scott Chacon ) Reflog message: merge phedders/rdocs: Merge made by recursive. Author: Scott Chacon Date: Thu Dec 11 15:08:43 2008 -0800 Merge commit 'phedders/rdocs'

Важно отметить, что информация в журнале ссылок строго локальная — это лог того, что вы делали в вашем репозитории. Ссылки не будут такими же в других копиях репозитория; а сразу после первоначального клонирования репозитория, у вас будет пустой журнал ссылок, так как никаких действий в вашем репозитории пока не производилось. Команда git show HEAD@ будет работать только если вы клонировали проект по крайней мере два месяца назад — если вы клонировали его пять минут назад, то не получите никаких результатов.

Воспринимайте reflog Git как историю командной строки

Если у вас есть опыт работы с UNIX или Linux, можете думать о reflog как об истории командной строки Git, которая подчеркивает, что то, что там есть, явно актуально только для вас и вашего «сеанса» и не имеет ничего общего с кем-либо ещё, кто может работать на той же машине.

Примечание
Экранирование фигурных скобок в PowerShell

При использовании PowerShell фигурные скобки, такие как < и >, являются специальными символами и должны быть экранированы. Вы можете экранировать их с помощью апострофа ` или поместить ссылку на коммит в кавычки:

$ git show HEAD@ # НЕ будет работать $ git show HEAD@` # OK $ git show "HEAD@" # OK

Ссылки на предков

Ещё один популярный способ указать коммит — это использовать его родословную. Если вы поместите ^ в конце ссылки, Git поймёт, что нужно использовать родителя этого коммита. Предположим, история вашего проекта выглядит следующим образом:

$ git log --pretty=format:'%h %s' --graph * 734713b Fix refs handling, add gc auto, update tests * d921970 Merge commit 'phedders/rdocs' |\ | * 35cfb2b Some rdoc changes * | 1c002dd Add some blame and merge stuff |/ * 1c36188 Ignore *.gem * 9b29157 Add open3_detach to gemspec file list

Для просмотра предыдущего коммита достаточно написать HEAD^ , что означает «родитель HEAD»:

$ git show HEAD^ commit d921970aadf03b3cf0e71becdaab3147ba71cdef Merge: 1c002dd. 35cfb2b. Author: Scott Chacon Date: Thu Dec 11 15:08:43 2008 -0800 Merge commit 'phedders/rdocs'

Примечание
Экранирование карета в Windows

В командной строке Windows ( cmd.exe ) ^ является специальным символом и требует другого обращения. Вы можете либо удвоить его, либо поместить ссылку на коммит в кавычки:

$ git show HEAD^ # НЕ будет работать в Windows $ git show HEAD^^ # OK $ git show "HEAD^" # OK

Также вы можете указать число после ^ — например, d921970^2 означает «второй родитель коммита d921970». Такой синтаксис полезен только для коммитов слияния, которые имеют больше одного родителя. Первым родителем является ветка, в которую вы выполняли слияние, а вторым — коммит в ветке, которую вы сливали:

$ git show d921970^ commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b Author: Scott Chacon Date: Thu Dec 11 14:58:32 2008 -0800 Add some blame and merge stuff $ git show d921970^2 commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548 Author: Paul Hedderly Date: Wed Dec 10 22:22:03 2008 +0000 Some rdoc changes

Второе важное обозначение для указания предков это символ тильда ~ . Он также соответствует ссылке на первого родителя, поэтому HEAD~ и HEAD^ эквивалентны. Различия становятся заметными, когда вы указываете число. HEAD~2 означает «первый родитель первого родителя» или «дедушка» — при этом происходит переход от заданного предка вглубь указанное число раз. К примеру, для показанной ранее истории, коммитом HEAD~3 будет:

$ git show HEAD~3 commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d Author: Tom Preston-Werner Date: Fri Nov 7 13:47:59 2008 -0500 Ignore *.gem

То же самое можно записать как HEAD~~~ , что также является первым родителем первого родителя первого родителя:

$ git show HEAD~~~ commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d Author: Tom Preston-Werner Date: Fri Nov 7 13:47:59 2008 -0500 Ignore *.gem

Вы также можете совмещать эти обозначения — можно получить второго родителя предыдущей ссылки (предполагается, что это коммит слияния) используя запись HEAD~3^2 , и так далее.

Диапазоны коммитов

Теперь вы умеете указывать отдельные коммиты, давайте посмотрим как указывать диапазоны коммитов. Это в частности полезно для управления вашими ветками — если у вас есть множество веток, вы можете использовать указание диапазонов коммитов для ответа на вопрос «Что было сделано в этой ветке, что я ещё не слил в основную ветку?»

Две точки

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

Пример истории для выбора диапазонов коммитов

Рисунок 136. Пример истории для выбора диапазонов коммитов

Вы хотите посмотреть что находится в вашей экспериментальной ветке, которая ещё не была слита в основную. Вы можете попросить Git отобразить в логе только такие коммиты, используя запись master..experiment — она означает «все коммиты, которые доступны из ветки experiment , но не доступны из ветки master ». Для краткости и наглядности в этих примерах вместо настоящего вывода лога мы будем использовать для коммитов их буквенные обозначения из диаграммы, располагая их в должном порядке:

$ git log master..experiment D C

С другой стороны, если вы хотите наоборот увидеть все коммиты ветки master , которых нет в ветке experiment , вы можете поменять имена веток в команде. При использовании записи experiment..master будут отображены все коммиты ветки master , недоступные из ветки experiment :

$ git log experiment..master F E

Это полезно если вы хотите сохранить ветку experiment в актуальном состоянии и просмотреть, какие изменения нужно в неё слить. Другое частое использование такого синтаксиса — просмотр того, что будет отправлено в удалённый репозиторий.

$ git log origin/master..HEAD

Такая команда покажет вам все коммиты вашей текущей ветки, которые отсутствуют в ветке master удалённого репозитория origin . Если вы выполните git push , находясь на ветке, отслеживающей origin/master , то коммиты, отображённые командой git log origin/master..HEAD , будут теми коммитами, которые отправятся на сервер. Вы также можете опустить одну из частей в такой записи, Git будет считать её равной HEAD . Например, вы можете получить такой же результат как в предыдущем примере, выполнив git log origin/master.. — Git подставит HEAD , если одна часть отсутствует.

Множественная выборка

Запись с двумя точками полезна как сокращение, но, возможно, вы захотите использовать более двух веток для указания нужной ревизии, например, для того, чтобы узнать какие коммиты присутствуют в любой из нескольких веток, но отсутствуют в ветке, в которой вы сейчас находитесь. Git позволяет сделать это, используя символ ^ или опцию —not , перед любой ссылкой, доступные коммиты из которой вы не хотите видеть. Таким образом, следующие три команды эквивалентны:

$ git log refA..refB $ git log ^refA refB $ git log refB --not refA

Этот синтаксис удобен, так как позволяет указывать в запросе более двух ссылок, чего не позволяет сделать синтаксис с двумя точками. Например, если вы хотите увидеть все коммиты, доступные из refA и refB , но не доступные из refC , вы можете использовать одну из следующих команд:

$ git log refA refB ^refC $ git log refA refB --not refC

Это делает систему запросов ревизий более мощной и должно помочь вам лучше понять, что содержится в вашей ветке.

Три точки

Последний основной способ выбора ревизий — это синтаксис с тремя точками, который обозначает все коммиты, доступные хотя бы из одной ссылки, но не из обеих сразу. Вспомните пример истории коммитов в Пример истории для выбора диапазонов коммитов. Если вы хотите узнать какие коммиты есть либо в ветке master , либо в experiment , но не в обеих сразу, вы можете выполнить:

$ git log master. experiment F E D C

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

В таких случаях с командой log часто используют опцию —left-right , которая отображает сторону диапазона, с которой был сделан каждый из коммитов. Это делает данную информацию более полезной:

$ git log --left-right master. experiment < F < E >D > C

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *