├── README.md ├── git-backup ├── git-restore ├── install.sh ├── manpages ├── git-backup.1 └── git-restore.1 └── testing ├── clean testData └── create git repos /README.md: -------------------------------------------------------------------------------- 1 | git-backup 2 | ========== 3 | 4 | 5 | Git-backup v0.2b - August 18th 2012 6 | 7 | 8 | ## NAME 9 | git-backup \- automates the process of creating a backup git bundle of a git repository 10 | 11 | 12 | ## SYNOPSIS 13 | git backup [-d DESTINATION DIR] [-f BUNDLENAME] 14 | 15 | 16 | ## DESCRIPTION 17 | Will run git bundle to create a backup of your git repository in the directory set in your git config file. Use git-restore to unpack a bundle made by git-backup 18 | 19 | 20 | ## INSTALLATION 21 | Use 'sudo install.sh' to copy this script to a directory in your path. Make sure it has executable permissions. The man page will be copied to "/usr/local/man". 22 | 23 | 24 | ## CONFIGURATION 25 | Optionally you can set the following directives in your git configuration file 26 | 27 | Please note that git prioritizes more local config files as explained in the manpage for git-config: 28 | The .git/config file in each repository is used to store the 29 | configuration for that repository, and $HOME/.gitconfig is used to store a per-user configuration as fallback values for the .git/config file. The file /etc/gitconfig 30 | can be used to store a system-wide default configuration. 31 | 32 | backup.directory = string -- the directory for the backup bundles -- default = the directory where your repo is located 33 | backup.prefix-date = boolean -- will prepend the filename with a date in the format: "YYYY-MM-DD - " -- default = true 34 | backup.prefix-time = boolean -- will propend the filename with a time in the format: "HH:MM:SS - " -- default = false 35 | 36 | 37 | ## OPTIONS 38 | 39 | -d, --directory 40 | 41 | The destination directory if you want to override the git config value 42 | 43 | -f, --filename 44 | 45 | If you don't want to use the git repository name. Note that this still sets the date prefix and .git-bundle as extension. You can turn of the date prefix in the git config file. Probably in the future a raw filename option will be allowed so you can be sure of the created filename. 46 | 47 | 48 | ## SEE ALSO 49 | git-restore(1), git(1), git-clone(1), git-pull(1) 50 | 51 | https://github.com/najamelan/git-backup 52 | 53 | 54 | ## BUGS 55 | This is beta software. There is no automated testing and the feature set is limited. 56 | 57 | Please create bug reports and feature requests in the issue tracker of github or better do a pull request. 58 | 59 | For the moment this uses git-bundle. It seems very hard to gain an exact copy of a repository with git bundle. For example the following won't be copied: 60 | 61 | - .git/exclude 62 | - .git/config -> user section 63 | - ... potentially some more things 64 | 65 | So why not use tar or duplicity? We could and probably will at some point. One of the advantages of git bundle is it's improved compression. It repacks the object files and I think it gives a smaller file. Should be tested. 66 | 67 | On the other hand, backup utilities that do exactly this already exists (eg. backup-ninja). 68 | 69 | 70 | ## AUTHOR 71 | Naja Melan najamelan@autistici.org -------------------------------------------------------------------------------- /git-backup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # For documentation please sea man git-backup(1) 4 | # 5 | # TODO: 6 | # - make it a class rather than a function 7 | # - check the standard format of git warnings to be conform 8 | # - do better checking for git repo than calling git status 9 | # - if multiple entries found in config file, specify which file 10 | # - make it work with submodules 11 | # - propose to make backup directory if it does not exists 12 | # - depth feature in git config (eg. only keep 3 backups for a repo - like rotate...) 13 | # - take cmd line arguments as function parameters in order to be able to be called from other scripts. actually make git backup a class 14 | # - TESTING 15 | 16 | require "getoptlong" 17 | 18 | # allow calling from other scripts 19 | def git_backup 20 | 21 | puts "\n" 22 | 23 | # constants: 24 | git_dir_name = '.git' # just to avoid magic "strings" 25 | filename_suffix = '.git-bundle' # will be added to the filename of the created backup 26 | 27 | repo_dir = nil 28 | call_dir = Dir.getwd() 29 | 30 | arguments = parse_command_line() 31 | 32 | # Test if we are inside a git repo 33 | `git status 2>&1` 34 | 35 | if $?.exitstatus != 0 36 | 37 | puts 'fatal: Not a git repository: .git or at least cannot get zero exit status from "git status"' 38 | exit 2 39 | 40 | 41 | else # git status success 42 | 43 | until File::exists?( Dir.getwd + '/' + git_dir_name ) \ 44 | or Dir.getwd == '/' 45 | 46 | 47 | Dir.chdir( '..' ) 48 | end 49 | 50 | 51 | unless File::exists?( Dir.getwd + '/' + git_dir_name ) 52 | 53 | raise( 'fatal: Directory still not a git repo: ' + Dir.getwd ) 54 | 55 | end 56 | 57 | end 58 | 59 | # store the repo directory for later 60 | repo_dir = Dir.getwd 61 | 62 | 63 | # git-config --get of version 1.7.10 does: 64 | # 65 | # if the key does not exist git config exits with 1 66 | # if the key exists twice in the same file with 2 67 | # if the key exists exactly once with 0 68 | # 69 | # if the key does not exist , an empty string is send to stdin 70 | # if the key exists multiple times, the last value is send to stdin 71 | # if exaclty one key is found once, it's value is send to stdin 72 | # 73 | 74 | 75 | # get the setting for the backup directory 76 | # ---------------------------------------- 77 | 78 | if arguments[ "directory" ] == nil 79 | 80 | # git config adds a newline, so remove it 81 | # 82 | directory = `git config --get backup.directory`.chomp! 83 | 84 | 85 | # check exit status of git config 86 | case $?.exitstatus 87 | 88 | when 1 then directory = File.dirname( Dir.getwd ) 89 | 90 | puts "Warning: Could not find backup.directory in your git config file. Please set it.\n See \"man git config\" for more details on git configuration files.\n Defaulting to the same directroy your git repo is in: #{directory}\n\n" 91 | 92 | when 2 then puts 'Warning: Multiple entries of backup.directory found in your git config file. Will use the last one: ' + directory 93 | 94 | else unless $?.exitstatus == 0 then raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus ) end 95 | 96 | end 97 | 98 | 99 | else 100 | 101 | # TODO: error handling and validation. Security 102 | # absolute path 103 | # 104 | if( arguments[ "directory" ][ /^\// ] ) 105 | 106 | directory = arguments[ "directory" ] 107 | 108 | 109 | # relative path 110 | # 111 | else 112 | 113 | directory = Dir.glob( call_dir + '/' + arguments[ "directory" ] )[ 0 ] 114 | 115 | 116 | if directory === nil 117 | 118 | raise( "No such directory: #{arguments[ "directory" ].inspect} in current working directory: #{call_dir.inspect}" ) 119 | 120 | end 121 | 122 | end 123 | 124 | end 125 | 126 | 127 | # verify directory exists 128 | unless File::directory?( directory ) 129 | 130 | raise( 'fatal: backup directory does not exists: "' + directory + '"' ) 131 | 132 | end 133 | 134 | 135 | # The date and time prefix 136 | # ------------------------ 137 | 138 | prefix = '' 139 | prefix_date = Time.now.strftime( '%F' ) + ' - ' # %F = YYYY-MM-DD 140 | prefix_time = Time.now.strftime( '%H:%M:%S' ) + ' - ' 141 | add_date_default = true 142 | add_time_default = false 143 | 144 | prefix += prefix_date if git_config_bool( 'backup.prefix-date', add_date_default ) 145 | prefix += prefix_time if git_config_bool( 'backup.prefix-time', add_time_default ) 146 | 147 | 148 | # be sure to be in the right dir 149 | Dir::chdir( repo_dir ) 150 | 151 | # default bundle name is the name of the repo 152 | bundle_name = Dir.getwd.split( '/' ).last 153 | 154 | # set the name of the file to the first command line argument if given 155 | bundle_name = arguments[ "filename" ] if( arguments[ "filename" ] ) 156 | 157 | bundle_name = File::join( directory, prefix + bundle_name + filename_suffix ) 158 | 159 | puts "Backing up to bundle: #{bundle_name.inspect}\n\n" 160 | 161 | # git bundle will print it's own error messages if it fails 162 | `git bundle create #{bundle_name.inspect} --all --remotes` 163 | 164 | puts "\n" 165 | end # def git_backup 166 | 167 | 168 | 169 | # helper function to call git config to retrieve a boolean setting 170 | def git_config_bool( option, default_value ) 171 | 172 | # get the setting for the prefix-time from git config 173 | config_value = `git config --get #{option.inspect}` 174 | 175 | # check exit status of git config 176 | case $?.exitstatus 177 | 178 | # when not set take default 179 | when 1 then return default_value 180 | 181 | when 0 then return true unless config_value =~ /(false|no|0)/i 182 | 183 | when 2 then puts 'Warning: Multiple entries of #{option.inspect} found in your git config file. Will use the last one: ' + config_value 184 | return true unless config_value =~ /(false|no|0)/i 185 | 186 | else raise( 'fatal: unknown exit status from git-config: ' + $?.exitstatus ) 187 | 188 | end 189 | end 190 | 191 | 192 | 193 | def parse_command_line() 194 | 195 | parser = GetoptLong.new 196 | 197 | parser.set_options( 198 | 199 | [ "-h", "--help" , GetoptLong::NO_ARGUMENT ] , 200 | [ "-d", "--directory", GetoptLong::REQUIRED_ARGUMENT ] , 201 | [ "-f", "--filename" , GetoptLong::REQUIRED_ARGUMENT ] 202 | 203 | ) 204 | 205 | 206 | directory = nil 207 | filename = nil 208 | 209 | 210 | loop do 211 | 212 | begin 213 | 214 | opt, arg = parser.get 215 | 216 | break if not opt 217 | 218 | # Only for debugging purposes... 219 | # puts( opt + " => " + arg ) 220 | 221 | 222 | case opt 223 | 224 | when "-h" 225 | 226 | puts "\nUsage: ..." 227 | puts " run from within a git repository to back it up to a bundle. Run 'man git backup' for more details.\n\n" 228 | exit 0 229 | 230 | 231 | when "-d" 232 | 233 | directory = arg 234 | 235 | 236 | when "-f" 237 | 238 | filename = arg 239 | 240 | end 241 | 242 | 243 | rescue => err 244 | 245 | puts err 246 | break 247 | 248 | end 249 | 250 | end 251 | 252 | 253 | return \ 254 | { 255 | "filename" => filename , 256 | "directory" => directory 257 | } 258 | 259 | end 260 | 261 | # function needs to be called if we are not included in another script 262 | git_backup if __FILE__ == $0 263 | 264 | -------------------------------------------------------------------------------- /git-restore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # 3 | # 4 | # restores a git repository earlier backed up with git-backup 5 | # documentation in man git-restore(1) 6 | # 7 | # ideal scenario: git restore my-repo 8 | # 9 | # looks in git config for the default backup location 10 | # search for the files corresponding to the repo name 11 | # get the most recent 12 | # 13 | # In the future there will be probably 3 scripts, one for backup, one for restore and one with common functionality such as querying git config 14 | # 15 | # Right now, just take to command line parameters 16 | # 17 | # TODO: 18 | # -everything ;) 19 | # 20 | # 21 | 22 | bundle = ARGV[ 0 ] 23 | directory = ARGV[ 1 ] 24 | 25 | 26 | if not File.exists?( bundle ) 27 | 28 | raise( 'fatal: bundle not found: ' + bundle ) 29 | 30 | end 31 | 32 | 33 | # no dir given on cmd line 34 | # 35 | if directory === nil 36 | 37 | directory = ( Dir.getwd + '/' + bundle ).gsub( /\.[^\/.]+$/ , '' ) 38 | 39 | end 40 | 41 | 42 | # Destination already exists 43 | # 44 | if File::directory?( directory ) 45 | 46 | raise( 'fatal: destination directory exists! Overwriting not supported for the moment.' ) 47 | 48 | end 49 | 50 | 51 | 52 | `git clone --mirror "#{bundle}" "#{directory}/.git"` 53 | 54 | Dir::chdir( "#{directory}" ) 55 | 56 | `git config core.bare false` 57 | `git config core.logallrefupdates true` 58 | `git remote rm origin` 59 | `git checkout` 60 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | cp --remove-destination --force git-backup /usr/lib/git-core/ 5 | cp --remove-destination --force git-restore /usr/lib/git-core/ 6 | 7 | gzip --to-stdout manpages/git-backup.1 > /usr/share/man/man1/git-backup.1.gz 8 | gzip --to-stdout manpages/git-restore.1 > /usr/share/man/man1/git-restore.1.gz -------------------------------------------------------------------------------- /manpages/git-backup.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for git-backup. 2 | 3 | 4 | .TH man 1 "August 18th 2012" "git-backup v0.2b" "GIT-BACKUP" 5 | 6 | 7 | .SH NAME 8 | git-backup \- automates the process of creating a backup git bundle of a git repository 9 | 10 | 11 | .SH SYNOPSIS 12 | git backup [-d DESTINATION DIR] [-f BUNDLENAME] 13 | 14 | 15 | .SH DESCRIPTION 16 | Will run git bundle to create a backup of your git repository in the directory set in your git config file. Use git-restore to unpack a bundle made by git-backup 17 | 18 | 19 | .SH INSTALLATION 20 | Use 'sudo install.sh' to copy this script to a directory in your path. Make sure it has executable permissions. The man page will be copied to "/usr/local/man". 21 | 22 | 23 | .SH CONFIGURATION 24 | Optionally you can set the following directives in your git configuration file 25 | 26 | Please note that git prioritizes more local config files as explained in the manpage for git-config: 27 | The .git/config file in each repository is used to store the 28 | configuration for that repository, and $HOME/.gitconfig is used to store a per-user configuration as fallback values for the .git/config file. The file /etc/gitconfig 29 | can be used to store a system-wide default configuration. 30 | 31 | backup.directory = string -- the directory for the backup bundles -- default = the directory where your repo is located 32 | .br 33 | backup.prefix-date = boolean -- will prepend the filename with a date in the format: "YYYY-MM-DD - " -- default = true 34 | .br 35 | backup.prefix-time = boolean -- will propend the filename with a time in the format: "HH:MM:SS - " -- default = false 36 | 37 | 38 | .SH OPTIONS 39 | 40 | .B -d, --directory 41 | 42 | The destination directory if you want to override the git config value 43 | 44 | .B -f, --filename 45 | 46 | If you don't want to use the git repository name. Note that this still sets the date prefix and .git-bundle as extension. You can turn of the date prefix in the git config file. Probably in the future a raw filename option will be allowed so you can be sure off the created filename. 47 | 48 | 49 | .SH SEE ALSO 50 | git-restore(1), git(1), git-clone(1), git-pull(1) 51 | 52 | https://github.com/najamelan/git-backup 53 | 54 | 55 | .SH BUGS 56 | This is beta software. There is no automated testing and the feature set is limited. 57 | 58 | Please create bug reports and feature requests in the issue tracker of github or better do a pull request. 59 | 60 | For the moment this uses git-bundle. It seems very hard to gain an exact copy of a repository with git bundle. For example the following won't be copied: 61 | 62 | - .git/exclude 63 | - .git/config -> user section 64 | - ... potentially some more things 65 | 66 | So why not use tar or duplicity? We could and probably will at some point. One of the advantages of git bundle is it's improved compression. It repacks the object files and I think it gives a smaller file. Should be tested. 67 | 68 | On the other hand, backup utilities that do exactly this already exists (eg. backup-ninja). 69 | 70 | 71 | .SH AUTHOR 72 | Naja Melan najamelan@autistici.org -------------------------------------------------------------------------------- /manpages/git-restore.1: -------------------------------------------------------------------------------- 1 | .\" Manpage for git-restore. 2 | 3 | 4 | .TH man 1 "06 May 2012" "Git-restore v0.1" "GIT-restore" 5 | 6 | 7 | .SH NAME 8 | git-restore \- recovers git repositories from backup files made by git-backup 9 | 10 | 11 | .SH SYNOPSIS 12 | git restore BUNDLENAME LOCATION 13 | 14 | 15 | .SH DESCRIPTION 16 | Will run git-clone --mirror to make a repository out of the given bundle and will then remove the remote, and turn the bare repo into a working tree. Currently this is a very crude script without any errorhandling whatsoever. Use at your own risk. 17 | 18 | .SH INSTALLATION 19 | Use 'sudo install.sh' to copy this script to a directory in your path. Make sure it has executable permissions. The man page will be copied to "/usr/local/man". 20 | 21 | 22 | .SH OPTIONS 23 | Git-restore does not take any options. However, you should supply a bundlename and a location for the new repo. 24 | 25 | 26 | .SH SEE ALSO 27 | git-backup(1), git(1), git-clone(1), git-pull(1) 28 | 29 | https://github.com/najamelan/git-backup 30 | 31 | 32 | .SH BUGS 33 | Should be fixed instead of listed here 34 | 35 | Please create bug reports in the issue tracker of github or better do a pull request. 36 | 37 | 38 | .SH AUTHOR 39 | Naja Melan najamelan@autistici.org -------------------------------------------------------------------------------- /testing/clean testData: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | cd testData 5 | 6 | rm -rf subRemote super submoduleBundle bundledSub *.git-bundle 7 | ls -Ralh -------------------------------------------------------------------------------- /testing/create git repos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git --version 4 | 5 | cd testData 6 | 7 | mkdir super 8 | mkdir subRemote 9 | 10 | touch super/superFile.txt 11 | touch subRemote/subFile.txt 12 | 13 | cd super 14 | 15 | git init 16 | git add --all 17 | git commit -am"Initial commit" 18 | 19 | cd .. 20 | 21 | 22 | cd subRemote 23 | 24 | git init 25 | git add --all 26 | git commit -am"Initial commit" 27 | 28 | cd .. 29 | 30 | 31 | cd super 32 | 33 | git submodule add ../subRemote/.git 34 | git add --all 35 | git commit -am"added submodule" 36 | git submodule update 37 | 38 | 39 | cd subRemote 40 | 41 | echo -e "\ngit backup:" 42 | ../../../../git-backup -d ../.. 43 | 44 | echo -e "\ngit log in subRemote:" 45 | git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --date=relative --all 46 | 47 | 48 | cd .. 49 | 50 | cd .. 51 | 52 | 53 | ../../git-restore *.git-bundle bundledSub 54 | 55 | 56 | 57 | 58 | #------------------------------------------------ 59 | 60 | cd super 61 | 62 | echo -e "\nfiles in super": 63 | ls -alh 64 | 65 | cd .. 66 | 67 | 68 | cd super/subRemote 69 | 70 | echo -e "\nfiles in super/subRemote": 71 | ls -alh 72 | 73 | cd ../.. 74 | 75 | 76 | cd bundledSub 77 | 78 | echo -e "\nfiles in bundledSub": 79 | ls -alh 80 | 81 | cd .. --------------------------------------------------------------------------------