Pull to refresh

Comments 41

Спасибо, очень интересно!

Есть вопрос. Я склонировал npm и нашел этот коммит.

Вот его SHA1: 01f8f5befb33dd3e892d53fe9b9f27f7a9e69cb6
А вот работающая ссылка на GitHub с этим SHA1: github.com/npm/npm/commit/01f8f5befb33dd3e892d53fe9b9f27f7a9e69cb6

Как видно, ваш SHA1: 5ff786ae103161465d84ecdfdc5b0cfd8839eac8

Откуда взялся мой SHA1? GitHub понимает оба значения.
Второй sha — это как раз коммит, образовавшийся после rebase, копия коммита из топика. Его можно найти по содержимому или комментарию, если есть. Но иногда это бывает не так просто. BTW, а как вы его искали?
А как можно «утянуть к себе» объект из удаленного репозитория, если на него нет ссылок в виде веток/тэгов? Допустим если надо.
На stackoverflow проскальзывали утверждения, что без доступа к серверу — невозможно, гит не передает по сетке loose object'ы. Рекомендовали искать кого-нить у кого такой коммит есть локально. Но на 100% я не уверен. Ведь если есть доступ на запись — наверняка можно как-то соорудить для такого коммита ссылку и запушить с --force?
Я как-то интересовался этим вопросом. Мне объяснили, что запрет клонирования «потерянных» коммитов сделан намеренно в качестве меры безопасности. Предположим, кто-то случайно закоммитил и запушил скрипт с прописанными там логином-паролем к некоему сервису. Если этот кто-то быстренько откатит коммит и запушит с форсом, то несмотря на наличие на сервере прежнего коммита, его уже никто не увидит и не утащит.

P.S. Понятное дело, тут речь идёт конкретно о функциональности самого гита как VCS, а не о надстройках типа гитхаба, которые могут выдавать пользователю произвольный коммит по хэшу. Но даже там этот хэш ещё сначала надо откуда-то узнать.
Возможно, git clone --bare скопирует и loose-объекты.

Вообще, странно, что гитхаб не делает housekeeping. Ведро-битов делает с определенной периодичностью и однажды возникла необходимость перезалить репозиторий, убрав все старые коммиты, чтобы они не маячили в интерфейсе (git push --force не помог, новые коммиты налились наравне со старыми). Сейчас уже не вспомню подробностей, но с помощью бубна, plumbing-а и матюков удалось.
Возможно, git clone --bare скопирует и loose-объекты.
Нет, не склонирует. Я пробовал это.
Единственная ситуация, когда у меня склонировались висящие коммиты, это локальный клон.
Эта архитектурная особенность породила мутное высказывание, что Git отслеживает переименование по содержимому файла. При переименовании объект «коммит» будет содержать ссылку на объект «содержимое файла», но если содержимое не изменилось, то это будет ссылка на объект, уже имеющийся в хранилище.
Это не мутное высказывание, а вполне себе корректное. Если переименовать файл и немного отредактировать, то гит всё равно будет определять его как переименованный, хотя хэш уже другой. Можно даже настраивать граничное количество отличий, до которого файл будет считаться переименованным, а при превышении этого порога отобразится как пара операций «удаление старого файла + создание нового файла».
не будет, этот порог указывается при просмотре diff'ов что как бы намекает
Ну так больше, вроде бы, особо и негде.
С точки зрения внутренностей гита переименований как бы и вовсе нет. Но для удобства пользователей введён вот такой механизм «угадывания». Соответственно, и используется он только при взаимодействии с пользователем — в списках изменений.
Потому и написал — мутноватое высказывание :). Пока сам не прочел очень внимательно раздел книжки про internals — не мог понять, что все эти люди имели в виду.
ну так и svn храня патчи пользователю отдает полные файлы, но говорить что svn хранит полные файлы всех версий не корректно.
также и наоборот не корректно сказать что git хранит отличия между версиями файлов и вообще как-то отслеживает что меняются файлы
Это ещё одно место, когда «в действительности всё совсем не так, как на самом деле». Git, конечно же, не хранит копии всех файлов — иначе бы его репозитории были во много раз больше. Конечно «внутри себя» он использует delta-diff'ы. Но это — детали реализации, которые как бы никого не волнуют. Используются для уменьшения объёма репозитория и больше ни для чего — снаружи всё выглядит так, как если бы каждая версия файла хранилась как отдельная сущность. Никакой истории у файлов нет (история — это последовательность, возможно нелинейная, мгновенных состояний проекта). В SVN же есть понятие «история файла» есть и она активно используется в интерфейсе.
> Конечно «внутри себя» он использует delta-diff'ы
это делает очень не часто и в зависимости от внутренней логики git и то только в процессе запуска gc при формировании паков. и да это скрыто ото всех и никто, в том числе и все что есть в поставке гита, эти «дифы» не видит. Все работают с полными версиями.

> Никакой истории у файлов нет
я как бы этого и не утверждал
Не только. Во‐первых, merge. Во‐вторых, команды типа rebase, частично использующие тот же код. Mercurial и bazaar сохраняют в истории переименования/копирования именно для того, чтобы merge работал корректно. Git использует угадывание. У обоих вариантов есть достоинства и недостатки, но мне больше нравится то, что в mercurial я могу запротоколировать перемещение, даже если оно сопряжено с переписыванием значительного куска файла, а не то, что я могу забыть указать факт перемещения в git и merge всё равно сработает корректно.
Нифига это высказывание не корректное. Ибо высказывание с подстрокой «Git отслеживает переименования» априори неверно. Не отслеживает Git ничего! Отслеживание переименований вообще в его парадигму никак не вписывается — это не SVN!

Это типичный пример случая, когда «в действительности всё совсем не так, как на самом деле». Многим людям кажется, что Git отслеживает перемещения. И они могут многие годы жить с этим заблуждением. Но это совсем не так! Git восстанавливает перемещения с использованием переменных diff.renameLimit и/или merge.renameLimit когда вы используете команды git log или git diff. Немного особняком стоит команда git merge — тут переменные diff.renameLimit и merge.renameLimit могут-таки реально повлиять на результат, но, опять-таки, только на состояние файлов в дереве после объединения нескольких веток. Последующий git log будет заново рассчитывать переименования и может придти к совсем другим выводам.
Ибо высказывание с подстрокой «Git отслеживает переименования» априори неверно. Не отслеживает Git ничего!
Это всего лишь варианты трактовок слова «отслеживает». Его можно понять как «следит за переименованиями и сохраняет их в базе» — разумеется, это будет неправильно. Но «отслеживает» можно также применять в смысле «в процессе показа истории обрабатывает файлы, отслеживая слаборазличающиеся и маркируя их как скопированные/перемещённые».

Когда я писал своё сообщение, я подразумевал только второй вариант, и не подумал, что кто-то может воспринимать это слово в другой трактовке. Хотя, возможно, неподготовленному человеку именно «неправильный» вариант придёт на ум раньше. Так что слово технически корректное, но неудачное, это да.
А существуют ли адекватные DCVS или плагины к существующим, в которых для редактирования «некрасивой истории» в репозитории никаких деструкитвных действий не происходит (как при rebase в git), а добавляется только пачка сервисных коммитов, влияющих только на отображение?
UFO just landed and posted this here
Мне кажется, хотеть не «не принято», а «не имеет смысла». Git значительно менее расширяем: это mercurial архитектурно может позволить себе дополнения, предполагающие передачу собственных метаданных по сети или добавляющие аргументы к стандартным командам (и даже вставляющие их в вывод hg help). Для git я такого что‐то не помню.
У них просто «подход к жизни» отличается. В Mercurial главное — интерфейс, а как оно там устроено внутри — неважно (и может меняться со временем). Git же в своей сути — это вообще не система контроля версий. Это псевдофайловая система, которая может хранить миллионы связанных в иерархию снапшотов — и всё. Всё остальное — навешано «поверх» этой файловой системы. Новые команды могут добавляться и удаляться, команды могут возникать и исчезать, но единожды сделанный снапшот больше, конечно, меняться не может — отсюда и все следствия.

Тот факт, что там ничего изменить нельзя меня скорее радует и я вообще не могу себе представить как можно пользоваться DCVS без такого свойства. В случае с Git'ом условный Вася с условным Петей могуг говорить о коммите 5ff786ae103161465d84ecdfdc5b0cfd8839eac8 имея абсолютно уверенность о том, что они говорят об одном и том же объекте: у него совпадает содержимое всех файлов и вся история. Даже если они никогда в жизни до этого не сталкивались и одно и то же изменение (с тем же самым сопутствующим описанием) они сделали независимо! А как можно что-то обсуждать используя систему, где условный Вася и условный Петя могут глядеть на коммит с одной и той же меткой, но видя при этом разную историю или, ещё того хуже, разное содержимое? Тут уже централизация потребуется как ни крути, чтобы понять чья история «правильная»…
Кажется, вы что-то не то говорите. В Git'е сломать историю можно штатными средствами. Сделал rebase — и всё: «Вася и условный Петя могут видеть при этом разную историю или, ещё того хуже, разное содержимое». Причём можно ещё и весь репозиторий этим сломать, а потом лазить руками искать соответствие хеша коммиту… Хотя да, сами снепшоты при этом не меняются (и это правильно).

Мой вопрос был в том, есть ли какой инструмент, чтобы менять отображение, но чтобы история при этом никогда не менялась и однажды отправленный коммит был впечатан навеки так, что не выдерешь, как вы и описали. Чтобы можно было изменить её (истори) отображение навешивая коммиты сверху. Хочешь — отключаешь «вьюху» (аналогия с SQL) для дерева коммитов и видишь всё как есть. Хочешь — включаешь «вьюху» и видишь красивую историю, где показано не то, кто как случайно коммитил, забывая про разделение фич и адекватные комменты, находясь «в потоке» и не желая отвлекаться на весь этот гемор, а показана подправленная версия истории, где какие-то коммиты слиты в один, есть нормальные комменты, ветки по-человечески проименованы т.п.
В Git'е сломать историю можно штатными средствами.
Сломать — да, легко. Ну так против rm -rf ни одна DCVS не устоит. А вот изменить — нельзя. С помощь rebase и прочего вы порождаете новую, другую историю — и это хорошо заметно и спрятать это нельзя.

Хочешь — включаешь «вьюху» и видишь красивую историю, где показано не то, кто как случайно коммитил, забывая про разделение фич и адекватные комменты, находясь «в потоке» и не желая отвлекаться на весь этот гемор, а показана подправленная версия истории, где какие-то коммиты слиты в один, есть нормальные комменты, ветки по-человечески проименованы т.п.
Ну так создайте себе «красивую» историю в отдельной ветке, кто ж вам мешает? Если вы действительно «случайно коммитили, забывая про разделение фич», то вам придётся довольно сильно историю кромсать, чтобы что-то путное получилось. Поставьте в «реперной точке» ссылки на старую историю, или git tagами их пометьте. Так как git не отслеживает историю, то git diff вы можете сделать между двумя любыми ревизиями проекта, чего должно хватить, чтобы убедиться в том, что вы ничего не потеряли — чего вам ещё нужно?
Дополнения могут только добавлять метаданные, а никак не изменять их или изменять хэшируемое содержимое. Даже largefiles (который заменяет файлы‐хэши на хэшированное содержимое при некоторых условиях) под это попадает. В хэш git, кстати, включается дата, так что одно и то же изменение сделать практически невозможно.

И не забывайте про largefiles (точнее, git‐овые эквиваленты), submodules и прочие вещи, которые сохраняют в репозитории идентификатор, но не содержимое. Даже в git ни о какой абсолютной уверенности речи быть не может: Вася может иметь git-lfs и получить нормальное содержимое больших файлов, а Петя его не иметь и получить набор хэшей. Или забыть сделать git submodule update.

Также при использовании коммитов с любых хэшем конечной длины абсолютной уверенности быть не может никаким образом из‐за коллизий.

Хотя, конечно, у дополнений mercurial есть возможности по добавлению коммитов в экспортируемую/импортируемую историю, но, насколько я знаю, такое возможно только с использование дополнительного аргумента к push/pull (я имею ввиду Mercurial Queues со своим аргументом --mq). Помимо коммитов можно добавлять метаданные. И ещё можно запретить делать clone, если нет нужных возможностей: т.е. когда в случае с git отсутствие git-lfs означает, что вы успешно сделаете pull/clone, но checkout потерпит неудачу, оставив вас наедине с недосозданным рабочим деревом и хэшами в git log --patch, в случае с mercurial никакого pull/clone не будет, пока вы не включите largefiles. (Хотя хэши в log --patch будут и с largefiles, и с git-lfs, даже если они установлены.)
UFO just landed and posted this here
Всё равно изменить команды из /usr/libexec/git-core не получится, не трогая содержимое данного каталога (а потрогать его не дадут менеджеры пакетов; обходные пути есть, но не слишком удобны). А по метаданным вопрос: они хранят внешние к изменению метаданные? Т.е. те, что не учавствуют в расчёте хэша? Если да, то где? Я из «внешних метаданных» в git знаю только ссылки на объект в .git/refs/ / .git/packed-refs.
Правильно ли я понял, что он всё-таки меняет историю, а не управляет её отображением? Если так — то это не совсем то, что я имел в виду. История изменений не должна модифицироваться, должны только навешиваться на неё «фильтры», грубо говоря, с опцией «наследования» как в ООП (указание на слияние пачки коммитов в один и т.п.). Отключил фильтр отображения — история показывается «как есть». Включил — показывается красивая и подправленная этими дополнительными коммитами.
Evolve навешивает метаданные на изменения (которые управляют отображением и показывают, что вы с ними сделали (на какие изменения заменили или, может, вообще удалили их)), создаёт новые изменения (при rebase/graft, strip, конечно, ничего не создаст) и не даёт клиенту/серверу без явного запроса выдать «устаревшие» варианты, также уже имеющиеся «устаревшие» варианты на сервере/клиенте маскируются метаданными, если только они их уже не имеют.

Т.е. история меняется только путём добавления метаданных и их пересылки, но история «как есть» никому не передаётся и «фильтр отображения» можно поставить только туда, где это «как есть» есть.
UFO just landed and posted this here
Вспомнилась небольшая книга Think Like a Git. Там есть доходчивые пояснения, как работает Git. В частности и нечто подобное описывается.

  1. Коммиты выстраиваются в граф, по которому можно свободно перемещаться при наличии ссылок. Ссылки — это любые указатели на коммит: тэги, локальные и удалённые (remote) коммиты.
  2. Ссылки делают коммиты достижимыми (References makes commits reachable).
  3. Все недостижимые коммиты — не принадлежащие веткам и не помеченные тегами — спустя какое-то время удаляются из истории.

В данном случае, выходит, на Github можно просматривать недостижимые коммиты. А вот что такие объекты не передаются по сети не знал, хотя это логично — к чему таскать мусор.
Т.е. в результате rebase на коммите, имеющем tag, можно создать вечно-потерянный коммит? Т.е. он не будет виден ни в одной ветке, но из-за наличия tag GC его тоже никогда не удалит? Он же так может навсегда «зависнуть». Это грустно. Много разных шаманских скриптов и расширений вешают временные тэгит при rebase и некоторые могут забывать их удалять.
С точки зрения git это будет совсем не потерянный коммит. Какой же он потерянный, если на него указывает тег? :)
Хорошо этому git :) Только пользователь может и не знать, что в результате работы какого-нибудь шаманского скрипта, делающего rebase, у него «завис» один или несколько коммитов :) Кстати, не подскажите, какой-нибудь простой путь это проверять — посмотреть коммиты, которые не имеют ссылок или имеют ссылки только в виде тэгов?
Посмотреть loose objects:

git fsck --unreachable --no-reflogs

А вот с тегами не уверен. У fsck есть аргумент --tags, но что именно он делает — хз.
Ну, дальше уже понятно куда копать. Спасибо. Тут главное знать с чего начать :)
если вы натворили бед, то есть git reflog, это аналог лога транзакций в СУБД. тамже можете найти с какого коммиты вы начали делать ребейз и т.п.
С точки зрения GC и вообще многих частей git ссылки из refs/heads и refs/tags не отличаются никак. В том числе это частично касается git pull и push: вы можете затянуть/обновить/удалить тёг точно так же, как и ветку (частично — потому что ветки обрабатываются особым образом, особенно текущая ветка).
Sign up to leave a comment.

Articles