📜 ⬆️ ⬇️

svn + bash = write 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 describes an example of writing an interactive bash script of the svn browser that runs in the terminal and allows you to perform several “daily” operations on the repository tree , namely:In this case, 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: <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/" 

Under the review of the review of the internals, the result can be downloaded at svnb link
Make it executable, run in the svn working copy directory (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 added another solution to improve the usability of the command line svn - autocompletion of the path .

What for?

Subversion (svn) is a popular version control system that is often used by software developers and not only. To work with it, there are such GUI applications as TortoiseSVN, rapidSVN, etc. which inter alia are integrated with the explorer / window manager / file browser.
Conveniently, what to say, but GUIs are not always appropriate: work on ssh on the server, the absence of X-server, or the banal desire not to use the GUI where you can not use them - love of the command line)). In 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 required 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 doing a bunch of svn list from the root to the desired directory is inconvenient, if only because there is no auto-completion of file names in the svn path argument .

What to do?

In the working copy of svn there is always a directory .svn / in which, among other useless garbage, there is a file .svn / entries, which contains lines containing absolute paths to the repository and working copy. It’s easy to verify this by running:

 $cat .svn/entries | grep :// 

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

We write!

I would like to write something very simple , intuitive, with minimal gestures allowing you to perform such operations as viewing logs and export, 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 the button presses on which the svn functions are “hung” and renders the result on the screen.


Define the necessary control commands:
The buttons are defined at the beginning of the script, you can peredefaynit 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.

The “Exit” command ends the script 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 in long paths and not do a dozen svn list . Just go to the desired path, close the script with the "Exit" command ( q uit) and copy the output path.

Catching pressed buttons

To read the keyboard, we need an infinite loop to wait for the input of a button; for this we use the following construction:

 ############     ############# 233 #   stdin, -s  , -n1    234 while read -s -n1 key 235 do ... 281 done 

The read command returns control only when it considers the entire line and waits for Enter , 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 . The echo is also not for us (key -s ), we will display everything we need on the screen. : D

Cursor keys

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 - Arrow Down
^ [C - Arrow Right
^ [D - Left Arrow

There is an example on opennet.ru , where reading 3 characters each ( read command -n3 key) is used, this is convenient if the script does not need to respond to single (usual asci) characters, and in our case it is unacceptable.

We will enter a variable-flag, which we will set when accepting the ESC-symbol, increment when accepting the remaining 2 characters of the sequence, and then drop to 0 and call the corresponding processing function, while ignoring all unnecessary keystrokes and sequences, expecting 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 codes of buttons that interest us.


When writing a script, duplicate parts of the code or simply separate in meaning, it is convenient to draw up in a function, this is done simply:
Example: a function that takes one argument (via the variable $ 1)

 40 ###########    ############### 41 function svn_get_list { 42 SVN_LIST="`svn list $SVN_REPO_PATH/$1`" 43 } 

Function call (we transfer a variable in parameter):

 133 svn_get_list "$SVN_PATH_PTR" 

A function can take one or more arguments, this is not monitored, so if the function expects arguments, and you call it without them, it may not work as you would expect or completely throw out an error or warning.


The output to the screen is carried out by the echo command, we consistently draw everything that is needed:Before drawing the next screen, it must be cleared; there is a clear shell command for this ; .
The screen needs to be redrawn every time, even when the cursor has moved, the output takes time and it is noticeable by eye, nothing can be done.


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

 echo "^[[1m   ^[[0m  ." 

will output:
bold text , normal text
With the text in the terminal, you can do a lot of things, color, invert, blink, etc.… More information 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 /

commandline /
firmware /

This is a copy of the text from the terminal, I didn’t do the image, because all of the text-based console, the only difference with the original is that there is a cursor, an inversely displayed text.


If you have a lot of files in the repository under the control of versions, it is likely that their list will not fit into the screen, and scrolling the terminal is not the most beautiful solution. The $ tput lines command that returns the number of terminal lines by height comes to the rescue. Before each output on the screen we will know this value, and we take away (we reserve) the space for the number of “constantly present” lines:

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

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


Many people know what a pipe or "channel" is, it is a convenient way to associate the output of something one with something else, I deliberately did not say the utility, because You can also bind pipe connections. Why did I talk about them? Because I ran into one interesting feature:
When something is called through a pipe, a child shell instance is created for it, the so-called sub-shell. 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. can not 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 also have a line count when the equality condition is met (line 113) to invert the text of the line. Great, the cursor is set.
At the end of the function, we always know the number of lines (variable CNT ) which will be useful to us later (as a function of moving the cursor, to control the output outside the list).
But we cannot return the value of the counter! As soon as we exit the block that is on the right side of the pipe (line 109), the CNT variable also takes on the value that was before the pipe was created.
I don’t know any other solutions, except for how to write another function only for counting lines, which as a result will make the ehco variable outside the pipe , and call the result of the block with pipe to the prisoners `` and assign the variable to the "parent" 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 } 
It's funny, but the code parser didn’t cope with this construction and didn’t highlight it as it should be, in vim, by the way, the illumination with it was also steamed;)


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 yes - 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.


“Cursor Left” - everything is simple, you need to move to the level of the root, to do this, in a 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 again with the resulting path. For convenience, the cursor would not be bad to leave on the directory from which you just left, this is easy to do if you “remember” the “cut off” tail, find it in the list and set the cursor on it.

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


Clicked "Copy / Cut" - remember the path

Click "Paste" - call the svn utility with the command cp / mv and the path saved in the previous commands.

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

For the checkout and export commands, you need to enter the path inside the $ HOME home directory as the destination path, and it is automatically substituted in the beginning of the path:
/ home / user_name / src / my_proj
src / my_proj - right))


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

Send suggestions and bugs by email in the script.


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

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

Alternative option

After thinking and googling, I decided to autocomplete the path, because with such a feature many problems are solved and surfing the repository is as pleasant as in the home directory!
I found such a script , started it - it complements something, but not the ways ... read man bash and the Internet and screwed the paths addition, the result can be downloaded:

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

Material used

Opennet.ru The Art of Programming in the Command Shell Scripting Language

Thanks for attention!

Source: https://habr.com/ru/post/104062/

All Articles