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:
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 .
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:
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:
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 .
In the working copy of svn there is always a .svn / directory in which, among otheruseless 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:
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.
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.
We define the necessary control commands:
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.
To read the keyboard, we need an infinite waiting loop for the button input, for this we use this design:
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
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.
and so on for all the button codes that interest us.
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)
Function call (pass the variable in the parameter):
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.
The output to the screen is carried out by the echo command , sequentially we draw everything that is necessary:
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.
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:
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.
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:
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.
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:
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 :
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.
“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.
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))
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.
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))
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
Opennet.ru The art of programming in a shell script language
Thank you for your attention!
- Surf the repository
- View logs
- Copy directories to create tags / branchs
- Create / delete / rename directories
- Extract / export (checkout / export)
$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
$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
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
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:
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!