Thursday, January 31, 2013

How to: get nice git history

How to: get nice git history

Problem

There are 3 developers. Each created 1 commit locally.

  • developer 1:
    • git push origin master - OK
  • developer 2:
    • git push origin master - FAIL. 
    • git fetch origin
    • git merge origin master - creates new branch and new commit *
    • git push
  • developer 3:
    • git push origin master - FAIL. 
    • git fetch origin
    • git merge origin master - creates new branch and new commit *
    • git push
*Note: git pull origin master == git fetch origin && git merge origin master

So, result in git history:

By this reason history in our projects often looks like



How to fix it?

Everyone should use git up instead of git pull

To add this useful alias execute:

For Linux/Mac OS
git config --global alias.up '!(git add . && git stash && git pull --rebase >&2) | grep -v "No local changes to save" && git stash pop'

For Windows - UPD
git config --global alias.up 'git pull --rebase'
This command is not so powerful, but works on Windows


It uses git pull --rebase instead of git pull

More details about difference between git pull and git pull --rebase at this link: http://habrahabr.ru/post/161009/ (There is main part of article below - sometimes sites die).


Git Rebase: руководство по использованию tutorial


Rebase — один из двух способов объединить изменения, сделанные в одной ветке, с другой веткой. Начинающие и даже опытные пользователи git иногда испытывают нежелание пользоваться ей, так как не видят смысла осваивать еще один способ объединять изменения, когда уже и так прекрасно владеют операцией merge. В этой статье я бы хотел подробно разобрать теорию и практику использования rebase.

Теория


Итак, освежим теоретические знания о том, что же такое rebase. Для начала вкратце — у вас есть две ветки — master и feature, обе локальные, feature была создана от master в состоянии A и содержит в себе коммиты C, D и E. В ветку master после отделения от нее ветки feature был сделан 1 коммит B.



После применения операции rebase master в ветке feature, дерево коммитов будет иметь вид:



Обратите внимание, что коммиты C', D' и E' — не равны C, D и E, они имеют другие хеши, но изменения (дельты), которые они в себе несут, в идеале точно такие же. Отличие в коммитах обусловлено тем, что они имеют другую базу (в первом случае — A, во втором — B), отличия в дельтах, если они есть, обусловлены разрешением конфликтных ситуаций, возникших при rebase. Об этом чуть подробнее далее.

Такое состояние имеет одно важное преимущество перед первым, при слиянии ветки feature в master ветка может быть объединена по fast-forward, что исключает возникновение конфликтов при выполнении этой операции, кроме того, код в ветке feature более актуален, так как учитывает изменения сделанные в ветке master в коммите B.

Процесс rebase-а детально


Давайте теперь разберемся с механикой этого процесса, как именно дерево 1 превратилось в дерево 2?

Напомню, перед rebase вы находтесь в ветке feature, то есть ваш HEAD смотрит на указатель feature, который в свою очередь смотрит на коммит E. Идентификатор ветки master вы передаете в команду как аргумент:

git rebase master

Для начала git находит базовый коммит — общий родитель этих двух состояний. В данном случае это коммит A. Далее двигаясь в направлении вашего текущего HEAD git вычисляет разницу для каждой пары коммитов, на первом шаге между A и С, назовем ее ΔAC. Эта дельта применяется к текущему состоянию ветки master. Если при этом не возникает конфликтное состояние, создается коммит C', таким образом C' = B + ΔAC. Ветки master и feature при этом не смещаются, однако, HEAD перемещается на новый коммит (C'), приводя ваш репозитарий состояние «отделеной головы» (detached HEAD).


Успешно создав коммит C', git переходит к переносу следующих изменений — ΔCD. Предположим, что при наложении этих изменний на коммит C' возник конфликт. Процесс rebase останавливается (именно в этот момент, набрав git status вы можете обнаружить, что находитесь в состоянии detached HEAD). Изменения, внесенные ΔCD находятся в вашей рабочей копии и (кроме конфликтных) подготовлены к коммиту (пунктиром обозначена stage-зона):


Далее вы можете предпринять следующие шаги:

1. Отменить процесс rebase набрав в консоли

git rebase --abort

При этом маркер HEAD, будет перенесен обратно на ветку feature, а уже добавленные коммиты повиснут в воздухе (на них не будет указывать ни один указатель) и будут вскоре удалены.

2. Разрешить конфликт в вашем любимом merge-tool'е, подготовить файлы к коммиту, набрав git add %filename%. Проделав это со всеми конфликтными файлами, продолжить процесс rebase-а набрав в консоли

git rebase --continue

При этом, если все конфликты действительно разрешены, будет создан коммит D' и rebase перейдет к следующему, в данном примере последнему шагу.

3. Если изменения, сделанные при формировании коммита B и коммита D являются полностью взаимоисключающими, причем «правильные» изменения сделаны в коммите B, то вы не сможете продолжить набрав git rebase --continue, так как разрешив конфликты обнаружите, что изменений в рабочей копии нет. В данном случае вам надо пропустить создание коммита D', набрав команду

git rebase --skip

После применения изменений ΔDE будет создан последний коммит E', указатель ветки feature будет установлен на коммит E', а HEAD станет показывать на ветку feature — теперь, вы находитесь в состоянии на втором рисунке, rebase окончен. Старые коммиты C, D и E вам больше не нужны.


При этом коммиты, созданные в процессе rebase-а, будут содержать данные как об оригинальном авторе и дате изменений (Author), так и о пользователе, сделавшем rebase (Commiter):

commit 0244215614ce6886c9e7d75755601f94b8e19729
Author:     sloot69 <***@****.com>
AuthorDate: Mon Nov 26 13:19:08 2012 +0400
Commit:     Alex <***@****.com>
CommitDate: Mon Nov 26 13:33:27 2012 +0400


С небес на землю — rebase в реальных условиях


На самом деле обычно вы работаете не с двумя ветками, а с четырьмя в самом простом случае: master, origin/master, feature и origin/feature. При этом rebase возможен как между веткой и ее origin-ом, например feature и origin/feature, так и между локальными ветками feature и master.

Rebase ветки с origin-ом


Если вы хотите начать работать с rebase, то лучше всего начать с ребейза своих изменений в ветке относительно ее копии в удаленном репозитарии. Дело в том, что когда вы добавляете коммит, и в удаленном репозитарии добавляется коммит, для объединения изменений по-умолчанию используется merge. Выглядит это примерно так:



Представим умозрительную ситуацию — 3 разработчика активно работают с веткой master в удаленном репозитарии. Делая одновременно комиты на своих машинах они отправляют каждый по 1 изменению в ветку. При этом первый отправляет их без проблем. Второй и третий сталкивается с тем что ветка не может быть отправлена операцией git push origin master, так как в ней уже есть изменения, которые не синхронизированы на локальные машины разработчиков. Оба разработчика (2 и 3) делают git pull origin master, создавая при этом локальные merge-коммиты у себя в репозитарии. Второй делает git push первым. Третий при попытке отправить изменения снова сталкивается с обновлением удаленной ветки и снова делает git pull, создавая еще один merge-коммит. И наконец, третий разработчик делает успешныйgit push origin master. Если удаленный репозитарий расположен например на github, то network, то есть граф коммитов будет иметь следующий вид:

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



Анализ изменений в таком графе неоправданно трудоемкое занятие. Как тут может помочь rebase?
Если вместо git pull origin master выполнить git pull --rebase origin master, то ваши локальные изменения, подобно коммитам C, D и E из первого примера будут перенесены наверх обновленного состояния ветки origin/master, позволяя без создания дополнительных коммитов передать их на удаленный сервер с помощью git push origin master. То есть слияние изменений будет выглядеть уже так:



Как видно, «лишних» merge-коммитов создано не было, ваши изменения в коммите C были использованы для создания коммита C'. Сам же коммит C остался у вас в локальном репозитарии, в удаленный репозитарий был передан только C'. Если все программисты в команде возьмут за правило пользоваться git pull --rebase, тогда каждая из веток в удаленном репозитарии будет выглядеть линейно.

Wednesday, January 23, 2013

Embedding Images into HTML Pages


Just another trick used for the offline page mentioned in this post.  The page should display a number of images (like jQuery UI theme icons) and they have to be completely available after the page is downloaded.  If you save the page from the browser menu it creates the additional folder named MyCoolHtmlPage_files (assuming the page is named MyCoolHtmlPage.html) and places all downloadable from the same server resources there.  JavaScript, CSS files, images - all of them go into that folder.  But I think for downloadable page it is not a good idea to give our users the page itself and all needed support files separately.  So we need to embed all the resources into the page.  It is no problem to embed JavaScript and CSS files as they are just a plain text.  Images are less friendly.

Fortunately all modern browsers support the data URI scheme which allows us to embed the actual image content into the CSS resource URL.  For one of embedded jQuery UI backgrounds this will look like:

.ui-widget-header {
    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSU
hEUgAAAAEAAABkCAYAAABHLFpgAAAALElEQVQYlWN49OjRfyYGBgaGIUT8//8fSqBx0
Yh///4RL8vAwAAVQ2MNOwIAl6g6KkOJwk8AAAAASUVORK5CYII=');
}

Yeah, some effort is needed to convert all used images into Base-64 encoded strings and write appropriate styles to override standard ones but I think it is minor (I just used an online encoder).

Note: Not all browsers support newlines in the url() so it is a good idea to place embedded data on the single line.  I have added newlines to my example just for readability (if the word "readability" is applicable to the Base-64 encoded binary data).

X-Post from: http://dzorin68.blogspot.com/2013/01/embedding-images-into-html-page.html

Looking for JavaScript Decompressor for Data Compressed by C#


It happened that I needed to make an HTML page.  Sometimes such things happen, you know.  That page was required to be completely offline, just a snapshot of some piece of data being downloaded by users for further analysis.  At the same time that offline page was required to be as interactive as the online page with the same data.

The online page for that data contained grid with "master" rows and upon clicking on the row it presented a dialog box with "details" loaded from the server by AJAX request.  The offline page could not use AJAX so it should contain all needed data, both "master" part and all the "details", inside it.  The average amount of data used on these pages was not very small - about 3 megabytes in JSON format, so it was decided to compress that data on the server side and decompress needed pieces on the fly when they should come into the dialog.

The code for the server side was not very complex.  As the web application uses ASP.NET MVC and is written on C# I could use standard .NET classes for compression like either System.IO.Compression.DeflateStream or System.IO.Compression.GZipStream:


var details = new Dictionary<stringstring>();
foreach (DataRow row in detailsData.Rows)
{
    var text = Convert.ToString(row["details_content"]);
    var bytes = Encoding.UTF8.GetBytes(text);
    using (var ms = new MemoryStream())
    {
        using (var zipStream = new DeflateStream(ms, CompressionMode.Compress))
        {
            zipStream.Write(bytes, 0, bytes.Length);
            zipStream.Flush();
        }
        details[Convert.ToString(row["master_id"])] = Convert.ToBase64String(ms.ToArray());
    }
}
viewModel.ZippedResultsJson = EncoderHelper.SerializeToJsonString(details);

But what to do on the page?  Are there any JavaScript implementations of, say, inflaters?  Initially I thought that these days there are lots of them as probably almost all things are already implemented in JS.  But when I tried to look for them I've found that things are not that good.  Actually there are just a few implementations of deflate/inflate algorithms and not all of them look working.  Thanks StackOverflow, they know answers to almost all our questions and I've found lots of answers how to get some of JS decompressors to work with Java or Python server-side compression.

But just a pair of questions about C#-JS were answered like "just use the browser gzip support", i.e. encode the whole response.  This is not my case as I don't have response so I tried to use some of implementations to see which of them is more usable.  Things were worse than I expected, no one of relatively small inflate implementations worked with C#'s DeflateStream-compressed data (although they worked quite fine with data compressed by themselves).

So I decided to try gzip instead of the "raw" deflate.  And I've found the only JS implementation of gzip decompressor on http://jsxgraph.uni-bayreuth.de/wp/2009/09/29/jsxcompressor-zlib-compressed-javascript-code/.  I tried it and it worked!  (Of course, I've changed the DeflateStream to the GZipStream on the server side).  Lots of thanks to those guys who created that really working tool!  With it all I needed on the page was just:

$('#details-div').html(JXG.decompress(detailsData[masterId]));

And that's it!  Simple, usable, great!  And it does Base-64 decoding inside so there is no need to use separate decoding tools or libraries.  Great job guys!  Now I know the right tool for this task.

X-Post from: http://dzorin68.blogspot.com/2013/01/looking-for-javascript-decompressor-for.html