svn + bash = write a console svn browser

    For those who use svn on the command line, as well as for those who are interested in programming bash scripts, the topic discusses an example of writing an interactive bash script “svn browser” that works in the terminal and allows you to do several “daily” operations with the repository tree , namely:
    • Surf the repository
    • View logs
    • Copy directories to create tags / branchs
    • Create / delete / rename directories
    • Extract / export (checkout / export)
    Moreover, any operation is done by pressing one or two buttons, not counting the input of comments, and does not require to remember / enter long paths, such as:

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

    Under the cat, an overview of the internals, the result can be downloaded from the link svnb
    Make executable, run in the directory - a working copy of svn (you can run it anywhere, but then you have to enter the path to the repository with which you want to work).

    PS At the end of the article, I added another solution to improve the usability of command line svn - path completion .

    What for?


    Subversion (svn) is a popular version control system that is often used by software developers and more. To work with it there are such GUI applications as TortoiseSVN, rapidSVN, etc. which, inter alia, integrate with Explorer / window manager / file browser.

    It’s convenient what to say, but GUIs are not always appropriate: working on ssh on the server, lack of an X-server, or a banal desire not to use the GUI where you can not use them - love for command line)). At the command line you can access such passes as:

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

    Very comfortably!
    But one “But”, as soon as you need to work with another directory located in the repository, for example, to view comments or a list of files, you need to call the svn utility with the path to the repository + path in the repository file system to the desired directory, for example:

    $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/"

    which in itself is not very convenient, because you do not have to remember the path to the repository and the path in the repository to your working copy, and making a bunch of svn list from root to the desired directory is inconvenient if only because there is no autocompletion of file names in the svn path argument .


    What to do?


    In the working copy of svn there is always a .svn / directory in which, among other useless garbage, there is a .svn / entries file that contains strings containing absolute paths to the repository and working copy. This is easy to verify by doing:

    $cat .svn/entries | grep ://

    Now we have absolute paths, this is not enough, I often used this command to copy the desired path and carry out the operation with the svn command, but we can automate the process! Having written a certain utility or script that allows us to automatically get these paths and display us a list of files / directories in the repository, as well as use these paths for the necessary operations.


    We are writing!


    I would like to write something very simple , intuitive, with minimal gestures allowing you to do operations such as viewing logs and exports, creating / deleting directories, creating tags / branch using svn.

    I decided to write a bash script which, when launched, does not return control itself, but works as an interactive application, i.e. catches button presses on which svn functions are “hung” and draws the result on the screen.

    Functional:

    We define the necessary control commands:
    • Cursor keys to move around the repository. The Left button to move to the root of the repository, the Right button to move from the root to the selected directory, the Up and Down buttons to move through the list of files.
    • PageUp / PageDown for scrolling pages, if the list of files does not fit on the screen
    • h (elp) - display help
    • l (og) - view the log
    • y (ank) - copy
    • x (cut) - cut
    • p (aste) - paste
    • e (xport) - export
    • s (heckout) - extract
    • m (kdir) - create a directory
    • r (ename) - rename
    • d (elete) - delete
    • q (uit) - exit
    The buttons are defined at the beginning of the script, you can redefine to your taste.

    For the export and checkout commands, we will add the $ HOME variable to the beginning of the entered path so that you do not have to enter / home / username / each time.

    By the "Exit" command, the script ends by displaying the path to the directory in the repository in which we were located. If you need to perform an operation on the command line that the script cannot do, you can at least not drive long paths and do a dozen svn list . Just go to the desired path, close the script with the "Exit" command ( q uit) and copy the displayed path.

    Captured buttons

    To read the keyboard, we need an infinite waiting loop for the button input, for this we use this design:

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

    The read command returns control only when it reads the entire line and waits for Enter to be pressed , but we do not want to press Enter after each button pressed. The -nN switch tells the read command to return control after reading N characters without waiting for Enter . We don’t need an echo either ( -s key ), we will display all that is needed on the screen. : D

    Cursor keys

    The cursor keys (as well as other special keys, such as Delete, Insert) in the terminal are transmitted by special Escape sequences consisting of 3 characters:
    ^ [A - Up arrow
    ^ [B - Down arrow
    ^ [C - Right arrow
    ^ [D - arrow Left

    On opennet.ru there is an example where 3 characters are used for reading (the -n3 key of the read command ), this is convenient if the script should not respond to single (normal asci) characters, and in our case it is unacceptable.

    We will set up a variable-flag, which we will set when receiving an ESC-symbol, increment when receiving the remaining 2 characters of the sequence, and then fold to 0 and call the corresponding processing function, while we will ignore all unnecessary clicks and sequences, waiting only listed above.

    Note that the ESC - character in the script code is written as ^ [ , in order to type it in the terminal (or in the console editor vim, nano), press Ctrl + V, and then 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        # вызываем соотв. функцию обработки
    

    and so on for all the button codes that interest us.

    Functions

    When writing a script, the repeating parts of the code, or simply separate in meaning, are conveniently formatted as functions, this is done simply:
    Example: a function that takes one argument (via the $ 1 variable)

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

    Function call (pass the variable in the parameter):

    
     133     svn_get_list "$SVN_PATH_PTR"
    

    A function can take one or more arguments, it is not controlled in any way, so if a function expects arguments, and you call it without them, it may not work as you expected or even throw an error or warning.

    Screen

    The output to the screen is carried out by the echo command , sequentially we draw everything that is necessary:
    • Headline
    • The path to the current directory with the words "You are here", the list of contents which goes below
    • List of files / directories
    • The team
    Before drawing the next screen, it needs to be cleaned, for this there is a clear shell command ; .
    The screen needs to be redrawn every time, even when the cursor has moved, the output takes time and by sight it is noticeable, nothing can be done.

    Styles:

    For convenience, directories are displayed in bold, and the cursor (the current selected directory is inverse). Again, you can control the font styles in the terminal using ESC sequences, for example:

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

    will output:
    bold text , normal text You
    can do a lot with text in the terminal, colorize, invert, blink, etc. ... More about colors and styles in the terminal is well written here .

    My running script looks like this:

    Bash svn browser v1.1. Usage: h elp, q uit, y copy, x cut, p aste, l og, r emane, d elete, m akedir, e xport, c heckout.
    --------------------------
    You are here [http: // xxxxxxx / xxxxx / _WorkServer]: / _ main_task / bootloader / _trunk /

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

    This is the copied text from the terminal, I did not make a picture, because everything is text-console, the only difference with the original is that there is a cursor, the text is inverted displayed.

    Pages:

    If you have a lot of files in the repository under version control, it is likely that their list will not fit on the screen, and skipping the terminal is not the most beautiful solution. The $ tput lines command comes back to the aid, returning the number of terminal lines in height. Before each output to the screen, we recognize this value, and we take away (reserve) a place for the number of “constantly present” lines:

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

    If the number of lines is more than the limit, with simple manipulations with arithmetic in bash, the list is divided into pages that can be scrolled with the PgUp / PgDown buttons.

    Pipe

    Many people know what a pipe or “pipe” is, it’s a convenient way to associate the output of one thing with something else, I intentionally did not say the utility, because pipe can also link bash commands. Why did I talk about them? Because I came across one interesting feature:
    When something is called via pipe, a child instance of the shell, the so-called sub-shell, is created for it. And the trick is that all the variables of the parent shell are visible in the child, but the child has local copies of all the variables of the parent shell, and cannot “globally” change their values. Those. cannot return anything with the help of them!
    Example, consider the function of displaying a list of files on the screen:

    
    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 }
    

    Functions, in order to display the cursor on the desired line, you need to know the line number on which the cursor is located, and in addition, have a line counter when the equality condition is fulfilled (line 113) invert the text of the line. Great, the cursor is set.
    At the end of the function’s work, we always know the number of lines ( CNT variable ) that will be useful to us in the future (in the cursor movement function, to control the exit from the list).
    But we cannot return the counter value! As soon as we exit the block located on the right side of the pipe (line 109), the CNT variable takes on the same value as before the pipe was created .
    I don’t know other solutions, except how to write another function only to count the number of lines, which as a result will make ehco a variable outside the pipe , and call the result of the block with pipe enclosed in quotation marks `` and assign the parent shell to the variable :

    
      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 }
    
    It's funny, but the code parser did not cope with this design and did not highlight it as it should, in vim, by the way, the backlight also steamed with it;)

    Algorithms

    All script algorithms are quite simple, and those whom they are interested in will easily understand them. In short.

    We start and check if there is a .svn directory, if so, we are in a working copy, we get information about the repository (absolute paths to the repository and to the working copy in it). If the .svn directory is not found, please enter the path to the repository.

    Navigation:

    “Cursor Left” - everything is simple, you need to move to the root level, for this, in the variable containing the path to the working copy, cut everything from the end to the “/” slash, then print the output of the svn list command with the resulting path again . For convenience, it would be nice to leave the cursor on the directory from which you just left, it is easy to do this if you “remember” the “cut off” tail, find it in the list and place the cursor on it.

    “Cursor to the right” - add to the current path what the cursor is on (at the same time, check that it is a directory, not a file, because we cannot go to a file =), the directories at the end have a “/”) , display the output of the svn list command with the path received.

    Teams:

    Clicked “Copy / Cut” - remember the path

    Clicked “Paste” - call the svn utility with the cp / mv command and the path saved in previous commands.

    Clicked “View log / Delete / Rename / Export / Extract” - call the svn utility with the command log / rm / mv / export / co for the current path in the repository, and if necessary, suggest introducing additional parameters (new name (rename), comment )

    For the checkout and export commands, you must enter the path inside the $ HOME home directory as the destination path automatically:
    / home / user_name / src / my_proj
    src / my_proj - right))

    Todo

    All I wanted was done.
    Perhaps in the comments, dear readers will suggest what else should be implemented? ;)
    The plans to think about merg and diff, with the launch of an external editor. But operations are not “daily” and require concentration and attention.

    Send suggestions and bugs by email in the script.

    Conclusion

    The script turned out to be convenient, I use it daily at home and at work. Perhaps it is far from ideal and the bash-scripting canons. In addition, perhaps this is a "bicycle construction" but to invent , it is so nice! )

    Download svnb v1.2
    Download, make executable, run in a working copy directory.
    For convenience, you can copy to / usr / local / bin /
    It is better to create the .bin directory in the hamster, add it to $ PATH and store the scripts there))

    Alternative option



    Thinking and googling, I decided to make auto-completion of the path, because with this feature many problems are solved and surfing in the repository is just as pleasant as in the home directory!
    I found such a script , tested it - it complements something, but not the paths ... read man bash and the Internet and screwed up the paths complement, the result can be downloaded:

    Download bash_completion_svn
    to put it somewhere and execute the source for it
    $source bash_completion_svn
    
    You can add this line to the end of .bashrc, then when bash starts it will happen automatically.
    Using
    We type $ svn list , press [TAB] the path is supplemented to the current working copy, and then everything is as usual)), press [TAB] and enjoy)).

    Material used

    Opennet.ru The art of programming in a shell script language

    Thank you for your attention!

    Also popular now: