├── .gitignore ├── .travis.yml ├── Gemfile ├── Rakefile ├── bin └── git-reclone ├── git-reclone.gemspec ├── lib ├── git-reclone-version.rb └── git-reclone.rb ├── readme.md └── spec ├── git-reclone_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.lock 2 | *.swp 3 | *.swo 4 | *.gem 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 2.4.0 3 | install: 4 | - bundle install --jobs=3 --retry=3 5 | - rake build # install gem locally 6 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task :default => :spec 2 | 3 | # Load git-reclone files straight into ruby path 4 | lib = File.expand_path("../lib/", __FILE__) 5 | $:.unshift lib unless $:.include?(lib) 6 | 7 | # gem name, version 8 | g = "git-reclone" 9 | require "git-reclone" 10 | v = GitReclone::Version 11 | 12 | 13 | # Testing 14 | # 15 | require "rspec/core/rake_task" 16 | RSpec::Core::RakeTask.new(:spec) do |rake| 17 | rake.rspec_opts = "--color --format documentation" 18 | rake.verbose = true 19 | end 20 | 21 | task :dev do 22 | sh 'filewatcher "**/*.rb" "clear && rake"' 23 | end 24 | 25 | 26 | # Gem management 27 | # 28 | task :build do 29 | sh "gem build #{g}.gemspec" 30 | sh "gem install ./#{g}-#{v}.gem" 31 | end 32 | 33 | task :clean do 34 | sh "rm -fv *.gem" 35 | end 36 | 37 | task :push => [:clean, :build] do 38 | sh "gem push #{g}-#{v}.gem" 39 | end 40 | 41 | -------------------------------------------------------------------------------- /bin/git-reclone: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "git-reclone" 4 | 5 | # git-reclone by jeremy warner 6 | # easily reset git repo from remotes 7 | 8 | GitReclone.new.fire ARGV 9 | -------------------------------------------------------------------------------- /git-reclone.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("../lib/", __FILE__) 2 | $:.unshift lib unless $:.include?(lib) 3 | 4 | require "git-reclone-version" 5 | 6 | Gem::Specification.new do |g| 7 | g.author = "Jeremy Warner" 8 | g.email = "jeremy.warner@berkeley.edu" 9 | 10 | g.name = "git-reclone" 11 | g.version = GitReclone::Version 12 | g.platform = Gem::Platform::RUBY 13 | g.date = Time.now.strftime("%Y-%m-%d") 14 | g.summary = "reclone a local git repo from the remote." 15 | g.description = "reclone a local git repo from the remote." 16 | g.homepage = "http://github.com/jeremywrnr/git-reclone" 17 | g.license = "MIT" 18 | 19 | g.add_dependency "colored", ">= 1.2", "~> 1.2" 20 | g.add_development_dependency "ronn" 21 | g.add_development_dependency "rake" 22 | g.add_development_dependency "rspec" 23 | 24 | g.files = Dir.glob("{bin,lib}/**/*") + %w(readme.md) 25 | g.executables = Dir.glob("bin/*").map(&File.method(:basename)) 26 | g.require_path = 'lib' 27 | end 28 | -------------------------------------------------------------------------------- /lib/git-reclone-version.rb: -------------------------------------------------------------------------------- 1 | # universal version tracking 2 | 3 | class GitReclone 4 | Version = "0.2.3" 5 | end 6 | -------------------------------------------------------------------------------- /lib/git-reclone.rb: -------------------------------------------------------------------------------- 1 | # git-reclone gem 2 | # jeremy warner 3 | 4 | =begin 5 | todo: add an option to automatically add a backup of the local copy 6 | todo: add all remotes other than the origins, maintain connections 7 | todo: -b / --backup, and this actually should be the default (maybe) 8 | =end 9 | 10 | require "colored" 11 | require "fileutils" 12 | require "git-reclone-version" 13 | 14 | class GitReclone 15 | def initialize(test=false) 16 | @pdelay = 0.01 # constant for arrow speed 17 | @testing = test 18 | @verify = !test 19 | end 20 | 21 | def fire(args = []) 22 | opts = args.select {|a| a[0] == "-" } 23 | opts.each {|o| parse_opt o } 24 | exit 0 if (@testing || opts.first) 25 | parse_arg((args - opts).first) 26 | end 27 | 28 | def pexit(msg) 29 | puts msg 30 | exit 1 31 | end 32 | 33 | def parse_opt(o) 34 | case o 35 | when "--force", "-f" 36 | @verify = false 37 | when "--help", "-h" 38 | puts GitReclone::Help 39 | when "--version", "-v" 40 | puts GitReclone::Version 41 | end 42 | end 43 | 44 | def parse_arg(a) 45 | a.nil?? verify(remote) : verify(remote(a)) 46 | end 47 | 48 | def no_repo? 49 | `git status 2>&1`.split("\n").first == 50 | "fatal: Not a git repository (or any of the parent directories): .git" 51 | end 52 | 53 | def git_root 54 | %x{git rev-parse --show-toplevel} 55 | end 56 | 57 | def remotes 58 | %x{git remote -v}.split("\n").map { |r| r.split[1] }.uniq 59 | end 60 | 61 | def reclonebanner 62 | 25.times { |x| slowp "\rpreparing| ".red << "~" * x << "#==>".red } 63 | 25.times { |x| slowp "\rpreparing| ".red << " " * x << "~" * (25 - x) << "#==>".yellow } 64 | printf "\rREADY.".red << " " * 50 << "\n" 65 | end 66 | 67 | def slowp(x) 68 | sleep @pdelay 69 | printf x 70 | end 71 | 72 | # trying to parse out which remote should be the new source 73 | def remote(search = /.*/) 74 | pexit "Not currently in a git repository.".yellow if no_repo? 75 | 76 | r = remotes.find { |gr| gr.match search } 77 | 78 | pexit "No remotes found in this repository.".yellow if remotes.nil? 79 | 80 | if r.nil? 81 | errmsg = "No remotes found that match #{search.to_s.red}. All remotes:\n" + remotes.join("\n") 82 | pexit errmsg 83 | return errmsg 84 | else 85 | return r 86 | end 87 | end 88 | 89 | # show remote to user and confirm location (unless using -f) 90 | def verify(r) 91 | reclonebanner 92 | puts "Remote source:\t".red << r 93 | puts "Local target:\t".red << git_root 94 | 95 | if @verify 96 | puts "Warning: this will completely overwrite the local copy.".yellow 97 | printf "Continue recloning local repo? [yN] ".yellow 98 | unless $stdin.gets.chomp.downcase[0] == "y" 99 | puts "Reclone aborted.".green 100 | return 101 | end 102 | end 103 | 104 | reclone remote, git_root.chomp unless @testing 105 | end 106 | 107 | # overwrite the local copy of the repository with the remote one 108 | def reclone(remote, root) 109 | # remove the git repo from this computer 110 | if !@testing 111 | tree = Dir.glob("*", File::FNM_DOTMATCH).select {|d| not ['.','..'].include? d } 112 | FileUtils.rmtree (tree) 113 | end 114 | 115 | cloner = "git clone \"#{remote}\" \"#{root}\"" 116 | 117 | puts "Recloned successfully.".green if system(cloner) 118 | end 119 | end 120 | 121 | GitReclone::Help = <<-HELP 122 | #{'git reclone'.red}: a git repo restoring tool 123 | 124 | reclones from the remote listed first, overwriting your local copy. 125 | to restore from a particular remote repository, specify the host: 126 | 127 | git reclone bitbucket # reclone using bitbucket 128 | git reclone github # reclone using github 129 | HELP 130 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # git-reclone :rocket: 2 | 3 | [![Gem Version](https://badge.fury.io/rb/git-reclone.svg)](https://badge.fury.io/rb/git-reclone) 4 | [![Build Status](https://app.travis-ci.com/jeremywrnr/git-reclone.svg)](https://app.travis-ci.com/github/jeremywrnr/git-reclone) 5 | [![MIT](https://img.shields.io/npm/l/alt.svg?style=flat)](http://jeremywrnr.com/mit-license) 6 | 7 | destroy your local copy of a git repo, and reclone it from your remote. 8 | 9 | ![Screencast](http://i.imgur.com/HIvZCJB.gif) 10 | 11 | tested and works well for: 12 | 13 | - github 14 | - bitbucket 15 | 16 | ## setup 17 | 18 | [sudo] gem install git-reclone 19 | 20 | This will enable the `git reclone` command automatically! 21 | 22 | 23 | ## usage 24 | 25 | git reclone 26 | 27 | reclones from the first git remote. to clone a specific remote, specify some 28 | part (or all) of the host name. for example: 29 | 30 | git reclone bit 31 | git reclone bucket 32 | git reclone bitbucket 33 | 34 | will all overwrite the current repository with bitbucket's remote (assuming 35 | that some other host/repo name doesn't also match 'bitbucket'). 36 | 37 | 38 | ## about 39 | 40 | sometimes i mess up git histories, with (merges or rebasing, etc), and it 41 | becomes more of a headache to figure out how to undo what i did than to just 42 | reclone the remote copy and apply the changes i want in the right way. i was 43 | doing this often enough that i figured it would be nice to have a tool that 44 | just did this automatically. besides, it can be satisfying to just reclone your 45 | local copy and start anew - after all, what are backups meant for? 46 | 47 | ## testing 48 | 49 | bundle || gem install bundler && bundle 50 | rake # running git-reclone's tests 51 | 52 | -------------------------------------------------------------------------------- /spec/git-reclone_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | # git-reclone unit testing 4 | 5 | describe GitReclone do 6 | before :each do 7 | @gn = GitReclone.new(true) # testing 8 | end 9 | 10 | it "should exit without args" do 11 | expect(@gn.fire).to eq nil 12 | end 13 | 14 | it "should show GitReclone help" do 15 | expect(@gn.parse_opt("--help")).to eq GitReclone::Help 16 | expect(@gn.parse_opt("-h")).to eq GitReclone::Help 17 | end 18 | 19 | it "should show GitReclone version" do 20 | expect(@gn.parse_opt("--version")).to eq GitReclone::Version 21 | expect(@gn.parse_opt("-v")).to eq GitReclone::Version 22 | end 23 | 24 | it "should find the correct remote" do 25 | expect(@gn.remote %{bitbucket}).to eq 'git@bitbucket.org:user/repo.git' 26 | expect(@gn.remote %{github}).to eq 'https://github.com/user/repo.git' 27 | expect(@gn.remote %{heroku}).to eq 'https://git.heroku.com/app.git' 28 | end 29 | 30 | it "should show all remotes after finding no match" do 31 | no_remote_err = "No remotes found that match \e[31mfake\e[0m. All remotes:\n" + @gn.remotes.join("\n") 32 | expect(@gn.remote 'fake').to eq no_remote_err 33 | end 34 | 35 | it "should handle pathnames with spaces" do 36 | remote = "https://github.com/jeremywrnr/git-reclone.git" 37 | gn_test_dir = "../test dir " + Time.now.to_s 38 | begin 39 | expect(@gn.reclone remote, gn_test_dir).to eq "\e[32mRecloned successfully.\e[0m" 40 | ensure 41 | FileUtils.rm_rf(gn_test_dir) 42 | end 43 | end 44 | end 45 | 46 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "git-reclone" 2 | require 'fileutils' 3 | 4 | # mock remotes/puts 5 | 6 | class GitReclone 7 | def exit(x) end 8 | def slowp(*x) end 9 | def printf(*x) end 10 | def puts(*x) 11 | return x.first 12 | end 13 | 14 | def remotes 15 | %w{ 16 | https://github.com/user/repo.git 17 | https://git.heroku.com/app.git 18 | git@bitbucket.org:user/repo.git 19 | } 20 | end 21 | end 22 | 23 | --------------------------------------------------------------------------------