Достаточно Git-а, чтобы быть (менее) опасным

imageТы просто-напросто ненавидишь Git? Ты абсолютно счастлив с Mercurial (или, фу, с Subversion), но раз в месяц тебе приходится отважно сталкиваться с Git, потому что каждый, даже его чертова собака, теперь использует GitHub? Тебя терзают смутные подозрения, что половина всех команд Git на самом деле удалят всю твою работу навсегда, но ты не знаешь какие именно и не хочешь проводить три недели, углубляясь в документацию?

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

Я постараюсь излагать коротко, но также, чтобы это было потенциально полезно тем людям, кто вообще никогда не сталкивался с контролем версий, поэтому повсюду будет разбросан 101 совет. Не бойся! Я не думаю, что пользователи Mercurial понятия не имеют, что такое патч.

Что здесь вообще происходит?


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

Git — это DVCS, или «распределенная система контроля версий», где «система контроля версий» означает «запоминает историю твоих файлов», а «распределенная» означает «ты можешь делать это оффлайн». (Раньше фиксирование изменений вызывало немедленную загрузку твоих изменений на центральный сервер. Ты не мог сохранить код, если не мог подключиться к серверу. Верно?)

Git был изобретен Линусом Торвальдсом, злобным мужиком, который также принес нам ядро Linux. Linux — это огромный проект с очень длинной историей и он перерос VCS, которую использовал, поэтому Линус решил написать новую, т.к. он программист, а именно этим мы и занимаемся.

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

Итак, у Линуса есть «каноничная» копия кодовой базы, которую ты можешь называть «репозиторий», потому что это место для хранения разных вещей. Время от времени ты можешь скачивать свежую копию и приступать к написанию каких-нибудь сломанных Wi-Fi-драйверов или еще чего-нибудь, и это уже будет отличаться от того, с чего ты начал. Поэтому ты генерируешь патч с изменениями и отправляешь его в почтовую рассылку, а кто-то говорит «по-моему неплохо» и Линус применяет этот патч к его копии кодовой базы. Теперь каждый, кто собирается работать с этим кодом, увидит там и твою работу тоже.

Великий секрет к пониманию Git, который, я надеюсь, заставит широко раскрыться твои глаза и прозвучать «ааа» из твоего рта, заключается в следующем:

Git — это просто набор инструментов для рассылки патчей по почте.

Нет, серьезно. Есть всего где-то пять команд внутри поставки Git для этих определенных целей. Есть даже подразделы в документации: am, apply, format-patch, send-email, request-pull. Ты можешь прямо сейчас пойти в почтовую рассылку ядра Linux и увидеть, что до сих пор все так и делается, Git просто делает большую часть скучной работы. Даже man-страница Git описывает Git, как «тупой трекер содержимого».

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

Давай будем рассматривать модель Git, держа это в голове.

Коммиты


Коммит — это патч. Все. Он перечисляет некоторые изменения в некоторых файлах в формате «единого diff-а».

В нем также есть некоторые заголовки, которые подозрительно напоминают почтовые. Там есть автор, отметка времени и прочее.

Здесь и происходит вся магия. Запомни, патч выражает различия между двумя наборами файлов. (Давай назовем их «деревьями» — по аналогии с деревьями каталогов.) Итак, если ты отправишь мне патч по почте, я не многое смогу с ним сделать, пока мы не согласуем к чему я должен применить патч. Может будет полезно указать, скажем, «примени этот патч к ядру Linux». Может будет даже более полезно указать «примени этот патч к релизу 3.0.5 ядра Linux».

Git-коммит кодирует это в заголовке «parent», указывая, поверх какого коммита его нужно применить.

Эй, подожди-ка, ведь коммит — это всего лишь патч. Как применить патч к другому патчу? Ты можешь применить патч только к полному набору файлов (дереву). Но после этого ты получаешь новый набор файлов (дерево). Поэтому «коммит» также означает «состояние репозитория после применения этого патча».

Но небольшая рекурсивная проблема все еще остается. Если у тебя есть коммит C и он говорит, что его родитель — B… что ж, ты не знаешь как выглядит состояние репозитория в B, пока не применишь его, и поэтому тебе нужно смотреть на его родителя, верно?

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

  • Коммит C: Мой родитель — B. Добавь «three» в конец файла «numbers.txt».
  • Коммит B: Мой родитель — A. Добавь «two» в конец файла «numbers.txt».
  • Коммит A: Создай файл «numbers.txt», содержащий «one».

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

Итак, ты начинаешь с чистого листа. Затем ты применяешь патч A, который дает тебе one. Затем ты можешь применить патч B, который дает тебе one two. И наконец, ты можешь применить патч C, который дает тебе one two three — состояние кодовой базы для коммита C. (Git не проделывает это буквально каждый раз, конечно; там достаточно хитрое кэширование и всякое-такое. Но модель действует достаточно схожим образом.)

Документация Git стремится изображать историю слева направо, поэтому описанное выше выглядело бы так:

A---B---C

Та же идея, только написанная по-другому. Имеет немного больше смысла, если ты представишь стрелки: A → B → C.

В реальности коммиты обозначаются не буквами, а хэшами, которые выглядят как 8edc525cd8dc9e81b5fbeb297c44dd513ba5518e, но обычно сокращаются до 8edc52. Ты можешь подумать, что они называются «хэшами», потому что это длинные шестнадцатеричные строки, которые выглядят как хэши SHA-1. В общем, да, но также они буквально являются SHA-1-хэшами патча, включая заголовки. (И т.к. родитель — это один из заголовков, то хэш включает хэш родителя, который включает хэш его родителя и т.д. Это длинная цепочка хэшей до самого начала. Прямо как в Bitcoin!)

Замечательное свойство такого хэширования в том, что отдельный коммит не может быть изменен. Ты не можешь просто вернуться назад и по тихому вбить строчку в патч A, потому что это изменит его хэш и B не будет указывать на измененный A. Если ты захочешь обновить родителя B, то это изменит его хэш и он потеряется. Как только у тебя будет хэш коммита, можешь быть абсолютно уверен, что его история неизменна.

Деревья


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

Тебе очень редко придется беспокоиться о деревьях — после многих лет использования Git я думал о деревьях примерно дважды. Это всего лишь деталь реализации. Я заметил их только потому, что документация обратила на них мое внимание, и это приятно, когда ты понимаешь о чем, черт возьми, тебе рассказывает документация. Они появляются в двух (практических) местах документации.

  1. Некоторые команды описаны, как принимающие «древовидный» аргумент, например, использование git checkout для работы с отдельными файлами. Это всего лишь означает «что-то, из чего Git может извлечь дерево». Т.к. у каждого коммита есть дерево, ты можешь просто использовать коммит в качестве аргумента.
  2. Существует множество ссылок на «рабочее дерево». Это просто дерево, в котором ты работаешь, т.е. актуальная копия кодовой базы, которая расположена у тебя на винте.

И это все, что тебе нужно знать о деревьях!

Ветки


Если ты использовал Mercurial, забудь о ветках Mercurial. Я не знаю как они работают, но пользователи Mercurial рассказывали мне, что это такая боль в заднице, что никто на самом деле их больше не использует.

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

Что ж, нет проблем. Когда они впервые скачивают код, они могут привязать его к директории под названием master (потому что это мастер-копия). Затем, когда они приступают к работе над своим драйвером, они могут все это полностью скопировать в директорию под названием ужасный-драйвер-broadcom. Чтобы сгенерировать патч, им нужно просто получить разницу между этими двумя директориями.

Это и есть ветки Git в двух словах.

Заметь, что при таком подходе, никто не знает и никого не волнуют имена твоих веток. В конце концов, ты не отправляешь кому-то другому целую директорию; ты просто отправляешь им некоторые патчи. Патчи не содержат имена веток; они только знают своих родителей.

Более технически, ветка — это только имя, которое указывает на какой-то коммит. (Буквально, ничего более. Ветка foo — это 41-байтный текстовый файл, содержащий хэш коммита.) Однако, ветка имеет особое свойство, при котором, если ты делаешь новый коммит, пока находишься в данной ветке, имя ветки начнет указывать на этот новый коммит. Еще раз, это работает как учебный пример: если ты делаешь какую-то работу или применяешь патч в своей директории ужасный-драйвер-broadcom, очевидно, что новое содержимое директории будет отражать новые изменения.

Вот почему о Git говорят, что у него «дешевое локальное ветвление». Оно дешевое, потому что ветка — это не более, чем имя; оно локальное, потому что тебя не заставляют синхронизировать имена твоих веток с кем-то еще.

Ветки добавляют новую возможность в нашу модель: теперь истории не обязательно быть линейной. Два разных патча могут иметь одного и того же родителя! Разумеется, тебе в действительности не нужны для этого ветки — если два человека работают с ядром Linux и оба делают изменения, они оба производят патчи с одинаковым родителем. Говоря о котором…

Удаленные репозитории


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

Когда ты впервые клонируешь репозиторий, место, из которого ты склонировал, обозначается как удаленный репозиторий под названием «origin», потому что это оригинал твоего кода. Ничоси.

Ты также получишь все ветки оригинала. Ну. Вроде того. Имена веток — локальны, запомни. Если у твоего оригинала есть ветка с именем foo, Git создаст для тебя ветку с именем origin/foo (называемую «удаленно-отслеживаемой» веткой). А т.к. ты ее не создавал, то по-умолчанию она не отображается в git branch.

В любом случае, тебе обычно не нужно работать с удаленными ветками напрямую.

Слияние


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

Так что, теперь у тебя: A → B → C

А у Линуса: A → B → D → E → F

Или, если рисовать в духе документации Git, где время течет слева направо:

      C            ужасный-драйвер-broadcom
     /
A---B---D---E---F  origin/master

Но, эй, нет проблем. Ядро Linux — это огромный проект, поэтому есть шансы, что ни один из этих патчей не касался тех же файлов, что и ты.

Если бы мы рассылали патчи по почте, мы бы могли просто сказать: плевать, просто примени C поверх F, даже если он говорит, что находится поверх B. Но в модели Git коммит обозначается своим хэшем, который включает его родителя. Изменение родителя потребует создание нового, отличающегося коммита, с другим хэшем.

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

      C-----------.    ужасный-драйвер-broadcom
     /             \
A---B---D---E---F---G  origin/master

Если никакие изменения с любой стороны не противоречат друг-другу, то это «простое» слияние. Т.к. ничего нового на самом деле не изменилось, то патч в G — пустой; он присутствует только для склейки C и F вместе.

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

Тэги


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

Также, тэги (чаще всего) глобальны, не ограничены пространством имен, как ветки. Наконец, они не меняются, поэтому обычно подразумевается, что каждому следует согласиться с тем, на что они указывают.



Понятненько. Это круто. Но как мне сделать хоть что-то?


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

Добудь немного кода


git clone github.com/funny_guy/just_for_lulz_code вывалит смешной код этого весельчака в новую директорию just_for_lulz_code.

Когда ты захочешь обновить его, ты можешь вызвать команду git pull origin master, которая получит все изменения и попытается слить их в твою текущую ветку. Если ты ничего не менял, то твое рабочее состояние просто перейдет в актуальное.

Если у тебя устаревшее рабочее состояние репозитория и ты не помнишь делал ли ты что-либо, ты можешь выполнить команду git pull --ff-only origin master, которая сделает что-либо только в том случае, если обновление будет «прямой перемоткой». Это всего лишь означает, что твоя сторона не делала никаких коммитов и никаких слияний не требуется. Другими словами, если у тебя состояние репозитория A, а у оригинала A → B → C, то это будет прямой перемоткой, потому что Git необходимо просто нарастить еще больше коммитов прямо поверх тех, что у тебя уже есть.

Посмотри содержимое


git log покажет тебе лог. Формат немного многословен и не очень подходит для беглого просмотра истории.

git log --oneline --graph --decorate намного приятнее для просмотра. Ты также можешь установить tig, который делает в основном то же самое, но ты сможешь использовать Enter на коммите, чтобы увидеть различия на месте.

git log --follow показывает тебе лог изменений, которые коснулись только конкретного файла (или директории). --follow означает - отслеживать историю файла, включая переименования, но это работает только для одного файла.

git show показывает тебе патч, внесенный коммитом. git show : показывает тебе состояние файла для конкретного коммита.


Просто используй эту чертову штуку, чтобы сделать этот чертов патч для этого чертова проекта


git status
рассказывает тебе о текущем состоянии твоей кодовой базы: в какой ветке ты находишься, какие изменения ты сделал и т.д.

git branch создает новую ветку, основанную на коммите, в котором ты работаешь, но не переключается на нее. Вместо этого тебе может понадобиться команда наподобие git checkout -b origin/master, которая создает новую ветку, основанную на origin/master, а также переключается на нее.

git checkout устанавливает текущую ветку и переключается в соответствующее состояние кодовой базы. Ты также можешь перейти в удаленную ветку, в тэг или в конкретный коммит, но текущая ветка будет покинута и ты будешь получать предупреждения о наличии "оторванной HEAD". Это буквально означает, что HEAD (специальное имя, которое всегда указывает на то, с чем ты работаешь) не указывает на ветку, и если ты делаешь новые коммиты, у них не будет ничего, указывающее на них и они могут леко потеряться.

git add говорит Git о новых файлах, созданных тобой, которые нужны тебе в следующем коммите.

git rm говорит Git, что ты собираешься удалить файл, а так же удаляет его физически. (Это всегда обратимо. Git отклонит операцию, если файл был изменен. Также ты можешь просто удалить файл командой rm, а git commit -a зафиксирует это.)

git mv говорит Git, что ты переименовываешь файл. (Заметь, что Git в действительности не хранит переименования; он догадывается на лету, был ли файл переименован.)

git commit -a откроет текстовый редактор, для запроса описания коммита, затем создаст коммит из всех сделанных изменений всех файлов, известных Git.

Кое-что в модели Git я еще не затронул: там есть одна вещь, называемая "index", или "staging area", или иногда "cache". (Я не знаю зачем ей нужно столько имен.) Это те изменения, которые ты собираешься зафиксировать. Когда ты используешь git add и компанию, любые изменения файла (или все содержимое целиком, если это новый файл) формируются и отображаются в своих собственных секциях в git status. Несформированные изменения перечисляются под ними. Если ты используешь простой git commit без -a, то только сформированные изменения станут частью коммита. Иногда это бывает довольно полезно, потому что позволяет тебе проводить кучу исследовательской работы, а затем упаковывать ее в различные коммиты для будущих археологов. (Если у тебя разыгралось воображение, то рассмотри git add -p.) Но ты можешь просто использовать git commit -a, когда захочешь. Черт, да тебе даже не нужно git add; ты можешь просто передавать список файлов в git commit.

Понятненько. Теперь, как мне работать где-угодно?


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

git push отправит твою ветку в ветку с тем же именем в удаленном репозитории. Если ты используешь форк с GitHub, тогда у тебя вероятно есть единственный удаленный репозиторий под названием "origin", который и является твоим форком, и ты, вероятно, просто работаешь в master ветке. Тогда ты можешь сделать git push origin master и все будет в порядке.

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

Конфликты слияния


Если ты делаешь слияние, или отправку изменений, или (упаси Господи) перемещение, возможно твои изменения будут конфликтовать с чьими-то чужими. Git остановит слияние с сообщением "Автоматическое слияние не удалось; ошибка бла-бла-бла". Если ты посмотришь в git status, ты увидишь новую секцию для конфликтующих фалов. Тебе необходимо это исправить для завершения слияния или выполнения множества других реальных задач.

Открой конфликтующий файл и ты увидишь что-то вроде этого:

<<<<<<< HEAD
что-то, что ты изменил
=======
что-то, что изменил кто-то другой
>>>>>>> origin/master

(Стиль отображения конфликтов diff3 может немного улучшить ситуацию; смотри секцию настроек ниже.)

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

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

Если тебе не повезло, то кто-то провел большой рефакторинг, пока ты исправлял маленький баг, и теперь конфликтует весь файл, а ты окончательно попал. Ты можешь выполнить git merge --abort, чтобы отменить слияние, создать новую ветку, основанную на текущей ветке master, и повторить свои изменения вручную.

Несколько примечаний:

  • Дважды проверяй, что ты действительно исправил все конфликты. Git НЕ БУДЕТ препятствовать тебе фиксировать отметки о конфликте!
  • Иногда, конфликт - это когда одна сторона отредактировала файл, а другая сторона удалила этот файл. Когда это происходит, Git расскажет тебе кто произвел удаление. Я чаще всего сталкиваюсь с этим, когда использую автоматическое форматирование, или рефакторинг, или еще что-то, в этом случае мне на самом деле плевать на файл, который был удален; если это тот случай, ты можешь просто удалить его через git rm.
  • Есть полу-интерактивная команда git mergetool, которую ты можешь использовать в ходе конфликта, и которая откроет твою программу разрешения слияний для каждого конфликтующего файла. В моем случае это vimdiff, использование которой у меня никогда не входило в привычку, поэтому я не использую ее слишком часто. В твоем случае это может отличаться.


Боже-божечки мои! Что я наделал?!


Ты скопипастил вызов git какого-то придурка на Stack Overflow и теперь все сломано. Не паникуй! И тем более не копипасть проверенное решение от какого-то другого придурка.

Если твоя рабочая копия или индекс окончательно навернулись, ты можешь использовать команду git reset --hard, чтобы отменить все свои незафиксированные изменения. Но не используй ее необдуманно, поскольку это, естественно, деструктивная операция.

Если ты делал какую-то интерактивную многоступенчатую вещь, вроде git rebase или git cherry-pick и все пошло ужасно неверно, git status укажет тебе на это, а, например, git rebase --abort гарантированно вернет тебя туда, откуда ты начал.

Если ты думаешь, что каким-то образом потерял коммиты, ты можешь найти их в git reflog.

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

И еще, немного синтаксического барахла


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

HEAD - это что-то вроде специального имени ветки, которое просто ссылается на то, с чем ты работаешь прямо сейчас.

Есть целая куча синтаксисов для указания коммитов и диапазонов коммитов. Ты можешь просмотреть man gitrevisions на досуге. Наиболее полезные это:

  • foo^ - это (первый) родитель foo. Чаще всего используется как HEAD^. Заметь, что ^ - это специальный символ во многих оболочках и может понадобиться экранирование.
  • foo..bar - это диапазон и обозначает все, что после foo, вплоть до bar включительно.

Есть еще больше в man gitrevisions, но 80% из этого я никогда не использовал, если честно.

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


Полезные настройки


У меня немного в моем .gitconfig, но там есть несколько моих любимых вещей, может тебе они тоже понравятся. Если ты очень активно используешь Git, то может быть полезным пролистать man git-config, какой-нибудь из множества представленных вариантов его настройки может относиться к твоей проблеме.

Ты можешь запросить свою конфигурацию Git с помощью git config foo.bar.baz. Ты также можешь редактировать ее с помощью git config --global foo.bar.baz value, где параметр --global изменит твой ~/.gitconfig файл (который применяется к любому репозиторию, с которым ты работаешь), а его пропуск изменит .git/config (который применяется только к текущему репозиторию).

Или ты можешь крякнуть ~/.gitconfig, открыв его в текстовом редакторе, потому что это чертов INI-файл, в общем, не бином Ньютона. Давай представим, что делаем это вместо команд.

Прежде, чем ты сделаешь ЧТО-ЛИБО, настрой свои имя и почту


Как мы знаем, каждый коммит Git содержит имя и почту, прикрепленные к нему, потому что Git был разработан людьми, которые буквально не могут представить себе никакой рабочий процесс, не сосредоточенный на почте. (Да, это означает, что адрес твоей почты на GitHub фактически публичен, даже если он явно не показан на веб-сайте.)

Если ты не укажешь Git свое имя, то ему придется гадать, а гадает он плохо. Он возьмет твое имя из поля "настоящее имя" в /etc/passwd (что может быть верным), а твою почту он возьмет из твоего логина плюс имени хоста твоего компьютера (что, конечно, полная бессмыслица, если только ты не на университетском сервере и это не 1983 год). И ты не сможешь исправить их задним числом, потому что они являются частью коммитов, а коммиты - неизменны.

Поэтому первые три строчки твоего .gitconfig должны исправить эту проблему:

[user]
    name = Eevee (Alex Munroe)
    email = eevee.git@veekun.com

Легкотня.

Стандартные цвета - это мусор, вселяющий ужас


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

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

Что меня встревожило и показалось ужасающим. Пожалуйста, просто доверься мне, когда я говорю, что тебе абсолютно необходимо тупо вставить этот блок определения цветов в свой .gitconfig.

[color "branch"]
    current = yellow reverse
    local = yellow
    remote = green
[color "diff"]
    meta = yellow bold
    frag = magenta bold
    old = red bold
    new = green bold
[color "status"]
    added = yellow
    changed = green
    untracked = cyan

Стиль отображения конфликтов


Единственная действительно стоящая вещь в моем .gitconfig вот эта:

[merge]
    conflictstyle = diff3

Обычно, конфликт слияния выглядит так:

<<<<<<< HEAD
то, на что ты поменял
=======
то, на что они поменяли
>>>>>>> master

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

Введи diff3, который меняет отображение конфликтов слияний так:

<<<<<<< HEAD
то, на что ты поменял
|||||||
то, что было изначально
=======
то, на что они поменяли
>>>>>>> master

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


Некоторые допущения, которые ты можешь, но не должен допускать


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

Скорее, Git - это странная файловая система и у нее есть набор инструментов, типа rm и ls. Чем на более низкий уровень ты спускаешься, тем меньше Git будет предполагать о том, что ты пытаешься сделать, и тем меньше будет пытаться тебя остановить от проделывания чего-то странного. Если ты почерпнул только одну вещь из этой статьи, пусть это будет следующее: Git был спроектирован для тех людей, которые уже поняли его на 100% - для людей, которые его написали. В этом плане сейчас становится лучше, но это причина множества его острых углов.

Ради наставления тебя на путь истинный, вот несколько допущений, которые ты уже мог, но не должен был делать:

  • Коммит не обязан иметь одного родителя. У него их может быть двое (если это слияние). Или трое, или больше (если это "осьминожное" слияние). Или ноль (если это первоначальный коммит).
  • У тебя может быть удаленный репозиторий, у которого ноль общих коммитов с твоим репозиторием. Нет ничего строго предписывающего двум репозиториям содержать "одинаковую" кодовую базу или заставляющего их никогда не взаимодействовать. Просто это обычно не так полезно. (Один возможный способ использования: я слил два проекта в один репозиторий без потери какой-либо истории, через добавление одного, как удаленного репозитория другого и просто слияния их историй вместе.)
  • Похожим образом у тебя может быть две ветки в том же самом репозитории, у которых ноль общих коммитов. (Что означает, что у тебя может быть более одного первоначального коммита!) Это то, как GitHub хранит "страницы GitHub": они находятся на отдельной ветке gh-pages внутри твоего репозитория, ведя совершенно независимую историю.
  • Коммиты не знают на какой ветке они были созданы. Ветка указывает на отдельный коммит; коммит никогда не указывает на ветку. Хотя, в большинстве практических случаев ты можешь достаточно верно это предположить.
  • Git отслеживает файлы, а не директории. Ты не можешь хранить пустую директорию в Git. Обычной практикой является хранения файла нулевого размера с имененм .keep или что-то еще в директории и фиксирование этого файла.
  • Документация не обязательно перечисляет опции, или формы команд, или огромное множество всего остального в порядке полезности. Например, наиболее фундаментальная команда это, вероятно, git commit, а третья опция в документации - это -C, выполняющая некую странную форму слияния, которую я сомневаюсь, что когда-либо использовал. Опция -m, которая позволяет тебе создавать описание коммита, появляется лишь на шестнадцатом месте.




Револьвер в сапоге, на всякий случай


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

Поэтому, вот несколько опасных вещей и то, как их безопасно использовать, или, по крайней мере, как их использовать с наименьшим риском.

git rm


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

git checkout


git checkout переключает ветки, но на более фундаментальном уровне то, что она делает - это вытаскивает файлы. Ты можешь использовать ее как git checkout [commit] -- <files...>, чтобы вытащить некоторые файлы конкретного коммита. По-умолчанию это относится к твоей текущей ветке, поэтому способом отменить изменения, которые ты сделал в файле (но еще не зафиксировал), является git checkout -- .

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

Ты можешь захотеть передать опцию -p
, которая интерактивно покажет тебе откат каждой отдельной части каждого файла. (Различные команды принимают опцию -p, включая git add, которая дает возможность делать различные изменения в отдельный файл и фиксировать только некоторые из них. Довольно удобно.)

git reset


"Reset" - это странная команда. Обычно она регулирует состояние твоей текущей ветки, индекса и твоего рабочего дерева.

Опасная часть это git reset --hard <files...>, которая отменит твою работу без предупреждений, прямо как git checkout. Здесь нет какой-либо "проверочной" опции. Будь очень осторожен с этим и трижды проверь, что у тебя нет ничего, что ты хотел бы сначала сохранить.

Более безопасный вариант - это git stash, которая запихнет все твои незафиксированные изменения в некий временный псевдо-коммит, не привязанный к твоей ветке. Ты можешь увидеть их, используя git stash list, и если ты поймешь, что хочешь оставить что-то из этой работы, ты можешь заново применить спрятанный патч с помощью git stash apply.

git rebase


Мне плевать, что говорят другие. Не используй ничего, что содержит "rebase", пока ты не понимаешь, что именно ты делаешь.

"Rebase" - для редактирования истории. Но ты не можешь редактировать историю, по причине ее полного хэширования. Вместо этого git rebase создает новую историю.

Скажем, у тебя есть A → B → C, где C - это твой собственный коммит, а B - это самый последний коммит в origin/master. Ты отправляешь изменения и... О, нет! Там уже есть новый коммит D на сервере. Поэтому ты получаешь следующее:

      .---C  master
     /
A---B---D    origin/master

Ты бы мог сделать слияние здесь... или ты бы мог сделать перемещение. Фундаментально, "перемещение" означает пересоздание коммита с другим родителем. Git возьмет патч в C, применит его поверх D, исправит все номера строк и попросит тебя разрешить все конфликты (прямо как в слиянии), а потом создаст новый коммит из результата. Это не может быть до сих пор коммит C, потому что родитель является частью хэша коммита, а родитель изменился. Вместо этого ты получишь коммит C'. (Новый хэш не обязательно похож как-либо на старый; апостроф, произносимый как "штрих", это соглашение, заимствованное из математики.)

Поэтому теперь у тебя:

      .---C
     /
A---B---D         origin/master
         \
          .---C'  master

Твой коммит должен был быть основан на B, но ты переписал его, чтобы он был основан на D. Следовательно, он перемещен. Я полагаю.

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

Заметь, что C до сих пор присутствует, но у него больше нет имени. Git сохраняет висящие коммиты вроде этого около 30 дней (видно в git reflog), просто на случай, если ты совершил ошибку, и удаляет их в ходе сборки мусора.

Перемещение может быть очень разрушительным, и не должно легко выполняться. Определенно, никогда не перемещай коммиты, которые ты уже так или иначе опубликовал - если у кого-то еще работа основана на твоем оригинальном коммите C, то обновление их работы так, чтобы она основывалась вместо этого на C', становится огромной болью в заднице. А если они этого не делают, то ты можешь остаться с обоими C и C' в своей истории, или они могут конфликтовать друг с другом, или кто его знает что еще. Я повидал злоупотребление git rebase, превратившее линейную ветку с четырьмя коммитами в запутанное месиво из порядка пятнадцати коммитов, все слитые вперемешку с копиями друг друга.

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

Если ты решил поэкспериментировать с перемещением, одно последнее предупреждение. В слиянии твоя ветка "наша", а чужая ветка - "их". Но в перемещении все наоборот - ты начинаешь с чужой ветки и заново добавляешь свои собственные коммиты поверх нее, даже если ты думал, что перемещал свою текущую ветку. Поэтому с точки зрения Git, твоя ветка "их", а чужая ветка - "наша"! Это влияет на принудительное разрешение с помощью команды git checkout --ours, она обходит все патчи в отметках о конфликте и инвертирует "их" в "нас", когда описывает конфликты в git status. Еще одна причина не производить перемещение до тех пор, пока ты абсолютно не уверен, что ты понимаешь, что происходит!

Если ты делаешь лажу во время перемещения, ты всегда можешь выполнить git rebase --abort. Или, если перемещение уже закончено, ты можешь сослаться на старую версию ветки с помощью специального синтаксиса имяветки@{1}, который означает "куда указывала имяветки, перед тем, как была изменена в последний раз". Тебе следует использовать git reset --hard чтобы заставить ветку вернуться, хотя, ой, фу.

--force


Обычно появляется как аргумет для git push после перемещения. Будь супер-пупер осторожен, т.к. он вслепую переписывает что бы то ни было на удаленном репозитории. Если ты читаешь эту статью, у тебя, вероятно, нет хорошего повода для принудительной отправки изменений. А если ты думаешь, что есть, то, вероятно, все еще нет, потому что в Git 2.0 есть аргумент --force-with-lease, который, по крайней мере, защищает от ситуации гонки.

Сохранение паролей, больших файлов и т.д.


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

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

Тебе действительно не захочется иметь необходимость проделывать такое. Не только потому что это огромная боль, но и потому что это требует координации со всеми остальными, кто использует твой репозиторий - после всего, у них до сих пор останутся копии оригинальной истории, от которой им нужно будет избавиться, по-хорошему. Или ты закончишь тем, что старая история сольется обратно с новой историей!

Забывая, что ты в середине чего-то


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

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

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


Вот и все, что у меня есть


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

Несмотря на это, я надеюсь, что что-то из этого будет полезным или, по крайней мере, сделает другие ресурсы по Git более понятными!

Если ты просто умираешь от нетерпения узнать больше о Git, интернет переполнен другими людьми, пытающимися рассказать о нем. Отсылаю тебя к списку статей от GitHub.


P.S.


Автор оригинальной статьи - Алекс Манро (Alex Munroe aka Eevee).
Автор перевода - Indexator.

Материал распространяется под лицензией CC-BY.

Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 365
  • +22
    Коммит — это патч. Все. Он перечисляет некоторые изменения в некоторых файлах в формате «единого diff-а». [...] История Git — это очень длинная цепочка инструкций для пересоздания кодовой базы с нуля, шаг за шагом. Представь это как стопку патчей, [...] В общем, да, но также они буквально являются SHA-1-хэшами патча, включая заголовки.

    Эм, но… нет.

    The major difference between Git and any other VCS (Subversion and friends included) is the way Git thinks about its data. Conceptually, most other systems store information as a list of file-based changes. [...] Git doesn’t think of or store its data this way. Instead, Git thinks of its data more like a set of snapshots of a miniature filesystem. Every time you commit, or save the state of your project in Git, it basically takes a picture of what all your files look like at that moment and stores a reference to that snapshot.


    (http://git-scm.com/book/en/v2/Getting-Started-Git-Basics)

    Иными словами, каждый коммит в гите — это дерево блобов с полным содержимым файлов, а не изменений.
    • –6
      Это только в «представлении». В паках гита хранятся диффы.
      • +11
        Вот только это не исторические диффы, а просто дельта-компрессия блобов.

        stackoverflow.com/a/8198276/1105881
        • +5
          Причём что интересно, дельта-компрессия может быть даже не от конкретной предыдущей версии данного файла, а от чего-угодно, что покажется гиту похожим на новую версию файла. К примеру в первой ревизии у нас есть файл 1.txt с содержимым «Вася» и файл 2.txt с содержимым «Петя». Теперь мы меняем содержимое файла 1.txt на что-то типа «Пета» — так вот в гите это может сохраниться как «файл 1.txt второй ревизии — это файл 2.txt из первой ревизии с 4-ым байтом 'а' вместо 'я' ».
          • 0
            Да, я именно это и имел в виду. Только файлы адресуются не по ревизиям, а напрямую по хэшам.
            • 0
              Дополнение: именно так git и определяет, что в комите файл был скопирован или переименовался и даже показывает процент совпадения, что невероятно удобно.
              • +3
                И невероятно НЕудобно, когда промахивается, а явно, в отличие от мерка, переименованный/перемещенный файл указать нельзя.
          • +4
            Увы. вы, похоже, совершенно неправильно понимаете гит и как он работает. Каждый коммит — это определенное состояние рабочей директории. Все дифы (патчи) которые возникают — всегда результат сравнения разных состояний рабочих директорий будь то merge, rebase или просто diff.. И они даже не кешируются, а каждый раз высчитываются. Именно поэтому в гите не рекомендуется хранить больших файлов — даже при малом изменении будет храниться новая полная копия файла. Другое дело, что хранение рабочих директорий устроено таким образом, что дифы считаются феерически быстро.

            Вы уверены, что хотите этому учить читателя?
          • 0
            Позвольте не согласиться с вашим высказыванием, как с валидной критикой данного поста.

            С технической точки зрения, вы, скорее всего, правы насчёт природы коммитов — я не влезал в тонкости реализации.

            Однако, семантика использования (за исключением rebase) больше всего напоминает набор патчей.

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

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

            Особенно наглядно это работает, когда, например, кроме почты, действительно нет общедоступного канала для обмена изменениями. Тут вы действительно оперируете всегда патчами (коммиты) и патч сиквенсами (ветки). При этом, тот же gitflow, вполне себе спокойно при таком канале работается, практически без лишних телодвижений, если юниксовые мэилбоксы использовать особенно.
            • 0
              С технической точки зрения, вы, скорее всего, правы насчёт природы коммитов

              Я опираюсь на утверждения из Git Book, и это не техническая точка зрения, а концептуальная.

              Однако, семантика использования (за исключением rebase) больше всего напоминает набор патчей.

              Вот это «за исключением» все и портит.
              • 0
                Ну тут можно спорить, но как модель — оно работает хорошо. А когда человек достаточно освоился, он может отбросить эту модель, и держать в голове больше деталей реализации (если оно ему надо).
                • +2
                  Я не очень люблю сначала учить неправильной модели, а потом, когда возникают проблемы, переучивать.
                  • +2
                    Вы очень категоричны :)
                    Никогда не занимались рефакторингом? Или вы всегда и сразу строите безупречную и полную модель бизнесс процессов? Является ли предыдущая модель неправильной?

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

                    когда возникают проблемы, переучивать

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

                    Опять же, если почитать man gittutorial — вы не встретите там ни слова даже про хэши — там да и в самих cli утилитах они, в основном, упоминаются как «commit», что как бы намекает на то, где предметная область, с которой предлагается оперерировать, а где детали исполнения. Пользователю не нужно знать про тонкости реализации до тех пор, пока ему действительно не нужно про них знать :)

                    Могу я вас попросить поделиться практикой введения людей в git (или введения вас в git :))? Вы отправляете читать документацию? На какой срок? Какие контрольные вопросы потом задаёте или тесты проводите? Через какой промежуток времени, вы понимаете, что человек готов работать с кодовой базой без вашего надзора?
                    • 0
                      Никогда не занимались рефакторингом? Или вы всегда и сразу строите безупречную и полную модель бизнесс процессов?

                      Занимался. Но обычно модель бизнес-процесса развивается эволюционно, а не революционно.

                      или введения вас в git

                      git-scm.com/book/en

                      И две недели на гитхабе со своим проектом.

          • +19
            размазать достаточно Git-а по твоему лицу
            нет, спасибо :)
          • +2
            … Когда они впервые скачивают код, они могут привязать его к директории под названием master (потому что это мастер-копия). Затем, когда они приступают к работе над своим драйвером, они могут все это полностью скопировать в директорию под названием ужасный-драйвер-broadcom. Чтобы сгенерировать патч, им нужно просто получить разницу между этими двумя директориями.

            ах, видимо, поэтому один из вопросов от меркуриальщиков: «как узнать в какой ветке были такие-то изменения?»
            • +1
              раз в месяц тебе приходится отважно сталкиваться с Git, потому что каждый, даже его чертова собака, теперь использует GitHub?
              для гитхаба раз в месяц есть hg-git и help.github.com/articles/support-for-subversion-clients
            • +16
              Господи, когда же в мейнстриме появится что-то более простое и понятное чем Git. Пока все статьи которые я вижу из серии «Git это просто» больше напоминают сессии аутотренинга в стиле «все хорошо, все нормально».

              Я через силу заставил себя базовый основы Git выучить, но в целом Git вызывает примерно такое же отвращение, как vi после Sublime.
              • +29
                Git настолько восхитителен, когда его понимаешь, что не представляю, что может быть лучше.
                Если вы не умеете его готовить, то это не означает, что он не вкусный.
                • +1
                  Мне в начале его освоения как раз помогло описание его внутреннего устройства. После чего стало понятно, что не может быть ничего лучше VCS с такой прекрасной архитектурой и идеями, которые в неё заложены.
                  • +12
                    то, что вы не представляете жизни за пределами Git, не значит, что её там нет
                    • +3
                      Знаю, что есть, но не возникает желания или необходимости мигрировать. Зачем, если все отлично работает?
                      И то, что именно git стал на сегодняшний день мэйнстримом, только подтверждает, что большинство придерживается того же мнения.
                      • +10
                        Ну так и wordpress стал мэйнстримом, Миллоны мух не могут ошибаться.
                        • 0
                          То что исходный код вордпресса ужасен не отменяет того факта, что с точки зрения пользователя, это превосходный очень удобный движок. Именно поэтому он и стал популярным. Так что аналогия с мухами неверная.
                          • 0
                            Я что-то не припомню, чтобы мухи ошибались.
                            • 0
                              Wordpress на момент появления был лучшим движком. Чему удивляться.
                              Хотя, если честно, я его всегда недолюбливал.
                              Все претензии к нему от того, что новостной движок пытаются использовать для разработки сайтов и магазинов. Просто не надо этого делать и он будет вполне сносен.
                              А какие опенсорсные движки сейчас лучше? Просто интересно.
                              • +1
                                Лучшим по каким критериям? Возможности PHP4 в нём если и использовались, то весьма ограниченно, а ему тогда года 3 уже было.
                                • 0
                                  Лучшим по тем критериям, что ничего лучше в опенсорсе на тот момент не было.
                                  «Миллионы мух» не интересуются, насколько там все прекрасно архитектурно или какие фичи PHP там используются. Раз движок стал так популярен, то разумно предположить, что были для этого причины? Раз считаете, что все так было на тот момент плохо в движке — сделали бы в то время что-то лучше и выложили бы в опенсорс, делов то.
                                  • 0
                                    Лучшим по тем критериям, что ничего лучше в опенсорсе на тот момент не было.

                                    Лучше по каким критериям? :)

                                    Есть множество критериев лучшести и Вордпресс оказался лучшим по совокупности этих критериев для «миллионов мух». Но это не значит, что он был абсолютно лучшим, просто какие-то его плюсы оказались более значимыми для «миллионов мух» чем все его минусы.
                                    • 0
                                      А потому, что совершенство технологии — это далеко на самый важный фактор в распространении. Просто написать крутую штуку и выложить на гитхаб — этого мало.

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

                                      Wordpress зашёл в 2003, это был расцвет эпохи блогов и тогда, он действительно был приемлем в технологическом плане.
                                      Он предложил простую установку и расширяемость.
                                      Wordpress стали выбирать из-за обилия плагинов и тем.
                                      Появился wordpress.com…
                                      Теперь уже есть армия wordpress-разработчиков тем и плагинов. Так что wordpress — это в какой-то мере технология-стандарт.

                                      Та же ситуация с GIT: удачный заход из мира разработки Linux (где ей было самое место) через авторитет Линуса (вспоминаем эксперимент Милгрема), потом github, фактически самая крупная социалочка для OpenSource-разработчиков. Потом codeplex отказывается от HG, потом git-используется по дефолту в кучи мест.
                                      Теперь даже если кто-то напишет что-то лучше и удобнее, чем git, оно не станет популярным без очень сильного PR.
                                      • 0
                                        Совершенно верно, о чем, собственно и разговор.
                                        Пока не возникнут новые задачи, которые не решает Git и пока кто-то не предложит новое решение, Git будет оставаться мэйнстримом.
                                        Должно быть не просто то же самое, но немного удобнее (что вообще спорная категория), а на порядок лучшее. Или лучше в чем-то конкретном (ниша). Тогда сообщество это быстро раскрутит.
                          • 0
                            приготовьте мне гит так, чтобы оно записывало переименование/копирование
                            • +1
                              Сколько лет пользуюсь и не знаю, что есть какие-то проблемы с переименованием или копированием. С чем именно у вас тут сложности?
                              • +5
                                Если у вас такие вопросы, вы не понимаете гит. Гит отслеживает не файлы, а изменения в них. И это круто, потому что когда ты спрашиваешь «откуда-куда скопирован файл», то скорее всего инетерсен не сам файл, а «откуда взялся здесь этот кусок кода?», и гит даёт ответ на этот вопрос (см. man git-log, флаг -S).

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

                                Да, а «переименование/копирование» — это состояние файлов, и это не так важно, как кажется. Впрочем, гит вычисляет эти категории изменений на лету по «похожести» файлов.

                                См. githowto.com/changes_not_files
                                • +6
                                  Гит отслеживает не файлы, а изменения в них.
                                  гит не отслеживает изменения, гит делает снапшоты, а изменения между ними вычисляет по требованию. соответственно, вычислить, когда файл просто переименовали/скопировали (не меняя содержимого), труда не составляет — достигается банальным сравнением хэшей содержимого. Однако в реальном мире контроля версий исходных текстов ПО, гораздо чаще встречается переименование/копирование одновременно с изменением содержимого файла (например, так). И вот в этом месте начинается «угадайка»: что в куда переименовано/скопировано. Естественно, при наличии нескольких похожих изменений в коммите, угадывалка может угадать не то. Что намного веселее, по достижении некоторого порога правок на файл (50% по умолчанию), Git решит, что вы переписали файл заново. Ну и да, в достаточно больших коммитах угадывалка отключается вообще.

                                  Разумеется, ни mercurial, ни даже всячески гонимый линусом и присными subversion данной проблеме не подвержены.
                                  • +2
                                    Я кстати, так понял, что Линус имел слабое представление о subversion, в его понимании subversion = CVS, но это очень сильно не так. CVS действительно была ужасной системой.
                                    • 0
                                      Кстати, кто-то может всё-таки понятно объяснить, чем это так плох subversion, что на него столько гонева?

                                      p.s я абсолютно точно уверен, что мне нужна именно нераспределённая vsc. В моём случае, распределённость — явный минус
                                      • +2
                                        Лично я для себя вижу только одно: в Subversion нет локальных коммитов. Может, их уже добавили, но раньше их не было.
                                        У SVN нет поддержки на github и bitbucket, так что для open source проектов он не очень походит. Там как раз нужно форкать репозиторий.
                                        А в остальном svn очень годная штука с низким порогом вхождения.

                                        • 0
                                          На мой взгляд интересная заметка автора библиотеки SObjectizer о SVN, нелюбви к нему и степени владения инструментом тех, кто его не любит: http://eao197.blogspot.ru/2015/09/progflame-svn.html
                                          • 0
                                            Неудобная (или сложно найти мануал по удобной) работа с ветками — это основное для меня.
                                            • +2
                                              Распределенность же есть не просит.
                                              Можете просто ее не использовать. По опыту работы 90% пользуются одним центральным репозиторием.
                                              Но на самом деле, это удобно — у вас один репозиторий, у клиента — свой и вы можете обмениваться правками работая каждый со своей логикой и своим процессом. Мне лично очень нравится эта гибкость.
                                            • 0
                                              SVN была ужасной системой. Медленной, с плохой работой веток,… (tree-конфликты как вспомню, так вздрогну).
                                              Пока не переехали на Git было очень много сложностей с командной работой над большим проектом с длинными параллельными разработками.
                                              • +1
                                                Как я понимаю, ветки в SVN изначально совсем для другого — не для параллельной разработки. А скорее, чтобы хранить несколько версий одного продукта для разных пользователей, скажем.
                                                А разработка в SVN хорошо идет, если все всё время работают с одним репозиторием (в одной ветке). Это требует вначале некоторой организации кода, чтобы реально можно было пробовать разные вещи не мешая друг другу, и не мешая основному проекту — на уровне кода, не через SVN. Но зато потом это работает железно, 0 проблем.

                                                Видимо Git пытается взять на себя поддержку параллельной разработки, и от этого на него такая нагрузка. Ведь параллельная разработка — это никогда не бывает слишком просто. В принципе это и не может быть просто, вряд ли здесь есть универсальное решение.
                                                • 0
                                                  Не в организации кода дело, просто при разработке в одной ветке (независимо от системы) эта ветка становится продакшен веткой, коммиты в неё могут попадать только после приемочного тестирования.
                                                  • 0
                                                    Насчет SVN — очень похоже на то.
                                                    Пока мы работали с одной веткой — все было довольно неплохо. Но когда стали вести параллельный процесс (несколько пользователей пилят сразу несколько фич), то сведение превратилось в сущий ад.
                                                    Процесс в git у нас сейчас такой же — несколько параллельных веток, которые регулярно сводятся. Но благодаря ребэйсу (ежедневному и обязательному) и правильной организации задач все годаздо проще.
                                              • 0
                                                Вы меня не слушали. Да, снепшоты он делает, и делает он это для отслеживания *изменений*, хотя лучше здесь сказать *содержимого*? Ведь данные он хранит в блобах (не связанных напрямую с именем оригинальных файлов, если быть педантом — имена хранятся рядом, в деревьях). И сжимает блобы, как было уже указано выше, дельта-компрессией. И всё это сделано, чтобы следить за изменением содержимого. Блин, ну не было у меня никогда проблем с переименованием файлов в гите, не было. И если файл при переименовании изменился больше, чем на 50%, то действительно ли это старый файл? Ну какая мне разница, что файл был переименован, если он настолько изменён, что по сути это другой файл? Ещё раз: мне не важно где какой файл как называется, мне важно, оттуда какой кусок купола взялся. И гит решает эту проблему идеологическую. А блобы там и деревья — это деталь реализации, и она меня редко волнует. А учитывая популярность гита, я не один такой. Так что может пора просто изменить точку зрения на нашу программисткую реальность, и понять, что мы создаём не файлы, а текст, код, содержимое?
                                                • +6
                                                  Ну какая мне разница, что файл был переименован, если он настолько изменён, что по сути это другой файл?
                                                  Это не так сложно как может показаться: переименовали пару тесно связанных классов вместе с файлами, поправили типы полей, имена переменных, ругательства в ассертах — и вуаля, сущность по сути та же, а гит думает, что мы её с нуля написали. Впрочем, с гита взятки гладки, гит — тупотрекалка содержимого, настоящая же проблема в том, что это 1) затрудняет археологические раскопки 2) запутывает коллег при code review
                                                  Так что может пора просто изменить точку зрения на нашу программисткую реальность
                                                  Интересно, это чего ради же? Чтобы вписаться в ограничения второстепенной утилиты, неспособной тупо записывать за мной? Много чести, проще взять аналог, который это умеет.
                                                  • –3
                                                    Вы сейчас какие-то небылицы рассказываете. Чтоб были понятно — у меня 15 лет стажа после универа, лет 5 на гите, писал профессионально на перле, пхп, питоне, теперь на скале пишу. Команды были от 2-3, до 15 человек, сейчас пишем большой командой суровый энтерпрайз: скала, хадуп, бигдата, все дела. С описанными проблемами не сталкивался никогда. Или просто за проблемы их не считал.
                                                    • +5
                                                      ну начинается :) у меня 10 опыта и 5 гита, и что с того? это каким-то образом отменяет объективную реальность?
                                                      С описанными проблемами не сталкивался никогда.
                                                      1) это только 1 (одна) проблема, 2) ах ну раз не сталкивались, значит всё в порядке. carry on :)
                                                      Или просто за проблемы их не считал.
                                                      philosoraptor.png
                                          • +3
                                            Что в нем восхитительного?
                                            Отсутствие веток (только именованные головы) и такая милая штука как отсоединенные коммиты?
                                            Или только автоматическое определение переименованных/перемещенных файлов?
                                            Или merge, который сливает два снапшота вместо двух графов диффов?
                                            • 0
                                              Почему же нет веток? У меня есть.
                                              У меня по процессу 4 постоянных ветки и я в любой момент могу делать сколько угодно новых — под конкретные фичи. И все это элементарно и беспроблемно таскается из ветки в ветку, меняется местами и сводится. Я в любой момент могу решить, что войдет в апдейт, что отложить, что выложить частично, например. Не представляю, что может быть гибче.
                                              Мерджи я по процессу совсем исключил. Они слишком мусорят историю. Все делаю ребэйсом. Даже пулл — с опцией ребэйса. Так же удобно собирать коммиты (если случилось, что по одной задаче их получилось несколько) в один интерактивным ребэйсом.
                                              А что из какой ветки пришло сохраняется при слиянии функционала после ребэйса уже мерджем с --no-ff.
                                              История всегда идеальна и прозрачна.
                                              • +3
                                                Я вас разочарую — у вас нет веток. У вас есть именованные головы и связи между коммитами. «Ветка» в гите — раскраска от коммита в прошлое по связям.
                                                Ветки, привычные по живой природе и другим системам контроля версий, совершенно другие: растут не от крайнего листочка в прошлое, а от родительской ветки в будущее.

                                                > И все это элементарно и беспроблемно таскается из ветки в ветку, меняется местами и сводится. Я в любой момент могу решить, что войдет в апдейт, что отложить, что выложить частично, например. Не представляю, что может быть гибче.
                                                Я вас снова разочарую: в мерке все это делается еще проще и надежнее ;) Потому что с нормальными именованными ветками, без дурацких отсоединенных коммитов, куда более интеллектуальным слиянием, возможностью руками указать на перемещение-переименование

                                                > Мерджи я по процессу совсем исключил. Они слишком мусорят историю. Все делаю ребэйсом.
                                                То бишь предпочитаете фальсифицировать историю. Это вполне возможно и в других системах контроля версий. Проблема только в том, что при ребейзе мерж никуда не пропадает по факту, но из истории вычеркивается. Это делает расследование и исправление проблем особенно увлекательным. Также очень приятно что без специальных ключей гит элегантно сотрет лишнюю по его мнению информацию о принадлежности коммита к ветке (я в курсе, что такой информации у гита на самом деле нет, я про то как это выглядит для пользователя).
                                            • +2
                                              Я умею его готовить и даже есть.
                                              Но гит именно что невкусный.
                                              • 0
                                                А что именно вам в нем не нравится? Конкретные примеры?
                                                • +2
                                                  Коммит отсоединенный.
                                                  Слияние без учета истории сливаемых веток.
                                                  Переименование-перемещение только если как сам гит соизволит принять вердикт… который может меняться на лету вместе с настройками.
                                                  Отсутствие именованных веток.
                                            • +2
                                              www.fossil-scm.org куда еще проще?
                                              • +4
                                                Ну а что? По сути это мини-github в кармане. Кроме распределенного репозитария получаем распределенный трекер ошибок, базу знаний и т.д. и т.п. Fossil — это лично мой выбор, как человека до сих пор сидящего на git. Он чуток проще git, можно таскать на флешке без надобности настройки окружения. Один минус — большие бинарные файлы не для него, но я пишу только код. Для маленькой комманды нет ничего лучше.
                                                Git оставим для больших проектов с кучей народа. А свои личные проекты я буду держать на этом маленьком гиганте.
                                              • +5
                                                Попробуйте Mercurial с помощью TortoiseHg. Он интуитивно кажется гораздо понятнее.
                                                • +3
                                                  а чем это будет отличаться от tortoisegit или tortoisesvn?
                                                  • 0
                                                    если пользоваться черепахой, то конечно, гит покажется недружелюбным. Git идеален а консоли — только так понимаешь, как именно он работает, и почему это правильно.
                                                    • 0
                                                      Родной GitGui тоже очень хорош.
                                                      • +5
                                                        По моему, все недовольство git'ом вызвано как раз использованием GUI-оболочек.
                                                        Оболочки еще больше запутывают и без того «странноватый» синтаксис git'а. А понимание логики работы приходит только когда работаешь с git'ом напрямую через консоль.
                                                        • +1
                                                          Ну в винде (если я не в шелле msys2) мне удобнее использовать GitGui. Ну а нравится он или нет — дело вкуса. Мне и так хорошо и так.
                                                          • +1
                                                            Лично мое первое впечатление от git было: «version destroy system»
                                                            А все потому что впервые столкнулся с отсоединенным коммитом.
                                                            Коммит, конечно, нашелся, но осадочек неприятный остался.
                                                            Логику работы понял так: «и чего только люди ни придумают лишь бы именованных веток не делать».
                                                            • 0
                                                              В первый раз отсоединенный коммит (особенно, когда его не ожидаешь) — это страшно )
                                                              Но это от непонимания что к чему.
                                                              На самом деле, ничего страшного в нем нет.
                                                              • +1
                                                                Страшного ничего нет — жить без веток и с отсоединенными коммитами можно.
                                                                Вот только как часть дизайна системы контроля версий и то, и другое — фейл, эпик фейл.
                                                                Не должна система контроля версий автоматически утрачивать такую важную часть метаданных, как ветка, в которую сделан коммит.
                                                                И уж тем более ничего подобного не должно автоматически происходить с самими коммитами.
                                                                • 0
                                                                  Не должна система контроля версий автоматически утрачивать такую важную часть метаданных, как ветка, в которую сделан коммит.

                                                                  а что есть такое важное в тех метаданных (кроме самого названия) ветки меркуриала? (без иронии и сарказма)
                                                                  я так и не понимаю пока, что это даёт…

                                                                  взять тот же репозиторий самого Git'а:
                                                                  развесист он до ужаса, однако, найти кто, когда в ходе какого набора коммитов сделано изменение — вообще не проблема…
                                                                  и самое главное — узнать почему: это описано в КАЖДОМ коммите
                                                                  и хотя это мера административная… весьма нужная… но в произвольном проекте всё равно зависит от разработчика
                                                                  и уж если разраб не пишет ПОЧЕМУ он сделал эти изменения, а ты потом вынужден догадываться (по имени той ветки, в которой он это делал), то это не столько проблема ЛЮБОЙ SCM (даже SVN), сколько «разрабу по башке надавать», чтобы не писал коммиты вида «Исправлена ошибка»
                                                                  или есть ещё что-то, что я не вижу?
                                                        • +3
                                                          И граф коммитов тоже в консоли смотрите?
                                                          У меня вот очень большой проект, чтобы проследить от кого пришло изменение мне порой приходится стреляться даже с SourceTree. Как вы это всё в консольке делаете?
                                                          • +1
                                                            git log с дополнительными опциями
                                                            stackoverflow.com/a/9074343/272927
                                                            • +4
                                                              Да, я знаю, что у git есть log с кучей параметров.
                                                              Мне интересные конкретные действия, которые делает человек, который отрицает GUI для Git для того, чтобы найти от кого пришло изменение. Ну там, открывает 10 терминалов, долго уныло скроллит, пишет grep или как?
                                                              • +1
                                                                Ну да. git log --grep, git blame и т.д.
                                                                10 терминалов не нужно. Достаточно одного.
                                                                • 0
                                                                  соответственно, с десяток тыкнутых клавиш вместо пары кликов мышкой) тут действительно нечего спорить, кому как удобнее. На небольших проектах с небольшим числом разработчиков gui вполне достаточно (а зачастую в силу более низкой планки входа — и удобнее). Консоль представляет больше возможностей, там где они действительно нужны. Этот вечный спор консольщиков и визуалов напоминает ситуацию с АКПП. Первое время автоматы считались моветоном, 'не по пацански' ;) а ведь ничего, сейчас половина машин, если не больше, уже без педали сцепления. И ничего, 'пацаны' нормально ездят.
                                                                • +1
                                                                  tig? — https://github.com/jonas/tig
                                                              • 0
                                                                Граф, каюсь, смотрю в gitk — запускаю из консоли.
                                                                В нем мне действительно удобнее смотреть историю.
                                                                Хотя проблем с git log тоже нет. Особенно, когда работаешь не на своей машине, а по ssh.
                                                                Из доп.средств еще пользуюсь kdiff3 для разбора конфликтов, тоже как-то привык к нему.
                                                              • –1
                                                                Не знаю, уверен что профессионалы Git, конечно, правы. Но так как по сути и Git, и SVN, и все такое — делают ОЧЕНЬ ПРОСТЫЕ ВЕЩИ, то совершенно ожидаемо, что я, как пользователь, ожидаю от них, что бы они делали это ПРОСТО.

                                                                Ну как бы система выполняет 3-5 команд всего. Неужели так было трудно сделать, чтобы эти 3-5 команд выполнялись легко, без того, что нужно год работать под Git чтобы во всем разобраться? Ясно, что в конце концов человек во всем способен разобраться.

                                                                Merge под Git — это просто катастрофа. Всегда приходится использовать посторонние программы, но дело не в этом. Не дай бог случайно сделать что-нибудь не то, какую-то ошибку. Потом откатить назад все очень сложно.
                                                                Как-то в SVN я этого не замечал. Так что я жалею, что весь мир перешел на Git.

                                                                Недавно мы начали работать с суб-репозиториями. Это вообще ужасно. Собрать головной проект по всем референтным ссылкам на суб-репозитории, которые независимо обновляются… Ну правда, это как использовать vi вместо Microsoft Word.
                                                          • +1
                                                            Ну. vim настолько же лучше Sublime, насколько vim лучше блокнота.
                                                            • +2
                                                              Git сложный и вся его прелесть внутри, в этих tree-hash, в этих индексах, в этих immutable цепочках комитов. Он настолько сложен и прекрасен внутри, что когда он выходит наружу через console, а еще хуже через UI, он становится совсем непонятным. Почему надо сначала делать git add, а потом git commit. Почему есть git push и есть git commit, эти многие вопросы, которые появляются когда люди переходят с svn или других систем. На самом git гораздо сложнее внутри, чем svn, зато эта сложность оправдана. Он позволяет делать такие алгоритмы, которые невозможны в других vcs. В git действительно, очень сложно потерять, его действительно очень сложно сломать без возможности восстановить (чего не скажешь о svn), git делает потрясающие merge. Сравнивать его с mercurial не стану, долго им не пользовался, но git действительно очень интересно устроен. Мне кажется, каждый программист должен изучить сначала его изнутри, тогда многое на верхнем уровне станет понятнее. На самом деле git команды изначально были не самым лучшим образом устроены, поэтому появилось множество утилит и «упрощений» типа github client, но они меняются со временем и становятся только лучше.
                                                              • +2
                                                                Так может, он бы и жил своей внутренней жизнью, вообще не выходя наружу? Мы бы радовались его красоте, а пользовались для жизни чем-нибудь другим?

                                                                Вот например, прекрасная ситуация: сделал git push, а потом, перед git commit проверил обновления и увидел, что код существенно изменен. Казалось бы, что проще? Возьми последнюю версию, сделай merge.
                                                                Но после обновления выходит, что уже есть 3 версии: локальная, та которая push и обновленный репозиторий. Сидишь, думаешь, с чем сливать. Ладно, проще иногда сделать commit с конфликтами, просто чтобы не связываться с этим, а потом еще раз вычистить все конфликты и снова сделать commit. Но тогда кто-то другой может успеть взять версию с конфликтами из репозитория…
                                                                • +2
                                                                  Но после обновления выходит, что уже есть 3 версии: локальная, та которая push и обновленный репозиторий. Сидишь, думаешь, с чем сливать.

                                                                  эээ… ТРИ версии? откуда?
                                                                  в простейшем случае в одной «ветке» проекта есть две версии кода:
                                                                  ваша локальная и та, что на удалённом «центральном» сервере, и всё.

                                                                  и зачем перед git commit делать обновления? ну, хорошо, git fetch можно делать в любое время, но каким образом это мешает коммитить? или вы следуете парадигмам SVN: коммитите только в последнюю версию с сервера?

                                                                  может порядок сначала навести в головах или workflow?

                                                                  если готовы конструктивно обсуждать, я тоже готов, а отвечать на нытьё «я там что-то сделал, не понял что, а оно сломалось, вот оно говно» не хочется :)))
                                                                  • +1
                                                                    эээ… ТРИ версии? откуда?

                                                                    1. локальная ветка
                                                                    2. удаленная ветка
                                                                    3. рабочая директория с результатом мержа
                                                                    • +3
                                                                      ну, давайте упростим себе задачу:
                                                                      1. начнём с того, что мёрджить стОит только закоммиченные изменения (хотя можно и с «грязной» WC, если изменённые файлы не трогаются мёрджем; но зачем себе яму рыть?)
                                                                      2. таким образом мы имеем две версии: локальная и удалённая… мёрджить их — не проблема. даже в случае ошибок, всегда можно повторить мёрдж…
                                                                      3. пушить на сервер можно только закоммиченные изменения, поэтому вопрос «у меня тут незакоммиченный результат мёрджа, не знаю что пушить» — как-то абсурден, мне кажется? а закоммиченный мёрдж превращается в локальную версию… и опять у нас ДВЕ версии: локальная и удалённая…

                                                                      поэтому я не понимаю тут проблемы ТРЁХ версий…
                                                                      • +1
                                                                        1. Иногда можно и забыть — человеческий фактор и всё такое, но не суть.
                                                                        2. В момент мержа или сразу после него у нам три версии по любому — локальная в репозитории до мержа, удаленная в репозитории до мержа и результат мержа (закомиченный или нет тут не особо важно), что хорошо видно по всяким *diff3
                                                                    • –1
                                                                      Сделать commit не получается просто так. Если удаленная версия изменилась, то, как я понимаю, Git заставляет её взять начала. Дальше возможны два варианта. Если его автоматический merge сработал, то в Tortoise возникает такая смешная картинка, которая меня приводит в некий ступор: одна стрелочка вверх, одна вниз. Ну то есть можно после этого сделать push, не вполне понятно в какую версию. Но через несколько pulls все восстанавливается, кажется.
                                                                      А вот если не повезло, и возникли конфликты в слиянии… Тут все совсем плохо. Локально версия становится с этими безумными <<<<<<, >>>>>>>>; версия staged без них. При этом откатить авто-commit, кажется, невозможно (может, я не знаю как просто?).
                                                                      Надо дальше читать про git?
                                                                      • 0
                                                                        У вас что‐то с workflow. Если бы вы делали коммит находясь исключительно на своей закладке‐ветке, то таких проблем бы не возникло на стадии фиксации. Конфликты, конечно, никуда не делись бы, но они возникли бы позже: при merge или rebase.

                                                                        Ну и если вам не нравятся <<</>>>, то используйте другой mergetool (см. git help mergetool и git help config, во втором искать mergetool).

                                                                        PS: вернуть репозиторий из абсолютно любого состояния возможно всегда: я, к примеру, иногда вынужден делать git branch -f branch origin/branch. Но не всегда это легко. git reflog вам поможет.
                                                                        • 0
                                                                          Извините, я неправильно написал. Надо было «сделать push не получается просто так»…
                                                                          Просто я работаю с несколькими системами одновременно, и все эти понятия слегка смешались у меня в голове.
                                                                          Но это была моя ошибка, извините.

                                                                          По сути я описал проблему верно, только ошибся в словах.
                                                                        • +1
                                                                          Коммит можно делать всегда и из любых положений, он не требует обновлений с сервера, потому что это сугубо локальная операция. Только git push может потребовать git pull если удалённая ветка обновилась.
                                                                          • +1
                                                                            Это была моя ошибка, извините. Я выше (и ниже) объяснил.
                                                                          • +3
                                                                            Если удаленная версия изменилась, то, как я понимаю, Git заставляет её взять начала.

                                                                            в этом и проблема, что Вы неверно понимаете
                                                                            в отличие от SVN, используя Git, вы можете вообще не обращать внимание на то, как обновляется «центральный» сервер (боюсь Вам говорить, что удалённых репозиториев может быть не один), коммитите себе и коммитите в свою ветку…
                                                                            а брать pull'ом удалённые репозитории — не самый лучший способ

                                                                            судя по всему, вы коммитите сразу в master, и ещё делаете git pull ПЕРЕД коммитом…
                                                                            в общем, вы трогаетесь сразу с третьей передачи, и удивляетесь, что она глохнет… иногда только газу когда сильнее даёте, до отсечки, тогда иногда трогается, но сцепление горит…

                                                                            сходите в автошколу, что ли… на пару занятий…
                                                                            • 0
                                                                              Да нет, я кажется плохо объяснил. Во-первых я обычный пользователь, и уж конечно никто мне в master работать не даст. Моя область профессионализма, ну извините, лежит не в IT; в своей области я хороший профессионал. А с Git мне просто приходится иметь дело.

                                                                              Commit, разумеется, сделать легко. И никакой pull перед commit я не делаю (хотя, возможно, стоило бы). Проблемы начинаются, когда делаешь push и понимаешь, что версия уже изменилась. И тут начинается автоматический merge + все последующие проблемы.

                                                                              Скажем, конкретный вопрос: а) можно ли откатить авто-merge? б) можно ли откатить commit? Просто легче делать merge между локальной копией, и удаленной. А не между stage-версией (которая неизвестно где находится), и удаленной.
                                                                              Под stage-версией я имею ввиду ту, которой уже сделали commit но еще не смогли сделать push.
                                                                              • +2
                                                                                Откатить автослияние должно быть можно обычным git reset. Но я предпочитаю просто работать с ветками‐закладками, которые трогаю только я, потому с такой ситуацией практически не сталкиваюсь. Но, насколько я помню, автослияние только при pull, а с push такого быть не должно. Если после push идёт автослияние, то вам поможет только push -f {remote} {old-commit-hash}:{branch-name}, если я правильно понимаю (точнее, вам точно нужен push -f, но не уверен, что он примет {old-commit-hash}: если нет, то придётся использовать ещё и branch -f {branch-name} {old-commit-hash}, предварительно слиняв с ветки или тот же reset (что проще: линять с ветки не нужно)).

                                                                                Фиксацию обычно откатывают через reset. Помните, что под «откатом» в git имеется ввиду исключительно перемещение закладок: само изменение никуда не денется, просто на него после отката перестанут ссылаться, а через некоторое время оно будет удалено сборщиком мусора. Поэтому в особо сложных случаях можно «откатывать» через branch -f {branch-name} {commit-hash}, восстанавливая изменения в рабочем каталоге через checkout {removed-commit-hash} -- '*'. Предварительно сделав checkout --detach и в конце не забыв checkout {branch-name}.

                                                                                И ещё одно: stage версия — это незафиксированное состояние, с которым до фиксации (commit) можно работать только локально, ни в коем случае не состояние, имеющиеся в любом из изменений.
                                                                                • 0
                                                                                  точнее, вам точно нужен push -f

                                                                                  Предварительно сделав checkout --detach

                                                                                  оу! оу! оу! полегче!
                                                                                  *и после этого ещё ругаются на Git...*
                                                                                  не лечите по фотографии, а то ща насоветуете…
                                                                                  • 0
                                                                                    Это единственный способ откатить слияние, если оно с какой‐то радости произошло именно при push, а не локально, как должно. Я недостаточно знаю git, чтобы сказать, что слияние на сервере при push в принципе невозможно.
                                                                                    • 0
                                                                                      Лечите, лечите. Может я наконец разберусь. Поверьте, я уже прочитал книжку по Git, и работаю с ним пару лет, и то до сих пор не разобрался в некоторых базовых вопросах. Это уже даже не смешно.

                                                                                      На работе, если у меня что-то не получается, то просто приходит человек и исправляет. Но я все равно не понимаю до конца. Причем, мне кажется, даже он не всегда понимает как именно надо исправить, просто делает несколько разных попыток и в конце все работает.
                                                                                      У нас последняя история — начали работать с submodules, это вообще финиш. Приходится каждый раз ВРУЧНУЮ проверять все ссылки на submodules каждый раз, прежде чем делать pull главной версии. А этих submodules уже больше десятка. Но про это я даже не спрашиваю, мне бы с вещами попроще разобраться.
                                                                                      • +1
                                                                                        В mercurial свой аналог (subrepos) пометили как «feature of last resort» («использовать в крайнем случае»). Наверное, не зря.
                                                                                        • 0
                                                                                          Наверное. К сожалению, это не мне решать. Возможно это облегчает координацию между группами в чем-то, когда на проекте более тысячи человек.
                                                                                          • 0
                                                                                            В git'е их тоже не стоит использовать, пока не прижмёт. Всё-таки фича тяжелая для повседневного использования и требует хорошо продуманного workflow.
                                                                                      • 0
                                                                                        Я что-то понял, но мне хотелось бы прояснить. Сначала определимся с терминологией.

                                                                                        Версия А — моя локальная версия. НЕКОТОРЫЕ файлы из которой я хочу отправить в репозиторий, а с другими продолжаю работать.
                                                                                        Будем считать, что когда я начал работать с локальной версией, она была синхронизована с репозиторием в день 1.

                                                                                        Я делаю commit этим файлам, и у меня появляется:
                                                                                        Версия Б — это удаленная версия день 1 + мои правки, все это все еще у меня на компьютере.
                                                                                        При этом версия А тоже продолжает оставаться на моем компьютере, так как я сделал commit не всем файлам.

                                                                                        Следующий этап — я делаю push, и понимаю, что удаленная версия изменилась. То есть сразу push не проходит. Назовем удаленная версия день 2, это версия В.
                                                                                        Значит я делаю pull этой версии В, и этот pull портит мне версию А. Но я могу зато сделать merge. При этом версия Б остается неизменной, мой merge на нее не влияет, к сожалению.

                                                                                        Ок, я делаю merge В с А (хотя хотел бы Б с А), потом делаю еще один commit и надеюсь, что теперь Б соединилась с В.
                                                                                        После этого я делаю push и вроде все должно быть нормально.

                                                                                        Однако, поскольку эта процедура портит мне локальную версию А, то и merge A c В может не вполне адекватно отражать то, что я хочу сделать merge Б c В.

                                                                                        Где физически находится версия Б? Могу ли я работать с ней напрямую, после commit но до push? Могу ли я работать с Б так, чтобы моя локальная версия А оставалась нетронутой?

                                                                                        Извините, если запутано получилось.
                                                                                        • 0
                                                                                          да уж… весьма запутано
                                                                                          Вы визуализируете себе дерево коммитов?
                                                                                          gitk --all ?
                                                                                          git log --all --oneline --graph --decorate на «худой конец»?
                                                                                          • 0
                                                                                            Ну здесь хотя бы нету слияния при push, так что про push -f лучше забыть. Версия Б находится локально в виде изменения и изменить её нельзя никак, но можно создать версию Г, что‐то сделав (rebase, к примеру) из Б и обычно этот вариант считают изменённой версией Б (знание о том, что версия Б не изменилась и никуда не делась пригодиться, если вы где‐то накосячили — Б можно добыть из reflog). Я предлагаю следующее:
                                                                                            1. git stash — фиксирую версию А в специальном изменении, которое никуда не push’ится. Достаётся с помощью git stash apply, при этом происходит аналог rebase. Это лучше вообще делать до pull, если вы хотите нормально работать с Б.
                                                                                            2. Теперь делаете pull, указывая что делать с Б (слияние, rebase).
                                                                                            3. И теперь достаёте сохранённый А, происходит rebase A поверх слияния Б+В (которое я ранее назвал Г для случая «rebase»).
                                                                                            Делать push можно в любой момент после 2.
                                                                                            • 0
                                                                                              Звучит очень хорошо. ОК, про А мы можем забыть. Делаем ей stash, действительно, и все. Вместо pull сначала fetch, чтобы осмотреться.

                                                                                              По поводу 2: вы не знаете, как в Tortoise выбрать слияние или rebase? И в чем разница?
                                                                                              И другой вопрос — как в Tortoise откатить слияние, если оно слишком запуталось (то есть вернуть чистую версию Б?)

                                                                                              Я как-то уже привык работать через Tortoise, не хочется учить terminal git только для пары вещей.

                                                                                              Спасибо!
                                                                                              • +1
                                                                                                Про tortoise меня не спрашивайте, я использую либо терминал, либо Vim с моим дополнением aurum (которое попутно стирает часть различий между git и mercurial). Но слияние/rebase только через терминал.
                                                                                • 0
                                                                                  #some changes in working
                                                                                  git add.
                                                                                  git commit
                                                                                  git fetch
                                                                                  git merge --no-commit origin/…
                                                                                  #control merge
                                                                                  git push
                                                                              • +4
                                                                                Думаю, что очень нескоро. Github, bower и прочие тулы, где git идёт по-умолчанию, фактически, монополизировали git.
                                                                                • +2
                                                                                  Да-да. У меня у прабабки, когда была жива компьютеры вызывали отвращение, поэтому она до упора пользовалась печатной машинкой. Это про vim против sublime. Про git Вам и так много ответили.
                                                                                  • +1
                                                                                    Некоторые и сейчас предпочитают печатные машинки ) Да и качество первых экранов и самих компьютеров могло вызывать.
                                                                                    Заголовок спойлера
                                                                                    image
                                                                                    Californication
                                                                                    • 0
                                                                                      Всё бы хорошо было в sublime, если бы там текстовый редактор доработали. А так — когда мне нужен блокнот на стероидах — я предпочитаю IDE.
                                                                                    • +4
                                                                                      «git gets easier once you get the basic idea that branches are homeomorphic endofunctors mapping submanifolds of a Hilbert space.» twitter.com/tabqwerty/status/45611899953491968
                                                                                      • +1
                                                                                        Вроде как Google собирается раскрыть свою систему контроля версий, основанную на mercurial.
                                                                                      • –8
                                                                                        Да, есть много хейтеров гита, даже я таким был, и боготворил меркуриал… Но до тех пор, пока не начал работать в команде из более чем 3х человек.
                                                                                        Меркуриал прекрасен, быстр, прост и удобен.
                                                                                        Но в гите понимаешь все прелести cherry-pick, rebase, ветвления, множество апстримов (они есть в меркуриале, но либо скрыты в недрах, либо реализованы костылями)… Около полутора лет назад плотно подсел на git, но до сих пор не понимаю как он работает. Очень часто ломаю ветки пытаясь сделать rebase на master, иногда возникают вопросы как сделать инвертированный cherry-pick (особенно для отката более чем одной ревизии), не всегда ясно почему это дерьмо не хочет «пушить» в мастер с одной машины, но успешно «пушит» с другой, но я его люблю, потому что… Да не знаю почему… Наверное потому что он прекраснее меркуриала, с ним можно поиграться, поискать проблем на пятую точку и сломать все нахрен =)
                                                                                        • +4
                                                                                          Если с каждой возникающей проблемой один раз разобраться и запомнить, то Вы начнёте боготворить гит.
                                                                                          • 0
                                                                                            Если разобраться предварительно, то и проблем не будет возникать.
                                                                                            • +16
                                                                                              Если использовать меркуриал, то и разбираться не придется — оно просто сразу работает так, как надо.
                                                                                              • –2
                                                                                                Не работает оно как надо, основной костыль mercurial — это бранчи, в git они работают просто и понятно, в mercurial это что-то другое. Еще mercurial не давал никаких инструментов по созданию «чистой» историю, а не сложнейшего графа комитов — не было rebase стратегии (ситуация может поменялась за 3 года, так что буду рад услышать что-то новое)
                                                                                                • +9
                                                                                                  Про бранчи — с точностью до наоборот.
                                                                                                  Главный костыль гита — обозвать именованную голову веткой, а отсоединенный коммит — нормой. «Свобода — это рабство, незнание — сила».
                                                                                                  И меркуриал вы не знаете — 3 года назад все перечисленное вами уже было.
                                                                                                  • 0
                                                                                                    А может мне кто нибудь пояснить необходимость rebase? Чисто для красоты дерева коммитов что ли?!
                                                                                                    Под hg есть плагин, видимо, повторяющий функционал из git, но не могу понять, зачем бы мне захотелось переносить изменения вместо слияния?
                                                                                                    • +3
                                                                                                      Есть замечательная презентация от Mark Jason Dominus: perl.plover.com/classes/git-rebase-wtf/samples/slide001.html
                                                                                                      Она многое объясняет.

                                                                                                      Применений много. Например, вы накоммитили в локальную ветку кучу коммитов, включая коммиты виды «сделал фичу а», «сделал фичу б», «ой, забыл запятую в фиче a», «починил фичу б», «фича б поломала фичу а, починил её», «отрефакторил фичу б». Никому не интересно в истории видеть все ваши метания с запятыми, фиксами и правкой пробелов. Ребейз позволяет такую историю ужать до «сделал фичу а», «сделал фичу б» и отдать ветку в мир с двумя аккурантыми коммитами.

                                                                                                      Другой пример: у вас ветка следит за мастером. Если постоянно вытягивать и мержить мастер в вашу фичеветку, в истории получается «колосок» из мержей, что 1) некрасиво, 2) после пуша только путает историю, т.к. никому неинтересно, когда и как вы подтягивали мастер, 3) добавляет лишний хаос, 4) эти мержи не несут смысловой нагрузки к вашей разработке, итд. Альтернатива: вытягивать master отдельно, а потом фичеветку ребейзить на него. Тогда, когда вы отдадите свою ветку в мир, он будет базироваться на актуальном мастере, история будет ровная, гладкая и шековистая^W^W.

                                                                                                      Есть ещё несколько примеров, когда ребейз полезен, в том числе для исравления всяких ошибок и факапов. Я привёл самые частые случаю использования. Рекомендую всё же посмотреть презентацию Mark-а.
                                                                                                      • +2
                                                                                                        Всё это, само собой, имеет для вас смысл, если вы заботитесь о чистоте истории так же, как о чистоте кода. Если вам всё равно, и вы считаете, что чистая история никому не нужно, то ребейз не для вас.
                                                                                                        • 0
                                                                                                          Да, точно. Поймал себя на мысли, что не вижу ценности в «чистой истории».
                                                                                                          • +1
                                                                                                            Очищенная история — это не история, а попытка выдать желаемое за действительное.
                                                                                                          • +1
                                                                                                            Чисто для красоты дерева коммитов что ли?!


                                                                                                            Применений много.


                                                                                                            Для красоты.
                                                                                                            • 0
                                                                                                              Подписываюсь под каждым словом.
                                                                                                              Вытягивать ветку за которой следит текущая можно через pull --rebase, еще проще это прописать в конфиг и делать автоматом при каждом пуле.
                                                                                                            • 0
                                                                                                              для того, чтобы в релиз пошла красивая история без творческих метаний и пингпонга с reviewerом
                                                                                                              • 0
                                                                                                                В релиз пойдёт снэпшот.
                                                                                                              • +2
                                                                                                                У меня длительные проекты и я через какое-то время уперся в то, что история стала просто нереально мусорной — мерджи копились и все это стало напоминать бесконечную пирамиду.
                                                                                                                Ну и сам вопрос мерджей — когда сливаются две правки в одну — не очень приятен при больших изменениях.
                                                                                                                Гораздо лучше из ситуации «делали две большие фичи параллельно и вот они сливаются в одну кучу» сделать ситуацию «вот сделана одна большая фича, вот за ней сделана другая большая фича» — так история получается линейной и фактически двухуровневой (вместо разрастающейся годами пирамиды). Если при этом ребэйсить правки друг-друга ежедневно, то о проблеме с конфликтами можно забыть окончательно. Гораздо легче разрешить конфликты как только они появились, чем делать это при финальном сведении правок. Поэтому у нас прописан в конфиге pull с опцией rebase — разработчик что-то делает в ветке, если при этом кто-то уже залил в эту ветку какой-то другой функционал, то этот разработчик должен свои правки локально перенести на актуальное состояние ветки — если при этом возникнут конфликты, то он их тут же, по свежей памяти разрешит, а когда он закончит работу над фичей и соберется запушить ее в основную ветку, то она идеально встанет в историю — без конфликтов, поскольку он постоянно актуализировал (ребэйсил) свою работу.
                                                                                                                Еще раз повторюсь — для меня это наиболее приемлемый вариант ведения истории по большим долгосрочным проектам с командной разработкой.

                                                                                                                Так же у ребэйса есть интерактивный режим — с возможностью объединять и переставлять коммиты — это очень удобно для финального рефакторинга правок.
                                                                                                                • 0
                                                                                                                  Ок, понял. На всякий случай уточню: в git ведь не обязательно для актуализации с мастером (при длительной разработке) делать ребэйсы? Просто можно время от времени смерживать актуальные изменения?
                                                                                                                  • +1
                                                                                                                    На всякий случай уточню: в git ведь не обязательно для актуализации с мастером (при длительной разработке) делать ребэйсы?

                                                                                                                    не обязательно
                                                                                                                    Просто можно время от времени смерживать актуальные изменения?

                                                                                                                    можно

                                                                                                                    вы вольны выбирать ту схему работы с коммитами, которая вам удобна )))
                                                                                                                    хочешь кучу merge'ей и видеть реально, кто, что, откуда и когда? — пажалте
                                                                                                                    не нравится — делайте rebase, и получи «полусинтетическую» «красивую» историю, в том виде, в котором с ней потом удобней работать
                                                                                                                    дело хозяйское, и SCM Git тому не препятствие ))
                                                                                                            • –2
                                                                                                              Сколько не пытался юзать меркуриал, долго плевался и возвращался на гит. Зачем там дублирование хешей номерами коммитов, для застарелых свнщиков? А ветки там тоже такую боль приносят, чтобы привычнее после свна было? Не знаю, может мой мозг съеден гитом, но мекруриал мне кажется каким-то поломанным и недоделанным подобием гита.
                                                                                                              • +8
                                                                                                                Еще как съеден ;)
                                                                                                                В мерке нет отсоединенных коммитов.
                                                                                                                В мерке мержатся не два снапшота, а две истории коммитов.
                                                                                                                В мерке после мержа двух веток коммиты сохраняют свою изначальную привязку к веткам.
                                                                                                                Так что ветки мерка никакой боли не приносят, если воспринимать их именно как ветки, а не именованные головы с предысторией.
                                                                                                                • +2
                                                                                                                  Вот чувствую там что-то прекрасное, но не могу никак понять, что именно и зачем?
                                                                                                                  Что дает отсутствие отсоединенных коммитов?
                                                                                                                  Чем хороша привязка коммита к ветке? У меня в git программист запушил в release то, что должно было быть в hotfix (или поменялись приоритеты и что-то из релиза нужно выложить срочно) — я просто перекинул коммит из ветки в ветку. Это равнозначно тому, что задача изначально и была бы сделана в нужной ветке. Зачем мне чтобы коммиты были привязаны навсегда к своей ветке? Или я неправильно понимаю и это работает как-то по другому?
                                                                                                                  Если работать только с мерджами разве не будет история похожа на карту метро? Или всегда все в мерке делается идеально? Или тоже ребэйсится для улучшения истории?
                                                                                                                  Чем мержить две истории лучше, чем мерждить два снапшота? Чем мердж двух историй отличается от ребэйса?
                                                                                                                  Мердж он и есть мердж — две ветки сводятся в одну. Мне кажется это вообще в истории неправильное действие и я пользуюсь ребэйсом.
                                                                                                                  Ветки у меня и в git есть — но я их контролирую самостоятельно, а не на уровне движка. Но не пойму, в чем именно профит?
                                                                                                                  Отсоединенные коммиты — чем плохи? Если я выкачиваю из gerrit что-то чего у меня в истории нет, конечно будет отсоединенный коммит. Ну и пусть будет (это ведь так — не к чему ему привязываться), в чем проблема? А если мне он нужен будет в истории, я его перекину cherri-pick'ом и запушу в одну из веток. Это ведь так и должно быть. Что здесь страшного? Как это решается в мерке? Видимо за счет физических веток выкачивается всегда вся история?
                                                                                                                  А как быть с тем, что мои ветки — это мои ветки. И у каждого программиста так. В git я сам решаю какую локальную ветку в какую удаленную закинуть, сколько веток иметь в какой момент времени и это очень удобно и очень гибко. Это главное в git.
                                                                                                                  Git мне чем-то напоминает регулярные выражения. Когда понимаешь, как он работает, ты можешь делать все, что угодно очень легко и изящно.

                                                                                                                  Вопросы искренние, затронули интересную тему — хочется понять. Есть ощущение, что мерк навязывает тот процесс, который я выстроил в git, но только уже на уровне системы. Это должно быть здорово, но это ведь должно и ограничивать? Не будет той гибкости за которую я так люблю git. Или я неправ?
                                                                                                                  Про мерк слышал много хорошего, но ведь у меня в git все то же самое, разве нет?
                                                                                                                  • +1
                                                                                                                    Что дает отсутствие отсоединенных коммитов?
                                                                                                                    Дело в том, что сама постановка вопроса неверна. Это не в Mercurial «отсоединённые коммиты» отсутствуют, совсем наоборот: единственная DVCS, в которой они есть — Git. Причина существования понятия detached HEAD — в том, что в Гите считаются значимыми только и исключительно те ветви DAG версий репозитория, на которые указывает какой-нибудь ref (branch или tag). В противном случае ветвь считается мусором и будет удалена git-gc. В Mercurial, bzr и прочих негитах все ветки DAG версий равноправны. И это нормально, т.к. проще.
                                                                                                                    • 0
                                                                                                                      Git мне чем-то напоминает регулярные выражения. Когда понимаешь, как он работает, ты можешь делать все, что угодно очень легко и изящно.

                                                                                                                      хорошо сказано! ))
                                                                                                                      • +1
                                                                                                                        Только хрен кто поймёт, что и как ты сделал. Результат есть, а процесс, как говорится, write-only.
                                                                                                                        • 0
                                                                                                                          что означает «вы не понимаете регулярок» :))))))))
                                                                                                                          мдааа… вот уж не предполагал, что и среди DVCS могут быть холивары!