Pull to refresh

Comments 47

«SVN, где ветвление было достаточно дорогим процессом: для создания ветки нужно было скопировать весь рабочий каталог»
Вовсе не обязательно. SVN управляет отдельными файлами и папками, можно ветвить конкретный фолдер — никто не запрещает. Хотя конечно это не особо нужно.

«В Git все проще: создание ветки подразумевает лишь создание нового указателя на определенный коммит в папке»
В SVN ничем не хуже:
svnbook.red-bean.com/en/1.7/svn.branchmerge.using.html
«Copying a directory on the server, however, is a constant-time operation»
«Subversion's repository has a special design. When you copy a directory, you don't need to worry about the repository growing huge—Subversion doesn't actually duplicate any data. Instead, it creates a new directory entry that points to an existing tree»
Может быть, мое знакомство с SVN закончилось достаточно рано для постижения некоторых истин, я никогда не считал себя знатоком этой системы, но если я усвоил правильно, тут именно что «on the server». При создании ветки на рабочем компьютере, выполняя копирование всего trunk'а или нужной его части в папку branches. Или я ошибаюсь?
> именно что «on the server».
Так и что же? В SVN-е все по сути on the server, никто локально репозитарий не держит. Локально у вас working copy.

Working copy можно скопировать локально на своей машине, и потом вкоммитать в отдельный фолдер для бренча, но это странный и ненужный способ.

Обычно вы говорите серверу сделать копию фолдера (например /trunk в /branches/myBranch), а потом через switch (про который написали ниже) переключаете свой working copy с /trunk на /branches/myBranch. Все просто.
Это ясно, что в SVN практически все происходит в центральном репозитории, но тут вопрос другой: какова нагрузка на файловую систему рабочего компьютера пользователя при создании им ветки. В Git создается файл размером 40 байт, который содержит ссылку на коммит, а в SVN все-таки нужно создать в branches копию файлов, над которыми предполагается работа.

То есть, при создании /branches/myBranch, файлы туда все-таки должны загрузиться, не пропадая из папки trunk. То есть, копии будут созданы.
> нужно создать в branches копию файлов, над которыми предполагается работа
Да нет же, не нужно! Я же скинул вам цитату!

Локально вы просто не чекаутите branches, а держите все в одном working copy.

На сервере «Subversion doesn't actually duplicate any data. Instead, it creates a new directory entry that points to an existing tree», и тречит свои диффы от этого места.

> файлы туда все-таки должны загрузиться
Не должно ничего никуда «загружаться» — опять же см. цитату из документации.
В svn все ветки — это обычные папки относительно корня репозитория и при создании ветки, грубо говоря, делается просто svn copy в папку /branches. Разумеется, при этом никакие файлы никуда физически не копируются и далее как обычно просто инкрементальные изменения от этого локального среза этой конкретной версии файла.
Я все это написал еще в первом своем комментарии. К чему капитанство?

Речь о том что если человек зачекаутил фолдер trunk, а потом сделал switch на branches/какойТоБренч, то никаких дополнительных расходов в локальной ФС не будет (ведь он сам писал — «вопрос другой: какова нагрузка на файловую систему рабочего компьютера»).

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

з.ы. а про локальные копии — в общем-то, да, всё верно. Но тут и в git так же по сути работает в локальной части то.
Вероятно, я чего-то не понимаю, но в моем представлении работа с ветками в SVN происходит следующим образом. У нас на рабочем компьютере есть репозиторий с папками trunk, branches, tags — копия центрального репозитория. Для создания ветки выполняем:

svn copy trunk branches/myBranch
Результатом будет полное рекурсивное копирование папки trunk в папку branches/myBranch. И если trunk большой, копирование будет долгим.

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

svn commit -m 'add branch myBranch'

Сервер теперь знает о нашей ветке. В репозитории на сервере файлы, конечно же, не дублируются, но у нас теперь две копии рабочего каталога: в папке trunk и в папке branches/myBranch.

Разве не так это происходит на рабочем компьютере? Если я описал процесс неправильно, очень хотелось бы узнать, как это происходит на самом деле.
> У нас на рабочем компьютере есть репозиторий с папками trunk, branches, tags — копия центрального репозитория.
Вам не обязательно держать все это локально. После того как вы создали папки trunk, branches, tags на сервере, можете смело оставить локально только trunk. Это будет ваш working copy.

> svn copy trunk branches/myBranch
> Результатом будет полное рекурсивное копирование
Так вам же сказано — копируйте на сервере. А вы пишете опять про локальное копирование. Зачем? На сервере же, на сервере. Еще раз:! копируйте на сервере!
Почему вы постоянно возвращаетесь к копированию локально? Я не понимаю.

Копирование на сервере:
Допустим ваш репозитарий находится на http://svn.myrepo.com/
Делайте svn copy http://svn.myrepo.com/trunk http://svn.myrepo.com/branches/myBranch
Готово.

Потом в своем фолдере где у вас был trunk делаете svn switch http://svn.myrepo.com/branches/myBranch .
Все, готово.

P.S. Но, даже если вы уже почему-то сделали копию локально — просто сотрите локальную копию trunk, и работайте с копией branches/myBranch. И место на диске, которое вас так беспокоило, освободится.
Теперь ясно: чтобы облегчить копирование, нужно делать копирование на сервере и копировать не весь репозиторий. Но все-таки это не совсем те легковесные ветки, которые предоставляет Git, когда можно позволить себе создать ветку на пару коммитов для фикса маленького бага, а потом слить его с основной веткой с параметром squash, даже не отправляя на сервер.
> можно позволить себе создать ветку на пару коммитов для фикса маленького бага, а потом слить его с основной веткой с параметром squash, даже не отправляя на сервер
Как сказать. Фактически ваша машина и есть сервер (-:
P.S. Я вам зато другой пример могу привести в котором GIT гораздо накладнее нежели SVN.
Особое отличие GIT-а в том что он тречит только полностью состояние всего репозитария, но не работает с отдельными файлами/каталогами. В каком-то смысле это его ахиллесова пята.

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

А в SVN легко можно было-бы вычекаутить конкретную папку с конкретным апликейшном, подфиксать его, вкомитать, и даже завести папки с бренчами для этого отдельного апликейшна. Такие дела.
> Когда-то сам Торвальдс говорил
На всякий случай, пруфлинк: lwn.net/Articles/246381/

As you are probably aware, some people have tried to import the whole KDE
history into git. Quite frankly, the way git works (tracking whole trees
at a time, never single files), that ends up being very painful, because
it's an «all or nothing» approach.

So I'm hoping that if you guys are seriously considering git, you'd also
split up the KDE repository so that it's not one single huge one, but with
multiple smaller repositories (ie kdelibs might be one, and each major app
would be its own), and then using the git «submodule» support to tie it
all together.

В этом плане да. Но такова философия Git — он распределенный, на каждом компьютере лежит полноценный репозиторий проекта. В некоторых случаях — когда надо срочно внести небольшую правку в большой проект, — это минус. В некоторых — наоборот, неоценимый плюс. Например, при работе на плохом сетевом канале или при потере центрального репозитория (пришлось мне как-то столкнуться с такой трагедией).
Философия философией, а копии всей истории всего репозитария у каждого работника на проекте это настоящий «не философский» оверхед по нагрузке на файловую систему, которая вас так беспокоила при работе с SVN, но совершенно не беспокоит при работе с GIT. Ну да — ведь GIT это круто и модно, ему можно. А SVN — отсталая ерунда, ему нелья. Так что ли? ((-:

Полные копии репозитария у всех оно, конечно, здорово — на случай ядерной войны например (-:
Но компаниям всегда лучше иметь авторитетный источник — центральный сервер, с своими бекапами. А не полагаться на то, что каждый работник будет постоянно у себя держать правильный бекап всего репозитария (а вдруг у них только пара submodule останутся?).

Я не знаю, может у вас и бывает что теряются центральные репозитарии — это, понятное дело, трагедия. Но ведь это событие из ряда вон выходящее. Это то, чего не должно быть никогда. Бекапы надо делать с центрального репо, и в отдельном здании хранить (на случай пожара). Что, в принципе, очевидно.
Под нагрузкой я имел в виду объем дискового ввода-вывода во время создания ветки. Конечно, я рассматривал случай локального создания ветки в SVN.

И я отнюдь не утверждаю, что SVN — отсталая и какая-то еще плохая. Как я упомянул ниже, я вспоминаю ее и добрым словом )
Я понял почти все слова, кроме «тречит». Что это значит?
Да, переборщил я малось с жаргоном.
Тречит — от слова трекинг (tracking). «Тречит диффы» = отслеживает изменения.
я перешел на Git с SVN, где ветвление было достаточно дорогим процессом: для создания ветки нужно было скопировать весь рабочий каталог.
поясните тем, кто знает про svn switch?
Уберите упоминания svn и будет отличная статья про стратегии мерджа.
Не стоит воспринимать мои слова относительно SVN как низкую оценку этой системы. Это просто личный опыт. Причем, мне и добрым словом вспомнить SVN есть за что.

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

Я, кстати говоря, сталкивался с подобной проблемой. Конечно, к сожалению, мне не доводилось работать в проектах уровня Badoo, но как-то раз я присоединился к проекту, где рабочей веткой была version-2, потому что никто не знал, как разрешить конфликты при слиянии с master'ом. Но тут все решилось просто, как по учебнику: стратегия ours. Как раз были откинуты коммиты мастера после ответвления version-2, в том числе те, которые все сломали.
Для меня всегда было проблемой, что squash commit не делает струкутрных связей, так что если мы объединили изменения второй ветки в основную, а потом продолжили работу над второй веткой, мы вообще теряем информацию, что в этой второй ветке сквошмерджилось, а что — нет (только в описании сквош-коммита остается текстовая инфа). И далее при классическом мердже этой ветки, то есть «новых» изменений, в историю также попадают ранее смердженные сквошем коммиты, таким образом в общей истории получаем дубль: отдельные говнокоммиты, и их squash-merge.

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

С горе-разработчиками, которые любят «нагадить в коммитах» очень актуально.

Может есть какая-то фича гита, которая делает именно так как я хочу? Кто как с этим борется? :)
А чем Вам тогда мердж коммит не подходит? Отрибейсите топик-ветку на красивые коммиты перед мерджем и будет красивый мердж коммит.
Если ее отrebase'ить, то ее нельзя будет заpushить. А если заpushить с --force — тогда у горе-разработчиков работа вообще стает :)

Может я не знаю, и ее можно как-то ее отребейзить на сервере «прозрачно» для разработчика, т.е. чтобы у него все автоматом обновилось без лишних вопросов?
Тут процесс дисциплины в комманде. Если каждый человек будет рибейсить свои коммиты с ветки перед отправкой на удаленный репозиторий, то история будет красивая сразу. Избегать мердж коммита ради того, чтобы его не было в истории как-то странно.
Вы не поняли. Я избегаю не мердж-коммита, а неугодных коммитов в истории.

То есть вот имеется:
master       A -> B -> C
shitcommits       \ -> D -> E 


Т.к. D и E — «не красивая история», делаем squash merge:
master       A -> B -> C -> D+E(squash)
shitcommits       \ -> D -> E 


История мастера красивая: A->B->C->D+E

Но тем временем разработчик продолжает работать над своей веткой.

master       A -> B -> C -> D+E(squash)
shitcommits       \ -> D -> E -> F


И тут проблемы во всех случаях:
1. Мы забыли про сквош и сделали обычный merge, получаем полную хрень истории главной A -> B -> C -> D -> E -> D+E -> F
2. Мы снова делаем squash commit, и если мы еще и улучшали его код после предыдущего сквоша, получаем конфликты

Если же на шаге создания D+E сделать rebase -i ветки разработчика:
master       A -> B -> C -> (merge_shitcommits_r)
shitcommits      \ -> D+E -> /
shitcommits[0]   \ -> D -> E


То нам необходимо как-то «переопределить» разработчика на новую ребейзнутую ветку, с «перетертой» историей. Простой пуш ребейзнутых веток сами понимаете к чему приводит…

В общем, вот ищу выход из данной ситуации, но так, чтобы это было прозрачно для разработчика, который знает только пару команд гит.
Не нашлось решения?
Красивого и прозрачного — нет. Только «вручную» сообщить разработчику о необходимости переключиться на такую-то ветку сразу после выполнения мерджа. Судя по всему, гит описанного юзкейса не предусматривает. :(
Я бы очень хотел услышать в тонкостях про причины появления и, соответственно, избегания наличия пустых комитов в истории
Merge branch 'feature_branch' into develop
На самом деле эти коммиты не пустые :). Они «пустые» в случае отсутствия конфликтов при мерже. Ваш вопрос, к сожалению, обычно приводит к холивору «merge vs. rebase», который ничем хорошим не заканчивается :).
Они «пустые» в случае отсутствия конфликтов при мерже.

По-моему они пустые всегда при git checkout develop && git merge --no-ff feature_branch && git push.

Ваш вопрос, к сожалению, обычно приводит к холивору «merge vs. rebase»

Я не думаю что rebase тут при чем. Проблема эта локального репозитория, соответственно, можно обойтись наверняка каким-нибудь no-commit при мердже и т.п.

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

(develop)$ vim test.php

(develop)$ cat test.php
<?php
echo 'Hello world';

(develop)$ git add test.php 

(develop)$ git commit -m 'One commit'
[develop ad4834f] [develop]: One commit
 1 file changed, 2 insertions(+)
 create mode 100644 test.php

(develop)$ git checkout feature
Switched to branch 'feature'

(feature)$ vim test.php

(feature)$ cat test.php
<?php

echo 'Hello world (from feature)';

(feature)$ git add test.php

(feature)$ git commit -m 'Feature commit'
[feature c996a10] Feature commit
 1 file changed, 3 insertions(+)
 create mode 100644 test.php

(feature)$ git checkout develop
Switched to branch 'develop'

(develop)$ git merge feature
Auto-merging test.php
CONFLICT (add/add): Merge conflict in test.php
Automatic merge failed; fix conflicts and then commit the result.
(develop|MERGING)$ git status
# On branch develop
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#
#	both added:         test.php
#
no changes added to commit (use "git add" and/or "git commit -a")

(develop|MERGING)$ git diff test.php
diff --cc test.php
index bd1ca6f,1001057..0000000
--- a/test.php
+++ b/test.php
@@@ -1,2 -1,3 +1,7 @@@
  <?php
++<<<<<<< HEAD
 +echo 'Hello world';
++=======
+ 
+ echo 'Hello world (from feature)';
++>>>>>>> feature

(develop|MERGING)$ vim test.php

(develop|MERGING)$ git status
# On branch develop
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#
#	both added:         test.php
#
no changes added to commit (use "git add" and/or "git commit -a")

(develop|MERGING)$ cat test.php
<?php
echo 'Hello world (from feature)';

(develop|MERGING)$ git add test.php

(develop|MERGING)$ git commit
[develop 8355f7c] [develop]: Merge branch 'feature' into develop

(develop)$ git log -p
commit 8355f7c42f619c46ba448732d1bc27302d4f8d73
Merge: ad4834f c996a10
Author: Yuriy Nasretdinov <...>
Date:   Mon Sep 30 19:38:02 2013 +0400

    [develop]: Merge branch 'feature' into develop
    
    Conflicts:
        test.php
... # якобы нет изменений, «пустой» коммит

(develop)$ git show
commit 8355f7c42f619c46ba448732d1bc27302d4f8d73
Merge: ad4834f c996a10
Author: Yuriy Nasretdinov <y.nasretdinov@corp.badoo.com>
Date:   Mon Sep 30 19:38:02 2013 +0400

    [develop]: Merge branch 'feature' into develop
    
    Conflicts:
        test.php

diff --cc test.php
index bd1ca6f,1001057..614ad2b
--- a/test.php
+++ b/test.php
@@@ -1,2 -1,3 +1,3 @@@
  <?php
- echo 'Hello world';
 -
+ echo 'Hello world (from feature)';
++


То есть, несмотря на то, что в «git log -p» изменений не видно, в «git show <коммит>» изменения всё же покажутся, причём именно то, что я менял.
Я просто оставлю это здесь.
(feature)$ git show
commit 504e36a28a81c175518ca4b478e28d93e3146df8
Merge: c278018 5654da6
Author: Evgeniy Makhrov <e.makhrov@corp.badoo.com>
Date:   Thu Oct 31 03:12:01 2013 +0400

    Merge branch 'bugfix' into feature

    Conflicts:
        test.php

(feature)$ git show -m
commit 504e36a28a81c175518ca4b478e28d93e3146df8 (from 5654da6fa73e780b9d9ecb06b3a3b90f6707b9c2)
Merge: c278018 5654da6
Author: Evgeniy Makhrov <e.makhrov@corp.badoo.com>
Date:   Thu Oct 31 03:12:01 2013 +0400

    Merge branch 'bugfix' into feature

    Conflicts:
        test.php

diff --git a/test.php b/test.php
index 51de675..8fc81b9 100644
--- a/test.php
+++ b/test.php
@@ -1,4 +1,4 @@
 <?php

-echo "Hello, world\n";
+echo "Hello from feature\n";

(feature)$ git log -1 -p
commit 504e36a28a81c175518ca4b478e28d93e3146df8
Merge: c278018 5654da6
Author: Evgeniy Makhrov <e.makhrov@corp.badoo.com>
Date:   Thu Oct 31 03:12:01 2013 +0400

    Merge branch 'bugfix' into feature

    Conflicts:
        test.php
(feature)$ git log -1 -p -m
commit 504e36a28a81c175518ca4b478e28d93e3146df8 (from 5654da6fa73e780b9d9ecb06b3a3b90f6707b9c2)
Merge: c278018 5654da6
Author: Evgeniy Makhrov <e.makhrov@corp.badoo.com>
Date:   Thu Oct 31 03:12:01 2013 +0400

    Merge branch 'bugfix' into feature

    Conflicts:
        test.php

diff --git a/test.php b/test.php
index 51de675..8fc81b9 100644
--- a/test.php
+++ b/test.php
@@ -1,4 +1,4 @@
 <?php

-echo "Hello, world\n";
+echo "Hello from feature\n";
git merge --no-ff

Вы кажется сами только что ответили на свой вопрос, почему они создаются. Вы наглухо запрещаете Fast-Forward стратегию, которая как раз позволяет избежать этих коммитов.
Почему они создаются я понимаю в этом случае, а почему они пустые я не понимаю. При no-ff я хочу видеть этот комит со всеми diff которые составляют дельту между предыдущим комитом:

A->B->C-->(E)
    \     /
     ->D->


где (E) = diff(C, D)
«git log -p -m» вам всё покажет. Только, уверяю вас, лучше на это не смотреть :).
Даже если изменений в них как таковых нет (вызванных конфликтами), они не пустые. Пустым коммит может быть только если он полностью дублирует какой-то предыдущий. Рассмотрим ситуацию:

A->B->C-->(E)
    \     /
     ->D->


в C у нас добавился файл C.txt, в D — соответственно D.txt

Ведь состояние рабочего дерева в E отличается и от C и от D. Значит он не пустой, что, в свою очередь, означает, что это состояние как-то необходимо адресовать, к примеру, для ветвления от него (ветвление в данном случае от C или D не будет тождественно, т.к. в первом случае не будет файла D.txt, а во втором — C.txt).

Отсутствие пустого возможно только в случае fast-forward, то есть если изменения были лишь в одной ветке после ветвления — в этом случае действительно «форсированный» --no-ff сделает «пустой» коммит, где рабочее дерево полностью тождественно дереву из предыдущего коммита.
В каком же случае проявляется недостаток стратегии resolve? Он проявляется в том случае, если для коммита (M) нам пришлось разрешить конфликты, после чего мы продолжили разработку и еще раз хотим выполнить git merge feature -s resolve. В этом случае в качестве общего предка снова будет использован коммит C, и конфликты произойдут снова и будут нуждаться в нашем вмешательстве.

В этой ситуации может помочь rerere.
UFO just landed and posted this here
Скажите пожалуйста, правильно ли я понял, что можно использовать рекурсивную стратегию слияния при ребейзе ветки, чтобы повторно не разрешать конфликты?
Отвечает ggray:
мне кажется что рекурсивная стратегия никак не связана с решениме конфликтов при ребейзе, а автору я бы посоветововал включить rerere если у него часто такие кейсы возникают :)
Благодаря упоминанию — смог залогиниться.

rerere как раз и позволяет решать повторяемые конфликты, стратегии лишь позволяют уменьшить их появления.
Sign up to leave a comment.

Articles