├── .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 | --------------------------------------------------------------------------------