├── .gitignore
├── README.md
├── svn-stash.py
└── svn_stash_register.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pyc
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | svn-stash
2 | ==========
3 |
4 | It's like the git stash command, but for Subversion. If you don't know git, you should read [this guide.](http://git.or.cz/course/svn.html)
5 | Svn-stash permits you to hide the changes that you don't want to commit just now. this can be more useful in some circunstances.
6 |
7 | Why?
8 | ----------
9 |
10 | I love git and I think that it should be used in the new projects that WHATEVER programmer starts (If you don't think the same, You are welcome to discuss it with me, but you can read [the pro git book](http://git-scm.com/book) before. :) ). However, in some old projects where I'm working now the svn-to-git migration is very difficult or imposible. Git has a set of awesome commands I usually use, (like stash) that svn hasn't direct equivalent. Svn-stash is an attempt to port some of the functionalities of the git stash command to subversion.
11 |
12 | How to Install
13 | ----------
14 |
15 | This command only is a common python script for now. If you want to use as a regular command, you can add a alias in your .bashrc or .bash_profile file.
16 | ```bash
17 | git clone https://github.com/frankcortes/svn-stash.git
18 | mv svn-stash ~/.svn-stash-command
19 | ```
20 | add this line in .bashrc(Linux) /.bash_profile(MAC OS X):
21 | ```bash
22 | alias svn-stash='python ~/.svn-stash-command/svn-stash.py'
23 | ```
24 |
25 |
26 | Documentation
27 | ----------
28 |
29 | ### push ######
30 | This command will save all changes in a secure directory to be later recovered.
31 | ### pop ######
32 | This command will recover all changes of the last stash.
33 | ### list ######
34 | List all saved stashes.
35 | ### show ######
36 | Show all changes of the files have been stashed with diff format, for each one of the stashes.
37 | ### clear ######
38 | Delete all saved stashes.
39 |
40 |
41 | GPL License
42 | ------------
43 | This program is free software: you can redistribute it and/or modify
44 | it under the terms of the GNU General Public License as published by
45 | the Free Software Foundation, either version 3 of the License, or
46 | (at your option) any later version.
47 |
48 | This program is distributed in the hope that it will be useful,
49 | but WITHOUT ANY WARRANTY; without even the implied warranty of
50 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
51 | GNU General Public License for more details.
52 |
53 | You should have received a copy of the GNU General Public License
54 | along with this program. If not, see .
--------------------------------------------------------------------------------
/svn-stash.py:
--------------------------------------------------------------------------------
1 | # This file is part of svn-stash.
2 |
3 | # svn-stash is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 |
8 | # svn-stash is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 |
13 | # You should have received a copy of the GNU General Public License
14 | # along with svn-stash. If not, see .
15 |
16 | import os,sys
17 | import random
18 | from datetime import datetime
19 | from svn_stash_register import svn_stash_register,svn_stash,HOME_DIR,CURRENT_DIR,SVN_STASH_DIR,COMMAND_DEFAULT,TARGET_FILE_DEFAULT
20 |
21 | def execute_stash_push(target_file,filename_list):
22 | if len(filename_list)>0:
23 | #save the svn status into a stash
24 | stash = svn_stash()
25 | stash.push(target_file,filename_list)
26 | register = svn_stash_register()
27 | register.register_stash(stash)
28 | register.write()
29 | else:
30 | print "nothing to stash in this directory."
31 |
32 | def execute_stash_pop(target_file,filename_list):
33 | #obtain last stash pop
34 | register = svn_stash_register()
35 | stash = register.obtain_last_stash()
36 | if stash:
37 | stash.pop()
38 | register.delete_stash(stash)
39 | else:
40 | print "there are not previous stashes."
41 |
42 | def execute_stash_list(target_file,filename_list):
43 | #obtain the list of stashes.
44 | register = svn_stash_register()
45 | for stash_id in register.stashes:
46 | print stash_id
47 |
48 | def execute_stash_clear(target_file,filename_list):
49 | #delete all stashes.
50 | register = svn_stash_register()
51 | marked_stashes = list(register.stashes)
52 | for stash in marked_stashes:
53 | current_stash = svn_stash()
54 | current_stash.load(stash)
55 | register.delete_stash(current_stash)
56 |
57 | def execute_stash_show(target_file,filename_list):
58 | #view all diffs of all stashes.
59 | register = svn_stash_register()
60 | for stash_id in register.stashes:
61 | current_stash = svn_stash()
62 | current_stash.load(stash_id)
63 | print current_stash
64 |
65 | def execute_stash_help(target_file,filename_list):
66 | b = "\033[1m"
67 | end_b = "\033[0m"
68 | help_content = "SVN STASH\n"
69 | help_content += "\n" + b + "NAME" + end_b + "\n"
70 | help_content += "svn-stash - Stash the changes in a dirty working directory away\n"
71 | help_content += "\n"+ b + "SYNOPSIS" + end_b + "\n"
72 | help_content += "\tsvn stash list\n"
73 | help_content += "\tsvn stash show\n"
74 | help_content += "\tsvn stash push\n"
75 | help_content += "\tsvn stash pop\n"
76 | help_content += "\tsvn stash clear\n"
77 | help_content += "\tsvn stash help\n"
78 | help_content += "\n" + b + "DESCRIPTION" + end_b +"\n"
79 | help_content += "\tSvn-stash permits you to hide the changes that you don't want to commit just now. this can be more useful in some circunstances.\n"
80 | print help_content
81 |
82 |
83 | #Parser order and file of the command
84 | def execute_svn_stash(command,target_file,filename_list):
85 | #print command+","+target_file
86 | if command == "push":
87 | execute_stash_push(target_file,filename_list)
88 | elif command == "pop":
89 | execute_stash_pop(target_file,filename_list)
90 | elif command == "list":
91 | execute_stash_list(target_file,filename_list)
92 | elif command == "clear":
93 | execute_stash_clear(target_file,filename_list)
94 | elif command == "show":
95 | execute_stash_show(target_file,filename_list)
96 | elif command == "help":
97 | execute_stash_help(target_file,filename_list)
98 |
99 | #obtain the svn status files
100 | def obtain_svn_status_files():
101 | status_files = []
102 | status_list = os.popen('svn st').read()
103 | status_list = status_list.split("\n")
104 | for line in status_list:
105 | words = line.split()
106 | if len(words) > 1:
107 | elements = line.split()
108 | status = elements[0]
109 | filename = elements[1]
110 | if status == "M":
111 | status_files.append(filename)
112 | return status_files
113 |
114 | def main(args):
115 | command = COMMAND_DEFAULT
116 | if len(args)>1:
117 | command = args[1]
118 |
119 | target_file = TARGET_FILE_DEFAULT
120 | if len(args)>2:
121 | target_file = args[2]
122 |
123 | filename_list = obtain_svn_status_files()
124 | execute_svn_stash(command,target_file,filename_list)
125 |
126 |
127 | if __name__ == '__main__':
128 | main(sys.argv)
129 |
--------------------------------------------------------------------------------
/svn_stash_register.py:
--------------------------------------------------------------------------------
1 | # This file is part of svn-stash.
2 |
3 | # svn-stash is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 |
8 | # svn-stash is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU General Public License for more details.
12 |
13 | # You should have received a copy of the GNU General Public License
14 | # along with svn-stash. If not, see .
15 |
16 | import os,sys
17 | import random
18 | from datetime import datetime
19 |
20 | HOME_DIR = os.path.expanduser("~")
21 | CURRENT_DIR = os.getcwd()
22 | SVN_STASH_DIR= HOME_DIR + "/.svn-stash"
23 | COMMAND_DEFAULT="push"
24 | TARGET_FILE_DEFAULT="all"
25 | STASH_REGISTER_FILENAME = ".stashed_register"
26 |
27 | class svn_stash_register:
28 | """A class to register all stashes."""
29 | def __init__(self):
30 | self.stashes = [] #list of stashes in the current dir
31 | self.all_stashes = [] #list of all stashes in all directories
32 | self.load() #load register
33 |
34 | def load(self):
35 | try:
36 | create_stash_dir_if_any()
37 | current_dir = SVN_STASH_DIR + "/" + STASH_REGISTER_FILENAME
38 | with open(current_dir,"r") as f:
39 | for line in f:
40 | content = line.rstrip()
41 | content = content.split(" ")
42 | if len(content)>0:
43 | stash_id = content[0]
44 | if is_a_current_stash(stash_id):
45 | self.stashes.append(stash_id)
46 | self.all_stashes.append(stash_id)
47 | f.close()
48 | except IOError as e:
49 | print e
50 | print 'registerFile cannot be readed.'
51 |
52 | def write(self):
53 | try:
54 | create_stash_dir_if_any()
55 | current_dir = SVN_STASH_DIR + "/" + STASH_REGISTER_FILENAME
56 | with open(current_dir,"w") as f:
57 | content = []
58 | for stash_id in self.all_stashes:
59 | line = str(stash_id) + "\n"
60 | content.append(line)
61 | f.writelines(content)
62 | f.close()
63 | except IOError as e:
64 | print 'registerFile cannot be created.'
65 |
66 | def obtain_last_stash(self):
67 | length = len(self.stashes)
68 | if length>0:
69 | stash = svn_stash()
70 | stash_id = self.stashes[length-1]
71 | stash.load(stash_id)
72 | return stash
73 | return False
74 |
75 | def register_stash(self,stash): #stash must be a svn-stash instance
76 | stash_id = stash.key
77 | self.stashes.append(stash_id)
78 | self.all_stashes.append(stash_id)
79 | stash.write()
80 | print "create stash " + str(stash_id)
81 |
82 | def delete_stash(self,stash):
83 | stash_id = stash.key
84 | self.stashes.remove(stash_id)
85 | self.all_stashes.remove(stash_id)
86 | self.write()
87 | #Remove stash files
88 | stash.clear()
89 | print "delete stash " + str(stash_id)
90 |
91 | class svn_stash:
92 | """A class to contain all information about stashes."""
93 | def __init__(self):
94 | self.files = {} #dictionary of files
95 | self.timestamp = datetime.now() #time of creation
96 | self.key = random.getrandbits(128) #unique identifier
97 | self.root_url = CURRENT_DIR
98 |
99 | def push(self,target_file,filename_list):
100 | create_stash_dir_if_any()
101 | if target_file == "all":
102 | for filename in filename_list:
103 | self.push(filename,filename_list)
104 | else:
105 | randkey = random.getrandbits(128) #unique identifier
106 | self.files[target_file] = randkey
107 | result = os.popen("svn diff " + target_file + " > " + SVN_STASH_DIR + "/" + str(randkey) + ".stash.patch").read()
108 | result += os.popen("svn revert " + target_file).read()
109 | #print "push " + target_file
110 |
111 | def pop(self):
112 | result = ""
113 | if os.path.exists(SVN_STASH_DIR):
114 | for target_file in self.files:
115 | randkey = self.files[target_file]
116 | result = os.popen("patch -p0 < " + SVN_STASH_DIR + "/" + str(randkey) + ".stash.patch").read()
117 | result += os.popen("rm " + SVN_STASH_DIR + "/" + str(randkey) + ".stash.patch").read()
118 | #print "pop " + target_file
119 | #delete the file of svn_stash
120 | result += os.popen("rm " + SVN_STASH_DIR + "/" + str(self.key)).read()
121 |
122 | def write(self):
123 | #Create file for svn stash
124 | try:
125 | current_dir = SVN_STASH_DIR + "/" + str(self.key)
126 | with open(current_dir,"w") as f:
127 | content = []
128 | #add the first line with root url
129 | line = self.root_url + "\n"
130 | content.append(line)
131 | for target_file in self.files:
132 | line = target_file + " " + str(self.files[target_file]) + "\n"
133 | content.append(line)
134 | f.writelines(content)
135 | f.close()
136 | except IOError as e:
137 | print 'randFile cannot be created.'
138 |
139 | def clear(self):
140 | result = ""
141 | if os.path.exists(SVN_STASH_DIR):
142 | for target_file in self.files:
143 | randkey = self.files[target_file]
144 | result += os.popen("rm " + SVN_STASH_DIR + "/" + str(randkey) + ".stash.patch").read()
145 | result += os.popen("rm " + SVN_STASH_DIR + "/" + str(self.key)).read()
146 |
147 | def load(self,stash_id):
148 | try:
149 | current_dir = SVN_STASH_DIR + "/" + str(stash_id)
150 | with open(current_dir,"r") as f:
151 | is_first = True
152 | for line in f:
153 | content = line.rstrip()
154 | #if is the first line, then it is the root url
155 | if is_first:
156 | self.root_url = content
157 | is_first = False
158 | #it is stashed filename, otherwise
159 | else:
160 | content = content.split(" ")
161 | if len(content)>=2:
162 | self.files[content[0]] = content[1]
163 | self.key = stash_id
164 | f.close()
165 | except IOError as e:
166 | print 'randFile cannot be readed.'
167 |
168 | def __str__(self):
169 | content = print_hr(70)
170 | content += "stash " + str(self.key)
171 | content += print_hr(70)
172 | content += "root in: <" + self.root_url + ">\n"
173 | for filename in self.files:
174 | try:
175 | real_dir = filename + ".stash.patch"
176 | current_dir = SVN_STASH_DIR + "/" + self.files[filename] + ".stash.patch"
177 | content += print_hr()
178 | content += "file " + real_dir
179 | content += print_hr()
180 | with open(current_dir,"r") as f:
181 | for line in f:
182 | content += line
183 | f.close()
184 | except IOError as e:
185 | content += 'randFile cannot be shown.\n'
186 | return content
187 |
188 |
189 | ########################
190 | #Auxiliar functions #
191 | ########################
192 | #Create stash directory
193 | def create_stash_dir_if_any():
194 | if not os.path.exists(SVN_STASH_DIR):
195 | os.makedirs(SVN_STASH_DIR)
196 | stash_register_file = SVN_STASH_DIR + "/" + STASH_REGISTER_FILENAME
197 | if not os.path.exists(stash_register_file):
198 | try:
199 | f = open(stash_register_file, "w")
200 | except IOError:
201 | print "registerFile cannot be created."
202 |
203 | def print_hr(lng=30):
204 | return "\n" + ("-"*lng) + "\n"
205 |
206 | def is_a_current_stash(stash_id):
207 | stash = svn_stash()
208 | stash.load(stash_id)
209 | current_dir_parts = CURRENT_DIR.split("/")
210 | stash_dir_parts = stash.root_url.split("/")
211 | stash_dir_parts = stash_dir_parts[:len(current_dir_parts)]
212 | stash_dir = "/".join(stash_dir_parts)
213 | if ".svn" in os.listdir(CURRENT_DIR):
214 | return stash_dir == CURRENT_DIR
215 | return False
216 |
--------------------------------------------------------------------------------