Как стать автором
Обновить

svn + bash = пишем консольный svn браузер

Время на прочтение 10 мин
Количество просмотров 4.6K
Для тех кто пользуется svn в командной строке, а так же для тех кто интересуется программированием bash-скриптов, в топике рассмотрен пример написания интерактивного bash-скрипта «svn-браузера», работающего в терминале и позволяющего делать несколько «ежедневных» операций с деревом репозитория, а именно:
  • Серфить по репозиторию
  • Просматривать логи
  • Копировать директории для создания tags/branchs
  • Создавать/удалять/переименовывать директории
  • Извлекать/экспортировать (checkout/export)
При этом любая операция делается нажатием одной-двух кнопок, не считая ввода комментариев, и не требует помнить/вводить длинные пути, такие как:<br><br>
$svn cp "http://workserver.com/_main_repository/embedded_system/product_xxx/_trunk/main_task/ http://workserver.com/_main_repository/embedded_system/product_xxx/_tags/"

Под катом обзор внутренностей, результат можно скачать по ссылке svnb
Сделать исполняемым, запускать в директории — рабочей копии svn (запустить можно в любом месте, но тогда придется ввести путь до репозитория с которым вы хотите поработать).

P.S. В конце статьи добавил еще одно решение для улучшения юзабилити command line svn — автодополнение пути.

Зачем?


Subversion (svn) — популярная система контроля версий которая часто используется разработчиками ПО и не только. Для работы с ней существуют такие GUI приложения как TortoiseSVN, rapidSVN ит.д. которые кроме всего прочего интергрируются с проводником/оконным менеджером/файловым браузером.

Удобно, что сказать, но GUI не всегда уместны: работа по ssh на сервере, отсутствие X-server, или банальное желание не использовать GUI там где можно их не использовать — любовь к command line)). В командной строке вам доступны такие пассы как:

$svn up #сделать update рабочий копии
$svn ci -m"коментарий к коммиту" #сделать коммит
$svn log #посмотреть комментарии коммитов для рабочей копии

Очень удобно!
Но одно «Но», как только вам потребуется поработать с другой директорией находящейся в репозитории например для просмотра коментариев или списка файлов вам необходимо вызывать утилиту svn с путем до репозитория + путем в файловой системе репозитория до нужной директории, например:

$svn list (log) "http://workserver.com/_main_repository/embedded_system/product_xxx/_tags/"
$svn cp "http://workserver.com/_main_repository/embedded_system/product_xxx/_trunk/main_task/ http://workserver.com/_main_repository/embedded_system/product_xxx/_tags/"

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


Что делать?


В рабочей копии svn всегда присутствует директория .svn/ в которой среди бесполезного мусора прочего есть файл .svn/entries, в котором содержутся строки содержащие обсолютные пути до репозитория и рабочей копии. Убедиться в этом просто, выполнив:

$cat .svn/entries | grep ://

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


Пишем!


Хотелось бы написать что то очень простое, интуитивно понятное, с минимальными телодвижениями позволяющего делать такие операции как просмотр логов и експорта, создание/удаления директоррий, создавание tags/branch средствами svn.

Я решил написать bash скрипт который, будучи запущен, не возвращает управление сам, а работает как интерактивное приложение, т.е. ловит нажатия кнопок на которые «повешаны» функции svn и отрисовывает на экране результат.

Функционал:

Определим необходимые команды управления:
  • Курсорные клавиши для перемещения по репозиторию. Кнопка «Влево» для перемещения к корню репозитория, кнопка «Вправо» для перемещения от корня в выбраную директорию, кнопки «Вверх» и «Вниз» для перемещения по списку файлов.
  • PageUp/PageDown для пролистывания страниц, если список файлов не влазит в экран
  • h(elp) — вывести справку
  • l(og) — просмотр лога
  • y(ank) — копировать
  • x (cut) — вырезать
  • p(aste) — вставить
  • e(xport) — експортировать
  • с(heckout) — извлечь
  • m(kdir) — создать директорию
  • r(ename) — переименовать
  • d(elete) — удалить
  • q(uit) — выйти
Кнопки определены в начале скрипта, можно передефайнить на свой вкус

Для команд export и checkout будем добавлять переменную $HOME в начало введенного пути, чтобы не приходилось каждый раз вводить /home/username/.

По команде «Выход» скрипт завершается выводя на экран путь до директории в репозитории в которой мы находились. Если вам потребуется выполнить в командной строке операцию, которую не может сделать скрипт, вы можете хотя-бы не вбивать длинные пути и не делать десяток svn list. Просто перейдите по нужному пути, закройте скрипт командой «Выход»(quit) и скопируйте выведеный путь.

Отлов нажатых кнопок

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


############ бесконечный цикл ожидания ввода #############
 233 # Прочитать из stdin, -s отключить эхо, -n1 только один символ
 234 while read -s -n1 key                      
 235 do
...
 281 done

Команда read возвращает управление только когда считает всю строку и дождется нажатия Enter, но мы не хотим жать «Ввод» после каждой нажатой кнопки. Ключ -nN говорит команде read возвращать управления после считывания N символов без ожидания Enter. Эхо нам тоже не кчему (ключ -s), все что нужно мы сами выведем на экран. :D

Курсорные клавиши

Курсорные клавиши (равно как и другие специальные клавиши, типа Delete, Insert) в терминале передаются специальными Escape-последовательностями состоящими из 3 символов:
^[A — стрелка Вверх
^[B — стрелка Вниз
^[C — стрелка Вправо
^[D — стрелка Влево

На на opennet.ru есть пример, где используется считывание по 3 символа (ключ -n3 команды read), это удобно, если скрипт не должен реагировать на одиночные (обычные asci) символы, и в нашем случае неприемлимо.

Мы же заведем пременную-флаг, которую будем устанавливать, при приеме ESC-симовла, инкрементировать при приеме оставшихся 2 символов последовательности, а затем, скидывать в 0 и вызывать соответствующую функцию обработки, при этом будем игнорировать все ненужные нам нажатия и последоавтельности, ожидая только перечисленые выше.

Заметьте что ESC — символ в коде скрипта пишется как ^[, для того чтобы набрать его в терминале (или в консольном редакторе vim, nano) следует нажать Ctrl+V, а затем Esc.


 378   if [ "$key" == "^[" ] # если приняли ESC-символ
 379   then
 380     cursor_ind="1"       # устанавливаем флаг в 1
 381   elif [[ "$key" == "[" && $cursor_ind == "1" ]] # если ожидаем 2го символа ESC-последовательности 
 382     then
 383       cursor_ind="2"  # устанавливаем флаг в 2
 384   elif [[ "$key" == "A" && $cursor_ind == "2" ]] # если ожидаем 3го (последнего) символа ESC-последовательности, и она соответствует кнопки "Вверх".
 385       then
 386         cursor_ind="0" # сбрасываем флаг
 387         menu_up        # вызываем соотв. функцию обработки

и так по всем интересующим нас кодам кнопок.

Функции

При написании скрипта, повторяющиеся части кода или просто отдельные по смыслу, удобно оформлять в функции, делается это просто:
Пример: функция которая принимает один аргумент (через переменную $1)


  40 ########### получает список файлов ###############
  41 function svn_get_list {
  42   SVN_LIST="`svn list $SVN_REPO_PATH/$1`"
  43 }

Вызыв функции (передаем переменную в параметре):


 133     svn_get_list "$SVN_PATH_PTR"

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

Экран

Вывод на экран осуществляется командой echo, последовательно отрисовываем все что необходимо:
  • Заголовок
  • Путь до текущей директории со словами «You are here», список содержимого которй идет ниже
  • Список файлов/директорий
  • Команду
Перед отрисовкой очередного экрана его необходимо очистить, для этого существует команда оболочки clear;.
Экран необходимо перерисовывать каждый раз, даже когда сдвинулся курсор, вывод занимает время и на глаз это заметно, ничего не поделаешь.

Стили:

Для удобства, директории выводятся жирным шрифтом, а курсор (текущая выбраная директория — инверсным). Управлять стилями шрифта в терминале можно опять таки ESC-последовательностями, например:


echo "^[[1m жирный текст ^[[0m нормальный текст."

выведет:
жирный текст, нормальный текст
С текстом в терминале можно делать многое, раскрашивать, инвертировать, мигать, итд… Подробнее про цвета и стили в терминале хорошо написано тут.

У меня запущеный скрипт выглядит вот так :

Bash svn browser v1.1. Usage: help, quit, y copy, x cut, paste, log, remane, delete, makedir, export, checkout.
--------------------------
You are here [ http://xxxxxxx/xxxxx/_WorkServer]:/_main_task/bootloader/_trunk/

===========
Changelog.txt
License.txt
Readme.txt
commandline/
firmware/
===========

Это скопированый текст из терминала, картинку делать не стал, ибо все текстово-консольное, единственное различие с оригиналом — там присутствует курсор, инверсно отображенный текст.

Страницы:

Если у вас в репозитории много файлов под контролем версий, вполне вероятно что их список не влезет в экран, а проматывать терминал — не самое красивое решение. На помощь приходит команда $tput lines возвращающая кол-во строк терминала по высоте. Перед каждым выводом на экран узнаем это значение, и отнимаем (резервируем) место под кол-во «постоянно присутствующих» строк:


 187     LINE_PER_PAGE=$((`tput lines` - 8))

Если кол-во строк больше лимита — несложными манипуляциями с арифметикой в bash список разбивается на страницы, которые можно пролистывать кнопками PgUp/PgDown.

Pipe

Многие знают что такое pipe или «канал», это удобный способ связывать вывод чего-то одного с чем-то другим, я намеренно не сказал утилиты, т.к. pipe-ом можно связать и команды bash. Почему я про них заговорил? Потому-что столкнулся с одной интересной особенностью:
Когда что-либо вызывается через pipe, для него создается дочерний экземпляр shell, так называемый sub-shell. И фокус в том, что все переменные родительского shell видны в дочернем, но дочерний имеет локальные копии всех переменных родительского shell, и не может «глобально» изменить их значения. Т.е. не может ничего с помощью них возвратить!
Пример, рассмотрим функцию вывода списка файлов на экран:


106 ############ выводит на экран список #############
107 function menu_print_list {
108
109   echo "$SVN_LIST" | while read line
110     do
111       if [ "`expr "$line" : '.*/$'`" -ne "0" ] # если это директория
112       then
113         if [[ "$CNT" -eq "$V_CURSOR" ]] # если курсорр установлен на это меню - выделить его
114         then
115           echo "^[[7m^[[1m$line^[[0m"
116         else
117           echo "^[[1m$line^[[0m"
118         fi
119       else
120         if [[ "$CNT" -eq "$V_CURSOR" ]] # если курсорр установлен на это меню - выделить его
121         then
122           echo "^[[7m$line^[[0m"
123         else
124           echo "^[[0m$line^[[0m"
125         fi
126       fi
127       CNT=`expr $CNT + 1`
128     done 
129 }

Функции, для того чтобы вывести курсор на нужной строке, необходимо знать номер строки на которой установлен курсор, и кроме того иметь счетчик строк, когда условие равенства выполнится (строка 113) инвертировать текст строки. Отлично, курсор установлен.
В конце работы функции мы всегда знаем кол-во строк (переменная CNT) которое пригодится нам в дальнейшем (в функции перемещения курсора, для контроля выхода за пределы списка).
Но вернуть значение счетчика мы не можем! Как только мы выходим из блока который находится по правую сторону pipe (строка 109) переменная CNT принимает тоже значение что было до того как создался pipe.
Других решений я не знаю, кроме как написать еще одну функцию только для посчтета кол-ва строк, которая в результате будет делать ehco переменной за пределы pipe, а результат работы блока с pipe вызывать заключенным в ковычки `` и присваивать переменной «родительского» shell:


  65 ####### вычисляем кол-во строк в списке ##########
  66 function menu_get_dir_cnt {
  67   DIR_CNT=0
  68   DIR_CNT="`echo "$SVN_LIST" | ( while read line
  69   do 
  70     ((DIR_CNT++))
  71   done; echo $DIR_CNT)`"
  72 }
Забавно, но парсер кода не справился с этой конструкцией и не подсветил ее как положено, в vim кстати тоже подсветка с ней парилась ;)

Алгоритмы

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

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

Навигация:

«Курсор Влево» — все просто, нужно переместиться на уровень к корню, для этого в переменной содержащей путь до рабочей копии отрезаем с конца все до слэша "/", затем снова распечатываем вывод команды svn list с получившимся путем. Для удобства курсор не плохо бы оставить на той директории из которой только что вышли, это легко сделать если «запомнить» «отрезаный» хвост, найти его в списке и установить курсор на нем.

«Курсор в право» — добавить к текущему пути то, на чем стоит курсор (при этом проверить что это директория, а не файл, т.к. в файл перейти мы не можем =), у директорий на конце имеется "/"), вывести на экран вывод команды svn list с полученным путем.

Команды:

Нажали «Копировать/Вырезать» — запомнить путь

Нажали «Вставить» — вызвать утилиту svn с командой cp/mv и путем сохраненным в предыдущих командах.

Нажали «Посмотреть лог/Удалить/Переименовать/Експортировать/Извлечь» — вызвать утилиту svn c командой log/rm/mv/export/co для текущего пути в репозитории, и если необходимо, предложить ввести дополнительные парамеры (новое имя (rename), комментарий).

Для команд checkout и export в качестве пути назначения нужно вводить путь внутри домашней директории $HOME в начало пути подставляется автоматически:
/home/user_name/src/my_proj
src/my_proj — правильно ))

ToDo

Все что хотел — сделал.
Возможно в комментариях уважаемые читатели предложат что еще следует реализовать? ;)
В планах подумать о merg и diff, с запуском внешнего редактора. Но операции это не «ежедневные» и требуют сосредоточенности и внимательности.

Предложения и баги отправляйте по email в скрипте.

Заключение

Скрипт получился удобным, сам ежедневно им пользуюсь дома и на работе. Возможно он далек от идеала и канонов bash-scripting. Кроме того, пожалуй это «велосипедо-строительство» но изобретать, это так приятно! ).

Скачать svnb v1.2
Скачать, сделать исполняемым, запускать в директории рабочей копии.
Для удобства можно скопировать в /usr/local/bin/
Лучше создать директорию .bin в хомяке, добавить ее в $PATH и хранить скрипты там ))

Альтернативный вариант



Подумав и погуглив решил сделать автодополнение пути, ведь с такой фичей решаются многие проблемы и серфить по репазиторию так же приятно как и по домашней директории!
Нашел вот такой скрипт, затестил — дополняет чего то, но не пути… почитал man bash и интернеты и прикрутил дополнение путей, результат можно скачать:

Скачать bash_completion_svn
положить куданибудь и выполнить source для него
$source bash_completion_svn
Можно добавить эту строку в конец .bashrc, тогда при старте bash это будет происходить автоматически.
Использование
Набираем $svn list, жмем [TAB] путь дополняется до текущей рабочей копии, а дальше все как обычно )), жмем [TAB] и наслаждаемся )).

Используемый материал

Opennet.ru Искусство программирования на языке сценариев командной оболочки

Спасибо за внимание!
Теги:
Хабы:
+34
Комментарии 20
Комментарии Комментарии 20

Публикации

Истории

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн