PageRenderTime 57ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/backup_on_the_go.rb

https://bitbucket.org/xuhdev-backup/backup-on-the-go
Ruby | 245 lines | 142 code | 38 blank | 65 comment | 23 complexity | 7373ff4c58f6bdc34caf0ab72f25832f MD5 | raw file
  1. # encoding: utf-8
  2. require 'github_api'
  3. require 'bitbucket_rest_api'
  4. require 'highline/import'
  5. require 'tmpdir'
  6. require 'colorize'
  7. # The module of BackupOnTheGo
  8. module BackupOnTheGo #:nodoc:#
  9. DEFAULT_CONFIG = {
  10. :backup_fork => false,
  11. :backup_private => false,
  12. :git_cmd => 'git',
  13. :github_repos_max => '200',
  14. :is_private => true,
  15. :no_public_forks => true,
  16. :repo_prefix => 'backup-on-the-go-',
  17. :verbose => true
  18. }.freeze
  19. # Back up GitHub repositories to BitBucket.
  20. #
  21. # = Parameters
  22. # * <tt>:backup_fork</tt> - Optional boolean - <tt>true</tt> to back up forked repositories, <tt>false</tt> to skip them. Default is <tt>false</tt>.
  23. # * <tt>:backup_private</tt> - Optional boolean - <tt>true</tt> to back up private repositories, <tt>false</tt> to NOT back up private repositories. Default is <tt>false</tt>.
  24. # * <tt>:bitbucket_password</tt> - Optional string - The password to access the BitBucket account. If not specified, a prompt will show up to ask for the password.
  25. # * <tt>:bitbucket_repos_owner</tt> - Optional string - Owner of the backup repositories on BitBucket. The owner could be a team. If not specified, <tt>:bitbucket_user</tt> will be used.
  26. # * <tt>:bitbucket_user</tt> - *Required* string if <tt>:user</tt> is not specified - The user name on BitBucket. If not specified, <tt>:user</tt> will be used.
  27. # * <tt>:git_cmd</tt> - Optional string - The git command you want to use. Default is 'git'.
  28. # * <tt>:github_password</tt> - Optional string - When backup_private is set to true, this is the password used to access the GitHub account. If not specified, a prompt will show up to ask for the password.
  29. # * <tt>:github_repos_max</tt> - Optional string - The max number of your GitHub repos, since GitHub API requires to give a repo number upper limit. Usually you don't need to set up this number unless you have more than 200 repositories. Default is <tt>"200"</tt>.
  30. # * <tt>:github_repos_owner</tt> - Optional string - The owner of the repositories that need to be backed up. The owner could be an organization. If not specified, <tt>:github_user</tt> will be used.
  31. # * <tt>:github_user</tt> - *Required* string if <tt>:user</tt> is not specified - The user name on GitHub. If not specified, <tt>:user</tt> will be used.
  32. # * <tt>:is_private</tt> - Optional boolean - <tt>true</tt> to make the backup repositories private even if the corresponding github repositories are public; <tt>false</tt> to keep the original privacy. Default is <tt>true</tt>.
  33. # * <tt>:no_public_forks</tt> - Optional boolean - <tt>true</tt> to forbid public fork for the backup repositories, <tt>false</tt> to allow public fork. Only valid for public backup repositories. Default is <tt>true</tt>.
  34. # * <tt>:repo_prefix</tt> - Optional string - The prefix you wanna prepend to the backup repository names. In this way, if you have a repository with the same name on BitBucket, it won't get flushed. Default is <tt>"backup-on-the-go-"</tt>.
  35. # * <tt>:user</tt> - *Required* string if <tt>:github_user</tt> and <tt>:bitbucket_user</tt> are not both specified - The user name of GitHub and BitBucket (if they are same for you). If you want to use different user names on GitHub and BitBucket, please specify <tt>:github_user</tt> and <tt>:bitbucket_user</tt> instead.
  36. # * <tt>:verbose</tt> - Optional boolean - <tt>true</tt> to print additional information and <tt>false</tt> to suppress them. Default is <tt>true</tt>.
  37. #
  38. # = Examples
  39. #
  40. # # Back up personal public repositories only
  41. # BackupOnTheGo.backup :github_user => 'github_user_name',
  42. # :bitbucket_user => 'bitbucket_user_name',
  43. # :is_private => false, # make backup repositories public
  44. # :bitbucket_password => 'bitbucket_password',
  45. # :repo_prefix => '' # don't need any prefix
  46. #
  47. # = Examples
  48. #
  49. # # Back up personal public and private repositories
  50. # BackupOnTheGo.backup :github_user => 'github_user_name',
  51. # :bitbucket_user => 'bitbucket_user_name',
  52. # :backup_private => true, # back up private repositories
  53. # :is_private => false, # make backup repositories public
  54. # :github_password => 'github_password',
  55. # :bitbucket_password => 'bitbucket_password',
  56. # :repo_prefix => '' # don't need any prefix
  57. #
  58. # = Examples
  59. #
  60. # # Back up organization repositories
  61. # BackupOnTheGo.backup :github_user => 'github_user_name',
  62. # :github_repos_owner => 'organization_name',
  63. # :bitbucket_user => 'bitbucket_user_name',
  64. # :bitbucket_repos_owner => 'bitbucket_team_name',
  65. # :bitbucket_password => 'bitbucket_password',
  66. # :repo_prefix => 'our-backup'
  67. #
  68. def self.backup(configs = {})
  69. config = DEFAULT_CONFIG.merge(configs)
  70. # either :user or :github_user and :bitbucket_user have to be set
  71. if config.has_key?(:user)
  72. config[:github_user] = config[:user] unless config.has_key?(:github_user)
  73. config[:bitbucket_user] = config[:user] unless config.has_key?(:bitbucket_user)
  74. end
  75. unless config.has_key?(:github_user) and config.has_key?(:bitbucket_user)
  76. raise 'No user name provided.'
  77. end
  78. unless config.has_key?(:github_repos_owner) # Owner of the github repos. Could be an organization
  79. config[:github_repos_owner] = config[:github_user]
  80. end
  81. unless config.has_key?(:bitbucket_repos_owner) # Owner of backup repositories. Could be a team.
  82. config[:bitbucket_repos_owner] = config[:bitbucket_user]
  83. end
  84. # Ask for the passwords if they are not specified
  85. if config[:backup_private] and !config.has_key?(:github_password)
  86. config[:github_password] = ask("Enter your GitHub password for #{config[:github_user]}: ") { |q| q.echo = false }
  87. end
  88. unless config.has_key?(:bitbucket_password)
  89. config[:bitbucket_password] = ask("Enter your BitBucket password for #{config[:bitbucket_user]}: ") { |q| q.echo = false }
  90. end
  91. # print an empty line
  92. puts
  93. # log in BitBucket
  94. bb = BitBucket.new :login => config[:bitbucket_user], :password => config[:bitbucket_password]
  95. backup_repo_names = Array.new
  96. bb.repos.list do |repo|
  97. if repo.owner == config[:bitbucket_repos_owner]
  98. backup_repo_names.push(repo.slug)
  99. end
  100. end
  101. # handling each GitHub repo, used below
  102. repo_each_proc = Proc.new do |repo|
  103. next if repo.fork && !config[:backup_fork]
  104. is_private = config[:is_private] || repo.private?
  105. puts "Backing up #{repo.name}..." if config[:verbose]
  106. backup_repo_name = "#{config[:repo_prefix]}#{repo.name}"
  107. # Create backup repositories if we don't have them yet
  108. unless backup_repo_names.include?(backup_repo_name.downcase)
  109. puts "Creating new repository #{config[:bitbucket_repos_owner]}/#{backup_repo_name}..." if config[:verbose]
  110. begin
  111. bb.repos.create :name => backup_repo_name, :owner => config[:bitbucket_repos_owner],
  112. :scm => 'git', :is_private => is_private,
  113. :no_public_forks => config[:no_public_forks]
  114. rescue
  115. puts_warning "Creation of repository #{config[:bitbucket_repos_owner]}/#{backup_repo_name} failed."
  116. end
  117. end
  118. puts "Backing up resources..." if config[:verbose]
  119. begin
  120. bb.repos.edit config[:bitbucket_repos_owner], backup_repo_name,
  121. :website => repo.homepage,
  122. :description => repo.description,
  123. :is_private => is_private,
  124. :no_public_forks => is_private && config[:no_public_forks]
  125. rescue Exception => e
  126. puts_warning "Failed to update information for #{config[:bitbucket_repos_owner]}/#{backup_repo_name}"
  127. puts e.message
  128. end
  129. Dir.mktmpdir do |dir|
  130. # clone git url
  131. clone_url = repo.clone_url
  132. clone_url.sub!(/https:\/\//,
  133. "https://#{config[:github_user]}:#{config[:github_password]}@") if config[:backup_private]
  134. cmd = "#{config[:git_cmd]} clone --mirror '#{clone_url}' #{dir}/tmp-repo"
  135. puts "Executing [#{config[:git_cmd]} clone --mirror 'https://#{config[:bitbucket_user]}:your_password@github.com/#{config[:github_repos_owner]}/#{repo.name}.git' #{dir}/tmp-repo]" if config[:verbose]
  136. unless system(cmd)
  137. puts_warning "'git clone' failed for #{clone_url}\n"
  138. break
  139. end
  140. # Add bitbucket remote
  141. cmd = "cd #{dir}/tmp-repo && " +
  142. "#{config[:git_cmd]} remote add bitbucket 'https://#{config[:bitbucket_user]}:#{config[:bitbucket_password]}@bitbucket.org/#{config[:bitbucket_repos_owner]}/#{backup_repo_name}.git'"
  143. puts "Executing [#{config[:git_cmd]} remote add bitbucket 'https://#{config[:bitbucket_user]}:your_password@bitbucket.org/#{config[:bitbucket_repos_owner]}/#{backup_repo_name}.git']" if config[:verbose]
  144. `#{cmd}`
  145. unless $?.exitstatus
  146. puts_warning "'git remote add bitbucket ...' failed for #{config[:bitbucket_repos_owner]}/#{backup_repo_name}\n"
  147. break
  148. end
  149. # obtain the main branch (usually master, just in case)
  150. cmd = "cd #{dir}/tmp-repo && #{config[:git_cmd]} branch"
  151. puts "Executing #{cmd}" if config[:verbose]
  152. branches = `#{cmd}`
  153. unless $?.exitstatus
  154. puts_warning "''#{config[:git_cmd]} branch' failed for #{config[:github_repos_owner]}/#{repo.name}"
  155. break
  156. end
  157. main_branch = nil
  158. branches.each_line do |line|
  159. # This is the main branch we need
  160. if line.length >= 1 and line[0] == '*'
  161. main_branch = line[1..-1].strip
  162. end
  163. end
  164. cmd = "cd #{dir}/tmp-repo && "
  165. if main_branch != nil # push bitbucket #{main_branch} first before push --mirror
  166. cmd += "#{config[:git_cmd]} push bitbucket #{main_branch} && "
  167. end
  168. cmd += "#{config[:git_cmd]} push --mirror bitbucket"
  169. puts "Executing #{cmd}" if config[:verbose]
  170. `#{cmd}`
  171. unless $?.exitstatus
  172. puts_warning "'#{config[:git_cmd]} push' failed for #{config[:bitbucket_repos_owner]}/#{backup_repo_name}\n"
  173. break
  174. end
  175. puts
  176. end
  177. end
  178. # obtain github repos
  179. if config[:backup_private]
  180. gh = Github.new :login => config[:github_user], :password => config[:github_password]
  181. else
  182. gh = Github.new
  183. end
  184. # private repos
  185. if config[:backup_private]
  186. puts "Backing up private repositories...\n".green
  187. gh_repos = gh.repos.list :per_page => config[:github_repos_max]
  188. gh_repos.each do |repo|
  189. # only back up private repositories with the owner specified
  190. if repo.owner.login == config[:github_repos_owner] and repo.private?
  191. repo_each_proc.call(repo)
  192. end
  193. end
  194. end
  195. # public repos
  196. puts "Backing up public repositories...\n".green
  197. gh_repos = gh.repos.list :user => config[:github_repos_owner],
  198. :per_page => config[:github_repos_max]
  199. gh_repos.each do |repo|
  200. repo_each_proc.call(repo)
  201. end
  202. end
  203. private
  204. def self.puts_warning(str)
  205. puts "[Warning]: #{str}".red
  206. end
  207. end