...

суббота, 17 октября 2015 г.

[Перевод] Объекты нулевого размера

В чём разница между следующими парами длин и указателей?
size_t len1 = 0;
char *ptr1 = NULL;

size_t len2 = 0;
char *ptr2 = malloc(0);

size_t len3 = 0;
char *ptr3 = (char *)malloc(4096) + 4096;

size_t len4 = 0;
char ptr4[0];

size_t len5 = 0;
char ptr5[];

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

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

malloc(0)


Поведение malloc(0) определено стандартами. Можно вернуть нулевой или уникальный указатель. Второй вариант во многих реализациях выполняется внутренним увеличением длины на единицу (которая затем обычно округляется до 16). По правилам, разыменовать такой указатель нельзя, но обычно несколько байт всё-таки размещаются, и поэтому такая программа не упадёт.

Возврат NULL приводит к возможности возникновения интересного бага. Часто возврат NULL из malloc расценивается как ошибка.

if ((ptr = malloc(len)) == NULL)
        err(1, "out of memory");

Если len равна нулю, это приведёт к неправомерному сообщению об ошибке – если не добавить дополнительную проверку && len != 0. Также можно вступить в секту адептов «непроверки malloc».

В OpenBSD malloc обрабатывает ноль по-другому. Размещение данных нулевого размера возвращает куски страниц, которые были защищены через mprotect() с ключом PROT_NONE. Попытка разыменовать такой указатель приведёт к падению.

Отметим, что требования к уникальным указателям запрещают «мухлевать» при их использовании.

int thezero;

void *
malloc(size_t len)
{
        if (len == 0) return &thezero;
}
void
free(void *ptr)
{
        if (ptr == &thezero) return;
}

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

Другие случаи


Если malloc не выдаст ошибку, то варианты 3, 4 и 5 в большинстве случаев работают идентично. Основное отличие будет в использовании sizeof(ptr) / sizeof(ptr[0]), например в цикле. Это приведёт к неверному ответу, правильному ответу или вообще ни к чему не приведёт, обломавшись на этапе компиляции. 4-й вариант не разрешён стандартом, но компиляторы, скорее всего, его проглотят.

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

null-объекты


Вернёмся к первому варианту и нулевым объектам. Рассмотрим следующий вызов:
memset(ptr, 0, 0);

0 байт ptr присваиваем 0. Какие из пяти перечисленных указателей позволят сделать такой вызов? 3, 4 и 5. 2-й – если это уникальный указатель. Но что, если ptr это NULL?

В стандарте С в разделе «Использование библиотечных функций» сказано:

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


В разделе «Соглашения по функциям работы со строками» уточняется:

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


Судя по всему, результат memset'а 0 байт на NULL будет неопределённым. В документации по memset, memcpy и memmove не указано, что они могут принимать нулевые указатели. В качестве контрпримера можно привести описание snprintf, в котором сказано: «Если n равен нулю, ничего не записывается, и s может быть нулевым указателем». Документация к функции read из POSIX похожим образом описывает, что чтение нулевой длины не считается ошибкой, но реализация может проверить другие параметры на ошибку – например, на недопустимые буферные указатели.

А что на практике? Самый простой способ обработки нулевой длины в функциях типа memset или memcpy – не входить в цикл и ничего не делать. Обычно в С неопределённое поведение вызывает какую-либо реакцию, но в данном случае уже определено, что с нормальными указателями ничего не происходит. Проверка на ненормальность указателей была бы лишней работой.

Проверка на ненулевые, но недопустимые указатели, довольно сложна. memcpy этим вообще не занимается, позволяя программе просто упасть. Вызов read тоже ничего не проверяет. Он делегирует проверку функции copyout, которая заводит хэндлер для обнаружения ошибок. И хотя можно добавить проверку на null, такие указатели не более недопустимы для этих функций, нежели 0x1 или 0xffffffff, для которых нет никакой особой обработки.

Облом


На практике это означает наличие большого количества кода, подразумевающего (специально или случайно), что нулевые указатели и нулевая длина допустимы. Я решил провести эксперимент, добавив в memcpy вывод ошибок и выход, в случае, если указатель оказывается NULL, и установил новую libc.
Feb 11 01:52:47 carbolite xsetroot: memcpy with NULL
Feb 11 01:53:18 carbolite last message repeated 15 times

Нда, это не отняло много времени. Интересно, что он там делает:

Feb 11 01:53:18 carbolite gdb: memcpy with NULL
Feb 11 01:53:19 carbolite gdb: memcpy with NULL

Ясно, понятно. Эти сообщения, похоже, очень быстро надоедят. Возвращаем всё, как было.

Последствия


Я занялся эти вопросом, поскольку на пересечении областей «не определено, но должно работать» и оптимизации компиляторов С не происходит ничего хорошего. Умный компилятор может увидеть вызов memcpy, отметить оба указателя, как допустимые, и убрать проверки на null.
int backup;
void
copyint(int *ptr)
{
        size_t len = sizeof(int);
        if (!ptr)
                len = 0;
        memcpy(&backup, ptr, len);
}

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

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

Поначалу мне не удавалось убедить компилятор удалять проверку на ноль после «разыменования» memcpy, но это не значит, что этого не может случиться. gcc 4.9 говорит, что эта проверка будет удалена оптимизацией. В OpenBSD пакет gcc 4.9 (содержащий множество патчей) не удаляет по умолчанию проверку, даже при –O3, но если разрешить "-fdelete-null-pointer-checks", это приводит к удалению проверок. Не знаю, что насчёт clang – первые тесты показывают, что не удаляет, но гарантий нет. В теории он тоже сможет провести такую оптимизацию.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Как посчитать всё на свете одним SQL-запросом. Оконные функции PostgreSQL


Я с удивлением обнаружил, что многие разработчики, даже давно использующие postgresql, не понимают оконные функции, считая их какой-то особой магией для избранных. Ну или в лучшем случае «копипастят» со StackOverflow выражения типа «row_number() OVER ()», не вдаваясь в детали. А ведь оконные функции — полезнейший функционал PostgreSQL.
Попробую по-простому объяснить, как можно их использовать.
Для начала хочу сразу пояснить, что оконные функции не изменяют выборку, а только добавляют некоторую дополнительную информацию о ней. Т.е. для простоты понимания можно считать, что postgres сначала выполняет весь запрос (кроме сортировки и limit), а потом только просчитывает оконные выражения.
Синтаксис примерно такой:
функция OVER окно


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

Например, в этом селекте к обычным полям id, header и score просто добавится нумерация строк.

SELECT
    id,
    section,
    header,
    score,
    row_number() OVER ()  AS num
FROM news;

 id | section |  header   | score | num 
----+---------+-----------+-------+-----
  1 |       2 | Заголовок |    23 |   1
  2 |       1 | Заголовок |     6 |   2
  3 |       4 | Заголовок |    79 |   3
  4 |       3 | Заголовок |    36 |   4
  5 |       2 | Заголовок |    34 |   5
  6 |       2 | Заголовок |    95 |   6
  7 |       4 | Заголовок |    26 |   7
  8 |       3 | Заголовок |    36 |   8


В оконное выражение можно добавить ORDER BY, тогда можно изменить порядок обработки.

SELECT
    id,
    section,
    header,
    score,
    row_number() OVER (ORDER BY score DESC)  AS rating
FROM news
ORDER BY id;

 id | section |  header   | score | rating 
----+---------+-----------+-------+--------
  1 |       2 | Заголовок |    23 |      7
  2 |       1 | Заголовок |     6 |      8
  3 |       4 | Заголовок |    79 |      2
  4 |       3 | Заголовок |    36 |      4
  5 |       2 | Заголовок |    34 |      5
  6 |       2 | Заголовок |    95 |      1
  7 |       4 | Заголовок |    26 |      6
  8 |       3 | Заголовок |    36 |      3



Обратите внимание, что я добавил еще и в конце всего запоса ORDER BY id, при этом рейтинг посчитан все равно верно. Т.е. посгрес просто отсортировал результат вместе с результатом работы оконной функции, один order ничуть не мешает другому.

Дальше — больше. В оконное выражение можно добавить слово PARTITION BY [expression],
например row_number() OVER (PARTITION BY section), тогда подсчет будет идти в каждой группе отдельно:

SELECT
    id,
    section,
    header,
    score,
    row_number() OVER (PARTITION BY section ORDER BY score DESC)  AS rating_in_section
FROM news
ORDER BY section, rating_in_section;

 id | section |  header   | score | rating_in_section 
----+---------+-----------+-------+-------------------
  2 |       1 | Заголовок |     6 |                 1
  6 |       2 | Заголовок |    95 |                 1
  5 |       2 | Заголовок |    34 |                 2
  1 |       2 | Заголовок |    23 |                 3
  4 |       3 | Заголовок |    36 |                 1
  8 |       3 | Заголовок |    36 |                 2
  3 |       4 | Заголовок |    79 |                 1
  7 |       4 | Заголовок |    26 |                 2



Если не указывать партицию, то партицией является весь запрос.

Тут сразу надо немного сказать о функциях, которые можно использовать, так как есть очень важный нюанс.
В качестве функции можно использовать, так сказать, истинные оконные функции из мануала — это row_number(), rank(), lead() и т.д., а можно использовать функции-агрегаты, такие как: sum(), count() и т.д. Так вот, это важно, агрегатные функции работают слегка по-другому: если не задан ORDER BY в окне, идет подсчет по всей партиции один раз, и результат пишется во все строки (одинаков для всех строк партиции). Если же ORDER BY задан, то подсчет в каждой строке идет от начала партиции до этой строки.

Давайте посмотрим это на примере. Например, у нас есть некая (сферическая в вакууме) таблица пополнений балансов.

SELECT
    transaction_id,
    change
FROM balance_change 
ORDER BY transaction_id;

 transaction_id | change 
----------------+--------
              1 |   1.00
              2 |  -2.00
              3 |  10.00
              4 |  -4.00
              5 |   5.50



и мы хотим узнать заодно, как менялся остаток на балансе при этом:
SELECT
    transaction_id,
    change,
    sum(change) OVER (ORDER BY transaction_id) as balance
FROM balance_change 
ORDER BY transaction_id;

 transaction_id | change | balance 
----------------+--------+---------
              1 |   1.00 |    1.00
              2 |  -2.00 |   -1.00
              3 |  10.00 |    9.00
              4 |  -4.00 |    5.00
              5 |   5.50 |   10.50



Т.е. для каждой строки идет подсчет в отдельном фрейме. В данном случае фрейм — это набор строк от начала до текущей строки (если было бы PARTITION BY, то от начала партиции).

Если же мы для агрегатной фунции sum не будем использовать ORDER BY в окне, тогда мы просто посчитаем общую сумму и покажем её во всех строках. Т.е. фреймом для каждой из строк будет весь набор строк
от начала до конца партиции.

SELECT
    transaction_id,
    change,
    sum(change) OVER () as result_balance
FROM balance_change
ORDER BY transaction_id;

 transaction_id | change | result_balance 
----------------+--------+----------------
              1 |   1.00 |          10.50
              2 |  -2.00 |          10.50
              3 |  10.00 |          10.50
              4 |  -4.00 |          10.50
              5 |   5.50 |          10.50



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

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

SELECT
    transaction_id,
    change,
    sum(change) OVER (ORDER BY transaction_id) as balance,
    sum(change) OVER () as result_balance,
    round(
        100.0 * sum(change) OVER (ORDER BY transaction_id)  /  sum(change) OVER (),
        2
    ) AS percent_of_result,
    count(*) OVER () as transactions_count
FROM balance_change
ORDER BY transaction_id;

 transaction_id | change | balance | result_balance | percent_of_result | transactions_count 
----------------+--------+---------+----------------+-------------------+--------------------
              1 |   1.00 |    1.00 |          10.50 |              9.52 |                  5
              2 |  -2.00 |   -1.00 |          10.50 |             -9.52 |                  5
              3 |  10.00 |    9.00 |          10.50 |             85.71 |                  5
              4 |  -4.00 |    5.00 |          10.50 |             47.62 |                  5
              5 |   5.50 |   10.50 |          10.50 |            100.00 |                  5



Если у вас много одинаковых выражений после OVER, то можно дать им имя и вынести отдельно с ключевым словом WINDOW, чтобы избежать дублирования кода. Вот пример из мануала:
SELECT
    sum(salary) OVER w,
    avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);


Здесь w после слова OVER идет без уже скобок.

Результат работы оконной функции невозможно отфильтровать в запросе с помощью WHERE, потому что оконные фунции выполняются после всей фильтрации и группировки, т.е. с тем, что получилось. Поэтому чтобы выбрать, например, топ 5 новостей в каждой группе, надо использовать подзапрос:

SELECT *
FROM (
    SELECT
        id,
        section,
        header,
        score,
        row_number() OVER (PARTITION BY section ORDER BY score DESC)  AS rating_in_section
    FROM news
    ORDER BY section, rating_in_section
) counted_news
WHERE rating_in_section <= 5;

Еще пример для закрепления. Помимо row_number() есть несколько других функций. Например lag, которая ищет строку перед последней строкой фрейма. К примеру мы можем найти насколько очков новость отстает от предыдущей в рейтинге:

SELECT
    id,
    section,
    header,
    score,
    row_number() OVER w        AS rating,
    lag(score) OVER w - score  AS score_lag
FROM news
WINDOW w AS (ORDER BY score DESC)
ORDER BY score desc;

 id | section |  header   | score | rating | score_lag 
----+---------+-----------+-------+--------+-----------
  6 |       2 | Заголовок |    95 |      1 |          
  3 |       4 | Заголовок |    79 |      2 |        16
  8 |       3 | Заголовок |    36 |      3 |        43
  4 |       3 | Заголовок |    36 |      4 |         0
  5 |       2 | Заголовок |    34 |      5 |         2
  7 |       4 | Заголовок |    26 |      6 |         8
  1 |       2 | Заголовок |    23 |      7 |         3
  2 |       1 | Заголовок |     6 |      8 |        17


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

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

пятница, 16 октября 2015 г.

Создание космической браузерной игры. Первый опыт

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

В качестве вступления


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

Идея создания космической игры родилась довольно давно. Почему космической? Отчасти потому, что космическая тема всегда вызывала мой личный интерес, а отчасти, по причине ностальгических воспоминаний от приятного времяпрепровождения за «Космическими рейнджерами», когда-то в далеком прошлом…

На чем писать?


Так как основной вид моей деятельности — это веб-разработка, выбор пал на HTML+JavaScript в клиентской части, и PHP+MySQL в серверной. Возможно, этот вопрос — тема бесконечных дискуссий, однако далее рассказ пойдет о разработке именно на этих языках. Конечно, Flash по части анимации куда более производительный, чем JavaScript, однако Flash не был выбран сугубо по причине нехватки знаний в ActionScript.

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

Процесс разработки


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

Все задуманные идеи неплохо умещались в создание эпической онлайн-игры с элементами RPG, стратегии, текстового квеста и аркады. Вопросы реализации основного игрового интерфейса решались около двух недель. За это время: 7 макетов, 4 варианта оформления.
Интерфейс

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

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

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

В результате родился такой вариант:

image

Главная информация располагается в верхней части экрана (уровень игрока, ранг, деньги, топливо, грузовой отсек), как и основные кнопки навигации (инфо профиля, события, дипломатия и прочее). Игровые настройки и справочная информация — в правом нижнем углу.

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

image

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

image

image

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

Итак, хоть мы и не располагаем штатом профессиональных гейм-дизайнеров и художников, минимальными силами нам удалось создать приемлемый интерфейс, который впоследствии оказался весьма понятен игрокам.

Написание кода

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

Вышеизложенное применимо, по сути, к любому проекту, однако, как показала практика, при разработке игры эта проблема проявляет себя наиболее остро.

Исходя из этого, решение было довольно простым:

Все запросы клиента обрабатываются через один файл req.php, который в зависимости от получаемого GET-запроса подключает и выполняет нужные скрипты. Сами скрипты лежат в условной папке data/ и названы по принципу своего прямого назначения: planets.dat, solars.dat, colonies.dat, ships.dat, users.dat и т.д. Внутри этих файлов описаны классы и функции, разделенные по трем основным назначениям:

  1. Вывод данных игроку
  2. Создание/изменение/удаление данных
  3. Прочие общие функции движка (проверка данных, кэширование и т.д.)

Данный подход позволил свести к минимуму сложности в переработке структуры кода по причине расширения игровых возможностей: расширяются возможности кораблей — открывается файл ships.dat, добавляем новые типы планет — открываем planets.dat. Все изменения как правило выполняются в пределах одного — двух файлов, что позволяет ничего не забыть.

Подробнее о технических аспектах разработки я буду рад рассказать в следующих статьях. А сейчас перейдем дальше…

Запуск проекта


Итак, пилотная версия игры готова. Следующий вопрос — где запускать проект? Этот вопрос, разумеется, стал актуальным еще на стадии начала разработки. На самом деле, особых вариантов тут нет — либо это самостоятельная браузерная игра, либо это приложение в одной из социальных сетей.

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

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

Чтобы попасть в каталог приложений, потребовалось:

  • изучить API ВКонтакте, внедрить некоторые социальные функции (авторизация, приглашение друзей, рейтинги);
  • арендовать сервер. Изначально был арендован средненький KVM VPS на процессоре Intel Xeon (4 CPU) с 4 Гб оперативной памяти и SSD диском;
  • приобрести и подключить SSL-сертификат (бесплатно можно приобрести на startssl.com);
  • подать заявку на модерацию;

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

Приложение в каталоге приложений ВКонтакте

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

Статистика приложения. Первые посетители

Ситуация в корне изменилась, когда приложение попало в блок «Новые».

Статистика приложения после попадания в блок Новых приложений

За первые сутки в приложение пришло около 6000 человек. В последующие — меньше, однако поток был довольно большим. В течение недельного размещения приложение установило около 15 000 человек (имеются в виду те, кто не удалил приложение после установки).

Первые серьезные проблемы


Упал сервер

В первую очередь, по причине нагрузки на базу данных. В принципе, это было ожидаемо, именно поэтому пришлось вдвое увеличить мощности сервера (до Intel Xeon 4 CPU и 4 Гб оперативки). И конечно, оптимизация, оптимизация и еще раз оптимизация...

Т.к. оптимизацию необходимо было провести в рекордно короткие сроки, чтобы не терять аудиторию, решения принимались очень быстро. Было переписано множество строк кода, в частности, касающихся запросов к базе. Частично нагрузку на себя взял CPU. Был упрощен вывод данных из базы: в определенных местах, где требовались сравнение и выборка из нескольких таблиц, ставили простые SELECT-ы из каждой таблицы в отдельности, а затем выводили необходимые данные посредством сравнения массивов на PHP. И все потому, что JOIN-ы требуют довольно много ресурсов (как оказалось позже, это неправда, поскольку скорость работы JOIN-ов напрямую зависит от того, насколько грамотно они оптимизированы, но времени на это не было, и мы решили переложить часть нагрузки на PHP).

Помогло! Работу приложения удалось стабилизировать. И хоть вопрос оптимизации до сих пор далеко не последний, эта проблема более не вставала так остро.

Баги, много багов...

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

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

Дисбаланс

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

Первая наша ошибка была в том, что мы не учли один очевидный факт: игроки проводят в игре разное количество времени. И вот, один наш игрок проводил в игре около 5 часов в день. Через неделю его финансы выросли до девятизначного числа. Что он делал? Занимался торговлей. Находил планету, где один ресурс продавался дешевле, чем покупался на другой. Разумеется, опытные разработчики игр учтут этот момент в первую очередь. Для нас же — это было открытие.

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

Что дальше?

В качестве итога этой статьи, хочется отметить несколько наиболее значимых выводов, которые были сделаны с момента рождения идеи, до ее воплощения:
  1. Разработка игры — невероятно увлекательное занятие;
  2. Тестировать, тестировать и еще раз тестировать… В любом проекте тестирование и устранение багов занимают существенное количество времени. В игре — в три раза больше;
  3. Проработка игрового баланса — одна из основных задач при создании игры, которая должна закладываться на стадии формирования основных игровых возможностей;
  4. Мысль о том, что «я сам знаю, как должна выглядеть игра, которую я хочу создать» — верна лишь отчасти. Активные игроки и тестировщики могут внести значимый вклад в развитие игры. Не пренебрегайте общением с ними, желательно начиная с самой ранней стадии разработки;
  5. Вы должны быть целиком и полностью охвачены вашей идеей. Разработка займет очень много вашего времени. И чем меньше людей работают над ней — тем больше времени она от вас потребует;

Над проектом ведется работа более года (с некоторыми перерывами, поскольку требует личных финансовых вложений). Был проделан долгий путь, однако впереди по-прежнему много работы. Радуясь маленьким достижениям, совершая ошибки, мы движемся дальше, чего желаем каждому, прочитавшему эту статью! В конце концов, интересно, что из всего этого получится… :)

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

Спасибо за внимание!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Рецепт вращения планет в космосе на HTML5 + JavaScript

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

Вычеркиваем


После непродолжительного подбора различных способов реализации, сразу были исключены варианты:
  • с gif-анимацией (из-за низкого качества изображения);
  • с Flash (по договоренности, Flash-технологии решили в проекте не использовать);
  • с анимацией с помощью JQuery посредством функции $().animate (по причине ее прожорливости).

CANVAS в помощь!


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

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

image

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

image

image

Создаем планету

В качестве космического пространства у нас выступит блок div с любым подходящим для этого бэкграундом, а внутри разместим элемент с id=«planet»:
<div style="background-image:url(space.jpg); width:1000px; height: 1000px;">
     <canvas id="planet" width="300" height="300" style="position: absolute; left:200px; top: 200px;">
     </canvas>
</div>


Далее заставляем нашу карту двигаться внутри созданного элемента canvas, который скоро превратится в планету:
 $(function(){

        var pl_id = 'planet';

        var image = new Image();
        image.src = 'images/planets/1/1/map.jpg';

        // определяем длину и высоту элемента canvas
        var width = $('#'+pl_id).width();
        var height = $('#'+pl_id).height();

        var canvas = document.getElementById(pl_id);
        var id = canvas.getContext("2d");

        var newMoveWidth = 0;
        var newMoveHeight = 0;

        var drawPl = function(){

             id.clearRect(0, 0, width, height);
             // рисуем карту с новыми координатами внутри элемента
             id.drawImage(image, newMoveWidth, newMoveHeight, width, height, 0, 0, width, height); 

             if (newMoveWidth >= 899.5) newMoveWidth = 0; // если смещение достигло предела, начинаем сначала
             else newMoveWidth = newMoveWidth+0.5; // иначе двигаем карту дальше

        }

        setInterval(drawPl, 50); // запускаем анимацию со скоростью 50 мс.

 });


В результате произведенных действий, получаем примерно такую картину:

image

Закругляем...


Квадратных планет еще не открыли, поэтому придадим нашему небесному телу более привычный вид, прописав в style нашего элемента canvas border-radius на 50 процентов. Получаем:

image

Уже лучше, однако по-прежнему нет ощущения, что перед нами сферический объект.

Теперь подготовим в графическом редакторе круг с тенью по краям и бликами. Он должен быть обязательно полупрозрачным, т.к. мы будем накладывать его на нашу планетарную карту. В оригинале он будет выглядеть так:

image

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

Финальный код нашей анимированной планеты получается таким:

<div style="background-image:url(space.jpg); width:1000px; height: 1000px;">
     <canvas id="planet" width="300" height="300" style="position: absolute; left:200px; top: 200px; border-radius:50%">   
     </canvas>
</div>


И код самой анимации:
$(function(){

        var pl_id = 'planet';

        var image = new Image();
        image.src = 'map2.jpg';

        // загружаем изображение тени и бликов планеты
        var fxShadow = new Image();
        fxShadow.src = 'planet_shadow.png';

        // определяем длину и высоту элемента canvas
        var width = $('#'+pl_id).width();
        var height = $('#'+pl_id).height();

        // рассчитываем угол вращения планеты
        var beta = 360/900;
        var beta = (beta*Math.PI)/360;
        var l = (Math.sqrt(width*width+width*width))/2;
        var gam = Math.PI - ((Math.PI - (beta * Math.PI)/360)/2) - (Math.PI/4);
        var b = 2*l*Math.sin(beta/2);
        var x = b*Math.sin(gam);
        var y = b*Math.cos(gam);
        var p1 = Math.cos(beta);
        var p2 = Math.sin(beta);

        var canvas = document.getElementById(pl_id);
        var id = canvas.getContext("2d");

        var newMoveWidth = 0;
        var newMoveHeight = 0;

        var drawPl = function(){

                id.clearRect(0, 0, width, height);
                // применяем к нашей планете вращение
                id.transform(p1, p2, -p2, p1, x, -y);
                // рисуем карту с новыми координатами внутри элемента
                id.drawImage(image, newMoveWidth, newMoveHeight, width, height, 0, 0, width, height); 
                //добавляем тень и блики
                id.drawImage(fxShadow, 0, 0, width, height);
                // если смещение достигло предела, начинаем сначала
                if (newMoveWidth >= 899.5) newMoveWidth = 0; 
                else newMoveWidth = newMoveWidth+0.5; // иначе двигаем карту дальше

        }

        setInterval(drawPl, 50); // запускаем анимацию со скоростью 50 мс.

 });


Финальный результат анимации планеты в игре:

image

image

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

Спасибо за внимание!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Windows Camp //Labs — как научиться разрабатывать универсальные приложения Windows 10 за один день

На так давно мы провели Windows Camp, на котором рассказали вам про новую Windows 10 и универсальные приложения (если вы ещё не видели — можно посмотреть все доклады онлайн). Однако, как известно, лучше один раз увидеть, чем 7 раз услышать, и лучше один раз потрогать, чем 7 раз увидеть.

Мы приглашаем вас вместе с нами научиться разрабатывать приложения на платформе Windows 10 в ходе специальных лабораторных работ — Windows Camp //Labs. Мероприятие пройдет 28 октября в Москве (обязательна предварительная регистрация) в Digital October, участники не из Москвы смогут посмотреть трансляцию (на сайте мероприятия) и проделать работы у себя дома. Для участия (как на мероприятии, так и онлайн) потребуется ноутбук с установленным ПО (подробности — ниже).
Темы лабораторных работ с кратким описанием представлены на сайте мероприятия (раздел «программа»). Они будут покрывать широкий спектр тем, начиная от самых базовых (введение в универсальные приложения, демонстрацию работы универсальных приложений на Raspberry Pi 2 и Windows 10 Mobile), и заканчивая вопросами персонализированного голосового общения с приложениями и Cortana, работой с перьевым вводом, использованием облака Microsoft Azure и публикацией в магазине.

Для участия в мероприятии (как на площадке, так и в домашних условиях) понадобится компьютер с установленной ОС Windows 10 и Visual Studio 2015 (например, Community Edition). Также рекомендуется (но не обязательно) установить эмулятор для Windows 10 Mobile, или иметь смартфон с Windows 10.

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

На мероприятии не так много мест, поэтому спешите зарегистрироваться! Это прекрасная возможность изучить разработку на новой платформе Windows 10 всего за один день!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Завтра в 10:00 (МСК) смотрите онлайн-трансляцию SQLSaturday

Завтра состоится технологическая конференция SQLSaturday, которая пройдет в Москве с 10.00 до 18.00 в гостинице “Золотое Кольцо” по адресу Смоленская ул., д.5. Если вы не успели зарегистрироваться на конференцию, мы организуем онлайн-трансляцию открытия конференции и всех треков мероприятия. Трансляция будет доступна на сайте конференции. Присоединяйтесь, регистрация не требуется!

У Вас будет уникальный шанс прослушать почти два десятка докладов по ключевым темам платформы управления данными от признанных экспертов индустрии, включая:
• Особенности практического применения In-Memory OLTP в SQL Server 2014.
• Примеры использования AlwaysOn для обеспечения катастрофоустойчивости приложениий MS.
• Принципы и методика консолидации серверов БД MS SQL Server на предприятии.
• Анализ производительности Windows приложений на примере SQL Server.

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

• Мобильную бизнес-аналитику: Datazen и Power BI.
• Обзор сценариев использования продвинутой аналитики (Azure ML).
• Профилирование SSAS с помощью расширенных событий (XEvents).

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

Для кого?


На SQLSaturday будет интересно и полезно всем ИТ-профессионалам, сталкивающимся с платформой управления данными в своей работе. Не пропустите мероприятие, если Вы:

• администратор баз данных;
• ИТ-специалист, отвечающий за СУБД в организации;
• архитектор, аналитик, разработчик и администратор-BI решений;
• разработчик программного обеспечения.

Социальные сети


Официальный хэш-тэг в Twitter #sqlsatMoscow.
Чтобы оставаться на связи, Вы можете вступить в группы Russian SQL Server User Group и Russian BI PASS Chapter

Полезные ссылки


Курсы по SQL Server
Microsoft для IT-специалистов
Microsoft для разработчиков
Полезное видео по теме SQL Server

С уважением,
Команда SQLSaturday

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

[Из песочницы] Достаточно Git-а, чтобы быть (менее) опасным

Ты просто-напросто ненавидишь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 http://ift.tt/1GK7uKO вывалит смешной код этого весельчака в новую директорию 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 <path> показывает тебе лог изменений, которые коснулись только конкретного файла (или директории). --follow означает — отслеживать историю файла, включая переименования, но это работает только для одного файла.

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

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


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

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

git checkout <branch> устанавливает текущую ветку и переключается в соответствующее состояние кодовой базы. Ты также можешь перейти в удаленную ветку, в тэг или в конкретный коммит, но текущая ветка будет покинута и ты будешь получать предупреждения о наличии «оторванной 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 <remote> <branch> отправит твою ветку в ветку с тем же именем в удаленном репозитории. Если ты используешь форк с 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 -- <path>.

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

Ты можешь захотеть передать опцию -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.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

О безопасности UEFI, часть заключительная

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

Если вам интересно, чем безопасности прошивки могут помочь STM, SGX и PSP — жду вас под катом.

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

Часть седьмая. Технологии будущего


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

Про SGX и STM я уже упоминал в конце третьей части, поэтому начну рассказ с PSP, которым теперь без вариантов комплектуются все новые AMD APU.

AMD Platfrom Security Processor

Наблюдая за успехами Intel Management Engine, которым последние 5 лет оборудован каждый чипсет и SoC Intel, в AMD тоже решили не отставать от прогресса и встроить в свои SoC'и чего-нибудь эдакого.

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

В итоге в качестве IP Core купили ядро ARM Cortex-A5 с поддержкой технологии TrustZone, для эмуляции TPM 2.0 приобрели у Trustonic код TEE, остальное реализовали сами и представили получившийся SoC-внутри-SoC'а в 2013 году на очередном UEFI Plugfest.


Оригинальная схема PSP, про эмуляцию TPM речи тогда еще не шло.

Для обеспечения безопасности UEFI этот самый PSP предоставляет следующее: подсистему HVB, внутреннее хранилище для S3 BootScript, эмулятор TPM для реализации Measured Boot, генератор случайных чисел и ускоритель криптографических операций.

Hardvare Validated Boot

Про эту технологию я уже рассказывал в первой части, теперь расскажу более подробно. Суть ее простая — PSP получает управление до старта BSP и проверяет, чтобы содержимое второй стадии его прошивки и стартового кода не было изменено, в случае успеха BSP стартует с ResetVector'а и машина загружается как обычно, а в случае неудачи пользователю показывают код ошибки на POST-кодере, а BSP крутит мертвый цикл до hard reset'а, после которого все повторяется заново.

HVB, таким образом, является аппаратным корнем доверия для системы, но защищает эта технология только PEI-том, проверка же всего остального — на совести авторов прошивки.


Оригинальная схема AMD HVB

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

Integrated TPM 2.0

К релизу Windows 10 рабочая группа TCG подготовила интересное нововведение: вместо использовавшегося ранее интерфейса TIS для взаимодействия с модулями TPM теперь можно использовать вызовы ACPI, что позволяет производителям процессоров реализовать TPM не на внешнем чипе, а прямо в чипсете, да еще и половину реализации сделать программной. Такое решение имеет как преимущества (заменить чипсет сложнее, чем чип TPM в корпусе SSOP-28), так и недостатки (vendor lock-in), но реализовали его на данный момент и Intel (в Skylake) и AMD (в APU с PSP). Стандарт TPM 2.0 поддерживается обоими решениями не целиком, а только настолько, чтобы система со встроенным TPM могла использовать BitLocker и получить сертификат Windows 10 Ready. Тем не менее, теперь полку пользователей TPM однозначно прибудет. Вместе с встроенным TPM появились также аппаратный ГСЧ и криптоускоритель, которые, при желании, можно использовать отдельно.
Secure S3 BootScript Storage

Еще одна фишка PSP — встроенный NVRAM, в котором можно безопасно хранить какие-то пользовательские данные. На данный момент AMD сохраняет туда S3 BootScript, что хорошо защищает систему от атак на него. При этом немного страдает время выхода из S3, но лишние 50-100 мс ради безопасности вполне можно терпеть.

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

Intel Software Guard Extensions

Вернемся теперь к технологиям Intel. Об SGX начали говорить около года назад, но для конечного пользователя она стала доступна всего несколько недель назад, когда Intel включила ее для процессоров Skylake в очередном обновлении микрокода. SGX — это новый набор инструкций, позволяющих приложениям создавать т.н. «анклавы», т.е. регионы памяти для кода и данных, аппаратно защищенные от доступа извне, даже если этот доступ производится из более привилегированных режимов исполнения вроде ring 0 и SMM.

Технология достаточно сложная для понимания и использования (почти 200 страниц Programming Reference), но потенциально очень мощная, поэтому Intel начала заниматься ее продвижением.


Принципиальная схема работы SGX, один из более 200 слайдов вот этой презентации, она же в виде 80-минутного видео.

Intel называет SGX «обратной песочницей», т.е вместо того, чтобы пытаться изолировать потенциально вредоносное или недоверенное ПО, при помощи SGX программа может изолировать себя от всего остального мира. Идея сходна с ARM TrustZone, но если у ARM мир делится на обычный и доверенный и они выполняются на разных ядрах, взаимодействуя только через вызов инструкции SMC, то у Intel ядро одно и то же, зато память делится обычную и безопасную:


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

Мое отношение к этой технологии пока еще не сформировалось — я ее просто еще не пробовал, т.к. не работаю над Skylake в данный момент. Тем не менее, стараюсь не отставать от прогресса слишком уж сильно, поэтому читаю краем уха все, что пишут про SGX, к примеру:
Портал об SGX на сайте Intel.
Обзорная лекция об SGX с сайта Дармштадтского Технического Университета.
Обзорная статья NccGroup с кучей интересных ссылок.
Открытая платформа для написания своего кода для SGX.
И вообще, весь раздел про SGX на firmwaresecury.com.

Intel SMI Transfer Monitor

Вторая технология Intel, о которой я уже упоминал — STM. Первые упоминания о нем датированы 2009 годом, и после 6 лет разработки технология наконец-то была представлена в августе 2015. Суть ее простая: вместо диспетчера SMM в SMRAM запускается гипервизор, и все обработчики SMI выполняются в виртуализованном окружении, что позволяет запретить им вредоносные действия вроде изменения данных в памяти ядра и тому подобные.


Слайд из презентации STM на IDF2015

Технология позволяет значительно уменьшить как «поверхность атаки» на обработчики SMM, так и разрушительность последствий взлома обработчиков SMI. К примеру, запретив доступ к MMIO чипсета для всех обработчиков, кроме используемого для обновления прошивки, можно защитить ее от остальных обработчиков, путь даже они взломаны атакующим и он имеет возможность выполнить в них произвольный код.
Самое главное преимущество — неприхотливость, для работы STM нужны только включенные VT-x/AMDV и правильные настройки уровней доступа. На данный момент предварительная поддержка STM реализована в EDK2 только для тестовой платы MinnowBoard Max, но в ближайшие полгода-год IBV адаптируют ее для своих платформ, и взлома SMM можно будет опасаться гораздо меньше. Понятно, что бесплатной безопасности не бывает, и STM вносит дополнительную сложность в итак не самый простой процесс инициализации SMM, плюс обработка SMI занимает больше времени (страшнее, на самом деле, то, что оно занимает еще более неопределенное время, опять страдают пользователи жестких ОСРВ), плюс виртуализацию незнающий пользователь платформы может отключить и STM не получится использовать в таких условиях. Тем не менее, я потыкал в STM веткой на MinnowBoard и могу сказать: чем скорее IBV внедрят её — тем лучше.

Дополнительная информация для желающих:
Пост Винсента Циммера с анонсом STM.
Портал об STM на сайте Intel, с полезными ссылками.

Заключение


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

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

P.S. Обещал в последней части итоговую таблицу, но не смог в нее. Кодить — могу, писать по-русски — туда-сюда, красиво размечать таблицы — не выходит каменный цветок. Прошу искреннего пардону.

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.