17 июня 2010, 11:36
Темы: git, syntax, automation

Введение
Моя любимая система контроля версий имеет огромное количество инструментов. Как-то раз я участвовал в опросе, после которого выяснилось, что даже из самых популярных инструментов я использую от силы 10%.
Но иногда возникают ситуации, единственно продуктивным выходом из которых бывает изучение и использование нового для себя инструмента. О двух таких случаях я сегодня и расскажу.
Внезапные просьбы: git stash
Бывает так, что пока я работаю над нововведениями в программу, текущая стабильная её версия активно используется. При активном использовании, конечно же, могут возникнуть ошибки или пожелания что-то изменить. Бывает так, что при этом я нахожусь в середине тестирования какого-то новшества, и всё настолько сыро, что я даже не могу сделать коммит.
Итак, я нахожусь в середине правок на ветке extremely_experimental, а мне необходимо внести правки в ветку master. Вот, как это делается:
git stash save
git checkout master
После первой команды всё наши изменения, которые нельзя было закоммитить, сохранены и текущая ветка приведена в состояние до правок. После этого мы можем сменить ветку и внести наши правки. После того, как ошибки исправлены, нововведения сделаны и тесты проходят, мы можем вернуться обратно к нашим правкам.
Но скорее всего все или некоторые из сделанных изменений понадобятся нам в нашей экспериментальной ветке. После перехода на неё:
git checkout extremely_experimental
Если нам нужны все изменения, то:
git merge master
Если только некоторые, то:
git cherry-pick ...
После этого вернём наши правки:
git stash pop
Если возникли конфликты, то правим их и делаем:
git stash drop
git reset --mixed
Последнее нужно для того, чтобы вынести наши правки из индекса, т.к. при конфликте они не выходят оттуда самостоятельно.
Конечно же таких незавершённых правок может быть несколько, но это я оставлю на самостоятельное изучение пытливому читателю.
Неизвестно, когда сломалось: git bisect
Бывает так, что вдруг обнаруживается ошибка, про которую точно известно, что давным давно её не было. Так бывает в больших проектах, в непокрытых тестами областях. Бывает так, что обнаружить, в чём же дело, быстро не удаётся.
Хорошая новость в том, что это и не обязательно. Нужно просто начать процесс:
git bisect start
git bisect bad
Так мы обозначили, что текущий коммит содержит ошибку. После этого, либо мы знаем, как называется коммит, в котором ошибки ещё не было, или находим его.
git bisect good v2.3.1
или
git checkout ...
git bisect good
После этого за нас всё будет делать git. Он будет перемещать нас по истории, а мы будем проверять, есть эта ошибка или нет, и сообщать об этом:
git bisect good
или
git bisect bad
В конце концов нам сообщат, какой именно коммит всё поломал. Название инструмента подсказывает нам, что на тестирование нам всегда предоставляется коммит, который находится посередине между плохим и хорошим. Таким образом, мы просматриваем не все N коммитов в истории ошибки, а всего лишь log2N.
После того, как мы выяснили, в чём причина, убрать следы, которые оставил после себя git bisect можно так:
git bisect reset
А какими инструментами git пользуетесь вы?
Материалы для самостоятельного изучения
- Документация по git
- git stash
- git bisect
01 июня 2010, 23:40
Темы: ruby, git, automation, air, mistakes, syntax

Введение
Основной целью этого блога является сбор в одном удобном месте необходимых мне по работе знаний и фишек. Однако, именно потому что это активно используемые в работе решения, со временем появляется более продуктивный или более правильный способ сделать то, о чём написано почти в каждой статье.
Иногда я просто ошибаюсь. Трудно представить что-то более полезное для опыта, нежели набивание шишек. Будет хорошо, если проведение работ над ошибками станет доброй традицией. Итак, в этом году.
git hooks
Недостатков скрипта для удаления пробелов в концах строк нашёл два:
- Скрипт без нужды дёргает ни в чём не повинные файлы, потому что \s соответствует и символу конца строки, который там всегда есть.
- Скрипт не содержит решения для выбора всех текстовых файлов проекта.
Вот хороший скрипт:
#!/usr/bin/env ruby
`git grep -I --name-only -e ""`.split("\n").each do |p|
lines = File.readlines(p).map(&:chomp)
if lines.inject(false) { |memo, l| l.gsub!(/\s+$/, "") || memo }
File.open(p, "w") do |f|
f.puts lines.join("\n")
end
puts "Removed trailing spaced from '#{p}'"
system "git add #{p}"
end
end
Так же по совету Дмитрия в комментариях добавил скрипт для проверки счастливого коммита.
Работа с версией в (ai)rake
Совершенно очевидная ошибка в примере про работу с версиями air-приложения в rake. Когда увеличивается более старшая часть версии, то все младшие должны обнуляться:
namespace :version do
[:major, :minor, :patch].each_with_index do |subv, index|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].fill(0, index+1).join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}</version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end
Теперь rake version:bump_minor делает из 0.1.6 не 0.2.6, а 0.2.0, как и должно быть.
Мимоходом
Тем временем я сменил тарифный план у своего провайдера на (ve). И незаметно перенёс сайт. Посмотрим, как работает на собственном опыте. Работа по ssh, как была, так и осталась основным способом администрирования, а необходимость лазить в plesk пропала, потому что его теперь нет :)
26 мая 2010, 21:42
Темы: ruby, automation, git

Введение
Как-то раз мне попался очень злой git-репозиторий, который отказывался работать, если я оставлял пробелы в конце строк. Есть такая версия, что git заточен под отправку патчей по почте, и что пробелы в концах строк могут навредить в таком процессе.
Тогда я просто отключил эту проверку, а недавно подумал, почему бы мне не использовать эти мощности в мирных целях.
git hooks
Для множества различных целей у git есть хуки. (Как бы их перевести нормально?) Они находятся в каждом репозитории в папке:
.git/hooks
И имеют говорящие названия. Используются они для соблюдения форматов и соглашений, для оповещений, для проверки и т.п. Почему бы не возложить на них корректорские функции?
Использование pre-commit для удаления пробелов на концах строк
Поскольку я, опять же, фанат руби, то и скрипты благо есть такая возможность напишу на руби. Создаём файлик .git/hooks/pre-commit:
#!/usr/bin/env ruby
Dir.glob("*.{txt,rb}").each do |p|
lines = File.readlines(p)
if lines.inject(false) { |memo, line| line.gsub!(/\s+$/, "") || memo }
File.open(p, "w") do |f|
f.puts lines.join("\n")
end
system "git add #{p}"
end
end
Как видно, этот скрипт ищет файлы *.txt и *.rb в корневом каталоге репозитория, и если в них есть пробелы в конце строк, перезаписывает их и добавляет в индекс для коммита.
Не забыть сделать его запускаемым:
chmod +x .git/hooks/pre-commit
Теперь у нас в распоряжении автоматический помощник-редактор, который удаляет пробелы в конце строк.
Материалы для самостоятельного изучения
Документация по git hooks
28 апреля 2010, 15:21
Темы: tdd, actionscript, ruby, flex, automation, air

Введение
Уже некоторое время назад обнаружил гениальный инструмент. Правда только недавно опробовал его на своих рабочих проектах и зафанател ещё больше. Подготовка к докладу на секции Яндекса про панорамы на РИФе не позволила мне поделиться этим ранее. Исправляю ошибку.
Я люблю руби. И, естественно, rake, как инструмент, продолжающий славные традиции make в руби и с помощью руби. Так же я питаю нежные чувства к ActionScript. Мне нравится AIR, который позволяет писать действительно кросс-платформенные приложения довольно быстро. Так же я неплохо отношусь к TDD, как к одному из способов разработки.
Какова же была моя радость найти инструмент, который всё это объединяет! Хотя ему уже пара лет, он по-прежнему прекрасен.
Установка составляющих
Предполагаю, что ruby, rubygems и rake уже установлены у тех, кто читает этот блог.
Далее, качаем и разархивируем куда-нибудь Adobe AIR SDK и Adobe Flex SDK (или предыдущая версия, если вы консерватор), а так же устанавливаем Adobe AIR Runtime. Чтобы установить последний, после загрузки bin-файла нужно:
chmod +x AdobeAIRInstaller.bin
sudo ./AdobeAIRInstaller.bin
Теперь добавим в PATH пути к исполняемым файлам загруженных SDK. В .bashrc добавляем:
export PATH="/path/to/air_sdk/bin:$PATH"
export PATH="/path/to/flex_sdk_4/bin:$PATH"
Так же потребуется установить java для того, чтобы на ней работал компилятор:
sudo apt-get install sun-java6-jre
После установки, независимо от того, используете вы Flex3 или Flex4, нужно переписать содержимое AIR SDK поверх Flex SDK. Мне не совсем понятен сакральный смысл этих действий, но иначе ничего не работает.
Привѣтъ, Мiръ!
Создание пустого air-приложения теперь просто:
airake airake_hello_world
Чтобы запустить его, однако, следует исправить в src/AirakeHelloWorld-app.xml и test/Test-app.xml:
...
xmlns="http://ns.adobe.com/air/application/1.5"
...
Если вы решили использовать Flex4, то вам необходимо отредактировать сгенерированное приложение, чтобы запустить его. Это связано с изменениями в стилях. Поэтому проще просто удалить всё содержимое тэга WindowedApplication в файле src/AirakeHelloWorld.mxml.
Про использование TDD в ActionScript я уже писал, поэтому подробно останавливаться не буду. Для примера в код на github включён тривиальный тест. Запуск тестирования происходит привычным образом:
rake test
Документация, если вы пишете правильные комментарии ASDoc, тоже запускается привычным образом:
rake docs
Так же делается всё остальное: запуск приложения в отладочном режиме, генерирование сертификата, упаковка релиза приложения. Для того, чтобы это всё узнать, используйте:
rake -T
Использование rake
Конечно, вся прелесть rake не только в привычных и коротких командах для разработки, но и в том, что можно создавать свои сценарии. Например, вот как могла бы выглядеть работа с версиями приложения. Добавим в файл raketasks/version.rake следующий код:
require 'yaml'
desc "Print out current version"
task :version do
if md = File.read(YAML.load_file('airake.yml')["appxml_path"]).match(/<version>(.*)<\/version>/)
puts "Current version is #{md[1]}"
else
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
end
end
namespace :version do
[:major, :minor, :patch].each do |subv|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}<\/version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end
А в Rakefile соответственно:
# Custom rake tasks
Dir.glob("raketasks/*.rake").each { |rf| load rf }
Теперь мы можем привычным образом работать с версиями приложения (а версии эти потом будут распознаваться установщиком обновлений):
rake version
rake version:bump_major
rake version:bump_minor
rake version:bump_patch
И это не предел!
Материалы для самостоятельного изучения
- Полный код статьи на github
- Инструкция по работе с airake, которая во многом повторена в этой статье с добавлением манипуляций, чтобы всё заработало.
- Документация по FlexUnit. Не уверен, что в поставке airake идёт самая последняя версия, но ничего не мешает написать rake task для обновления версии FlexUnit :)
- Документация по rake
07 апреля 2010, 12:59
Темы: ruby, ruby1.9, syntax, security

Введение
Совершенно не по работе заинтересовался переменной $SAFE и её ролью в жизни современного разработчика. Оказалось, что всё нужно проверять самому.
Нежная безопасность
Для тестирования возможностей на разных уровнях безопасности собрал небольшую программку. Она просит ввести имя файла, делая строковую переменную небезопасной, и пытается что-то с этим всем сделать.
print "child: "
child = gets.chomp
puts "child tainted: #{child.tainted?}"
(0..4).to_a.each do |i|
puts "SAFE: #{i}"
$a = "safe"
th = Thread.new do
$SAFE = i
child_copy = child.dup
Thread.current[:out] = ""
begin
load child_copy
Thread.current[:out] += "1. Child loaded\n"
rescue SecurityError => e
Thread.current[:out] += "1. Security error: #{e.to_s}\n"
begin
child_copy.untaint
load child_copy
Thread.current[:out] += "2. Child untainted and loaded\n"
rescue SecurityError => e
Thread.current[:out] += "2. Security error: #{e.to_s}\n"
begin
Thread.current[:out] += "3. Read from file '#{child_copy}': '#{File.read(child_copy)}'\n"
rescue SecurityError => e
Thread.current[:out] += "3. Security error: #{e.to_s}\n"
begin
Thread.current[:out] += "4. Read from untainted file: '#{File.read("child.rb")}'\n"
rescue SecurityError => e
Thread.current[:out] += "4. Security error: #{e.to_s}\n"
end
end
end
end
begin
$a = "modified"
Thread.current[:out] += "5. Global variable modified: $a = '#{$a}'\n"
rescue SecurityError => e
Thread.current[:out] += "5. Security error: #{e.to_s}\n"
end
begin
Dir.mkdir "test"
Thread.current[:out] += "6. Created directory 'test': #{File.exist?("test")}\n"
Dir.rmdir "test"
rescue SecurityError => e
Thread.current[:out] += "6. Security error: #{e.to_s}\n"
end
begin
Thread.current[:out] += "7. Dir glob: #{Dir.glob(File.join("..", "*")).inspect}\n"
rescue SecurityError => e
Thread.current[:out] += "7. Security error: #{e.to_s}\n"
end
begin
Thread.current[:out] += "8. System ls output: '#{`ls`.chomp}'"
rescue SecurityError => e
Thread.current[:out] += "8. Security error: #{e.to_s}\n"
end
end
th.join
puts "Global variable: $a = '#{$a}'"
puts th[:out] if th[:out]
end
Конструкция со Thread.current[:out] используется потому, что для $SAFE >= 4 нельзя ничего писать ни в какие устройства вывода.
Вроде бы всё логично. Первый уровень годится для умеренного карантина внешних данных. При желании их можно и расколдовать. Второй уровень запрещает изменения в файловой системе. Третий уровень похож на осаду с постоянным подозрением на шпионаж. Все созданные объекты считаются небезопасными. А четвёртый уровень это самое близкое к песочнице (sandbox) в руби, что что есть.
Кстати, когда ещё github работал как репозиторий библиотек, спецификация gemspec выполнялась там под $SAFE = 3. Для разработчиков это выливалось в то, что нужно было перечислять все файлы своей библиотеки вручную вместо использования какого-нибудь листинга.
Суровый гайдлайн
Конечно же, только использование $SAFE не убережёт от действительно настойчивой атаки или блокирующего кода. Например:
Thread.new do
$SAFE = 2
class String
def ==(other_string)
true
end
end
end.join
puts "string modified: #{'a' == 'b'}"
И это на втором уровне! А на третьем открыть класс тоже можно, но вызов перегруженного оператора будет вызывать SecurityError.
На сегодняшний момент эту концепцию безопасности можно считать сырой. Актуальное поведение руби 1.8 слегка отклоняется от описаний, что я нашёл. Поведение в 1.9 изменилось, но подробно нигде не описано (я не нашёл).
Это не значит, что этой переменной нет применения в жизни прогрессивного человечества. Адекватное текущему состоянию применение это гайдлайн при разработке. Руководство для программистов, которое само следит за своим исполнением. Жестковато, но зато действенно. :)
Материалы для самостоятельного изучения
- Код примеров в статье на github
- Старая, но самая подробная документация по $SAFE
- Просто дополнительно: шпаргалка по руби