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