-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgit-backup
executable file
·169 lines (138 loc) · 4.74 KB
/
git-backup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#!/usr/bin/env ruby
require 'trollop'
require 'fileutils'
$program = File.basename(__FILE__)
opts = Trollop::options do
banner <<-EOT
Usage: #$program [OPTIONS] [<output-dir>]
You may omit <output-dir>, if you define GIT_BACKUP_DIR environment variable
EOT
opt :quite, "Be quite - no output at all"
opt :untracked, "Backup untracked files. Note: the path is not preserved, thus only one of files with the same name is stored."
opt :verbose, "Be verbose"
opt :zip, "Compress the output to a zip-archieve"
end
$backup_dir_holder = ARGV.empty? ? ENV['GIT_BACKUP_DIR'] : File.realpath(ARGV[0])
Trollop::die "Specify output directory for storing the backup" if $backup_dir_holder == nil or $backup_dir_holder.empty?
Trollop::die "Too many arguments #{ARGV.inspect}" if ARGV.size > 1
Trollop::die "Output dir #$backup_dir_holder doesn't exist" unless File.exists? $backup_dir_holder
Trollop::die "Output dir #$backup_dir_holder is not a directory" unless File.directory? $backup_dir_holder
# it checks if we call from a Git repo;
# it is also needed for untracked files, because they are filtered out by current dir
def chdir_to_git_root
until Dir.entries(Dir.pwd).include?(".git")
Trollop::die "#$program should be called from a Git repository" if Dir.pwd == "/"
Dir.chdir ".."
end
end
chdir_to_git_root
timestamp = Time.now.strftime("%Y-%m-%d-%H-%M-%S")
$backup_name = "gitbackup_#{File.basename(Dir.pwd)}_#{timestamp}"
$verbose = opts[:verbose]
$untracked = opts[:untracked]
$quite = opts[:quite]
$zip = opts[:zip]
$backup_dir = if ($zip)
require 'tmpdir'
File.join(Dir.tmpdir, $backup_name)
else
File.join($backup_dir_holder, $backup_name)
end
def run(command)
puts command if $verbose
output = `#{command}`
puts output if $verbose
end
def putq(string)
puts string unless $quite
end
def backup_tracking_branches(tracking_branches)
return if tracking_branches.empty?
putq "Saving tracking branches (#{tracking_branches.size})..."
fbdir = File.join($backup_dir, "tracking_branches")
FileUtils.mkdir_p(fbdir)
tracking_branches.each do |branch, tracked|
bdir = File.join(fbdir, branch)
FileUtils.mkdir_p(bdir)
run "git format-patch --output-dir=#{bdir} #{tracked}..#{branch}"
end
end
# TODO now comparing only with master, should find the closest tracking branch
def backup_feature_branches(feature_branches)
return if feature_branches.empty?
puts "Saving feature branches (#{feature_branches.size})..."
fbdir = File.join($backup_dir, "feature_branches")
FileUtils.mkdir_p(fbdir)
feature_branches.each do |branch|
bdir = File.join(fbdir, branch)
FileUtils.mkdir_p(bdir)
run "git format-patch --output-dir=#{bdir} master..#{branch}"
end
end
def backup_local
putq "Saving local unstaged changes..."
run "git diff > #{$backup_dir}/unstaged_local_changes.patch"
putq "Saving local staged changes..."
run "git diff --cached > #{$backup_dir}/staged_local_changes.patch"
end
def backup_stash
numstashes = `git stash list`.chomp.split("\n").size
return if numstashes == 0
puts "Saving stashes (#{numstashes})..."
sdir = File.join($backup_dir, "stash")
Dir.mkdir(sdir)
numstashes.times do |i|
num = format("%04d", i)
run "git stash show -p stash@{#{i}} > #{sdir}/#{num}_stash.patch"
end
end
def backup_untracked
files = `git ls-files -o`.chomp.split("\n")
return if files.empty?
putq "Saving untracked files (#{files.size}) ..."
sdir = File.join($backup_dir, "untracked")
Dir.mkdir sdir
files.each do |file|
FileUtils.cp file, sdir, :verbose => $verbose
end
end
putq "Collecting information..."
branches = []
`git branch`.split("\n").each do |line|
if line.start_with?("* ")
branches << line[2..-1]
else
branches << line[2..-1]
end
end
tracking_branches = {}
feature_branches = []
branches.each do | branch|
remote = `git config branch.#{branch}.remote`.chomp
rembranch = `git config branch.#{branch}.merge`.chomp
if not remote.empty? and not rembranch.empty?
rembranch["refs/heads/"] = "" if rembranch.start_with? "refs/heads"
rembranch = remote + "/" + rembranch
tracking_branches[branch] = rembranch
else
feature_branches << branch
end
end
puts "Tracking branches: #{tracking_branches.inspect}" if $verbose
puts "Feature branches: #{feature_branches.inspect}" if $verbose
# backup
FileUtils.mkdir_p($backup_dir)
puts "Backing up to #$backup_dir_holder" if $verbose
backup_local
backup_tracking_branches(tracking_branches)
backup_feature_branches(feature_branches)
backup_stash
backup_untracked if $untracked
# zip
if $zip
Dir.chdir Dir.tmpdir
$zipfile = "#{File.join($backup_dir_holder, $backup_name)}.zip"
run "zip -r #$zipfile #$backup_name"
end
putq "Finished."
puts $zip ? `unzip -l #$zipfile` : `tree #$backup_dir` if $verbose