gitcopier 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 27eb1556d77e6fbeb8dfe8fe61e3a3c325f7a5e3
4
+ data.tar.gz: ad00ef820966f47b9b354ea2888ad128b7796c94
5
+ SHA512:
6
+ metadata.gz: a56a7d322cc9d5833d5b8f65ef2c211c173a0760fbb7457ba390619ff03e6dd98dd5d337d9b3ae0fea9082391c42f96f5d618ce3b847dd624564079fc4cc9f1a
7
+ data.tar.gz: e40649d95ef607704cc08e9cd5a07e9d42b1b53c1f3007cc048350c44f2929681891e6fb8ce7fb34b2e163d1801884bfcdd003ed95c056afc479688c33da3b7f
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Victor Tran
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Gitcopier
2
+ Sometime, you work for a Rails project but its front end is adopted from other repositories (such as separated repository from a front end developer who is not familiar with Rails) and you need to integrate front end changes to the project. You need to see what files were changed, copy them accordingly. This gem will help you do the job really fast.
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'gitcopier'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install gitcopier
19
+
20
+ ## Usage
21
+
22
+ TODO: Write usage instructions here
23
+
24
+ ## Contributing
25
+
26
+ 1. Fork it ( https://github.com/tranvictor/gitcopier/fork )
27
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
28
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
29
+ 4. Push to the branch (`git push origin my-new-feature`)
30
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/gitcopier ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'gitcopier'
5
+
6
+ options = {}
7
+
8
+ begin
9
+ OptionParser.new do |opts|
10
+ opts.banner = "Usage: gitcopier [options]"
11
+ opts.on('--from [path]', 'Absolute path to your local repository that you want to copy from. This option must go with --to.') do |path|
12
+ options[:command] = "integrate"
13
+ options[:from] = path
14
+ end
15
+ opts.on('--to [path]', 'Absolute path to your local repository that you want to copy to. This option must go with --from.') do |path|
16
+ options[:command] = "integrate"
17
+ options[:to] = path
18
+ end
19
+ opts.on('-v', '--version') do
20
+ puts "Gitcopier v#{Gitcopier::VERSION}"; exit(0)
21
+ end
22
+ opts.on('--showall', 'Show all integration information. This option ignore any other options.') do
23
+ options[:command] = "showall"
24
+ end
25
+ end.parse!
26
+ rescue OptionParser::InvalidOption
27
+ puts "Invalid Option. Exit nicely."
28
+ exit(0)
29
+ end
30
+
31
+ Gitcopier::CommandLine.execute(options)
@@ -0,0 +1,119 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+ require 'colorize'
4
+
5
+ module Gitcopier
6
+ class CommandLine
7
+ def self.showall(options)
8
+ integrations = load_integration
9
+ puts "You have #{integrations.size} integration.".colorize(:yellow)
10
+ integrations.each do |int|
11
+ source = int['source'].colorize(:green)
12
+ des = int['des'].colorize(:green)
13
+ puts "Integration from #{source} to #{des}"
14
+ end
15
+ end
16
+
17
+ def self.integrate(options)
18
+ integrations = load_integration
19
+ return unless validated_options = validate(options)
20
+ if integration_exist?(integrations, validated_options)
21
+ puts "You already have this integration.".colorize(:yellow)
22
+ return
23
+ end
24
+ integrations << {
25
+ source: validated_options[:from],
26
+ des: validated_options[:to]
27
+ }
28
+ generate_git_callback(
29
+ validated_options[:from],
30
+ validated_options[:to])
31
+ ensure
32
+ save_integrations(integrations)
33
+ end
34
+
35
+ def self.integration_exist?(integrations, options)
36
+ integrations.find_index do |integration|
37
+ (integration['source'] == options[:from] &&
38
+ integration['des'] == options[:to])
39
+ end != nil
40
+ end
41
+
42
+ def self.generate_git_callback(from, to)
43
+ des = File.join from, ".git", "hooks", "post-merge"
44
+ puts "Generating script to #{des}.".colorize(:green)
45
+ script = <<-SCRIPT
46
+ #!/usr/bin/env ruby
47
+ #
48
+ # This script will ask to copy changed file after the merge
49
+ #
50
+ # User's decisions on coping the files will be recorded to not to ask next time
51
+ # User can reconfigure the decision later by modifing the record
52
+ # User can apply a decision on the whole directory
53
+ #
54
+
55
+ require 'gitcopier'
56
+
57
+ integrator = Gitcopier::Integrator.new(
58
+ "#{to}", "#{from}")
59
+ integrator.integrate_all_changes
60
+ SCRIPT
61
+ File.write(des, script)
62
+ FileUtils.chmod "+x", des
63
+ puts "Done."
64
+ end
65
+
66
+ # --from and --to options must be both specified
67
+ # --from must be an existing directory and is under git control
68
+ # --to must be an exising directory
69
+ # remove trailing / from --from and --to
70
+ def self.validate(opt)
71
+ options = opt.clone
72
+ if options[:from].nil? || options[:to].nil?
73
+ puts "--from and --to options must be both specified".colorize(:red)
74
+ return nil
75
+ end
76
+ if !under_git_control?(options[:from])
77
+ puts "--from option must be under git control".colorize(:red)
78
+ return nil
79
+ end
80
+ if !exist?(options[:to])
81
+ puts "--to option must be an existing directory".colorize(:red)
82
+ end
83
+ options[:from] = options[:from][0..-2] if options[:from][-1] == "/"
84
+ options[:to] = options[:to][0..-2] if options[:to][-1] == "/"
85
+ return options
86
+ end
87
+
88
+ def self.exist?(path)
89
+ File.directory? path
90
+ end
91
+
92
+ def self.under_git_control?(path)
93
+ return false unless self.exist?(path)
94
+ pwd = Dir.pwd
95
+ Dir.chdir(path)
96
+ git = IO.popen(['git', 'rev-parse', '--is-inside-work-tree']).read.chomp
97
+ Dir.chdir(pwd)
98
+ return git == "true"
99
+ end
100
+
101
+ def self.load_integration
102
+ file_path = File.join(Dir.home, Gitcopier::INTEGRATION_FILE_NAME)
103
+ JSON.parse(File.read(file_path)) rescue []
104
+ end
105
+
106
+ def self.save_integrations(integrations)
107
+ file_path = File.join(Dir.home, Gitcopier::INTEGRATION_FILE_NAME)
108
+ File.write(file_path, JSON.pretty_generate(integrations))
109
+ end
110
+
111
+ def self.execute(options)
112
+ if options[:command].nil?
113
+ puts "No command specified.".colorize(:red)
114
+ exit(0)
115
+ end
116
+ send(options[:command], options)
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,30 @@
1
+ module Gitcopier
2
+ class Decision
3
+ attr_accessor :source, :des
4
+ def initialize(decision, source, des)
5
+ @decision = decision
6
+ @source = source
7
+ @des = des
8
+ end
9
+
10
+ def is_copy?
11
+ @decision == 'y'
12
+ end
13
+
14
+ def is_follow?
15
+ @decision != 'y' && @decision != 'n'
16
+ end
17
+
18
+ def is_not_copy?
19
+ @decision == 'n'
20
+ end
21
+
22
+ def to_json(*a)
23
+ {
24
+ decision: @decision,
25
+ source: @source,
26
+ des: @des
27
+ }.to_json(*a)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,62 @@
1
+ {
2
+ "/": {
3
+ "decision": "",
4
+ "source": "/",
5
+ "des": ""
6
+ },
7
+ "/clicklion/": {
8
+ "decision": "",
9
+ "source": "/clicklion/",
10
+ "des": ""
11
+ },
12
+ "/clicklion/src/": {
13
+ "decision": "",
14
+ "source": "/clicklion/src/",
15
+ "des": ""
16
+ },
17
+ "/clicklion/src/assets/": {
18
+ "decision": "",
19
+ "source": "/clicklion/src/assets/",
20
+ "des": ""
21
+ },
22
+ "/clicklion/src/assets/css/": {
23
+ "decision": "n",
24
+ "source": "/clicklion/src/assets/css/",
25
+ "des": ""
26
+ },
27
+ "/clicklion/src/assets/data/": {
28
+ "decision": "n",
29
+ "source": "/clicklion/src/assets/data/",
30
+ "des": ""
31
+ },
32
+ "/clicklion/src/assets/js/": {
33
+ "decision": "",
34
+ "source": "/clicklion/src/assets/js/",
35
+ "des": ""
36
+ },
37
+ "/clicklion/src/assets/js/dashboard/": {
38
+ "decision": "y",
39
+ "source": "/clicklion/src/assets/js/dashboard/",
40
+ "des": "/app/assets/javascripts/dashboards/"
41
+ },
42
+ "/clicklion/src/assets/js/libs/": {
43
+ "decision": "y",
44
+ "source": "/clicklion/src/assets/js/libs/",
45
+ "des": "/app/assets/javascripts/libs/"
46
+ },
47
+ "/clicklion/src/dashboard/": {
48
+ "decision": "n",
49
+ "source": "/clicklion/src/dashboard/",
50
+ "des": ""
51
+ },
52
+ "/clicklion/src/img/": {
53
+ "decision": "n",
54
+ "source": "/clicklion/src/img/",
55
+ "des": ""
56
+ },
57
+ "/clicklion/src/pages/": {
58
+ "decision": "n",
59
+ "source": "/clicklion/src/pages/",
60
+ "des": ""
61
+ }
62
+ }
@@ -0,0 +1,76 @@
1
+ require 'json'
2
+ require 'readline'
3
+ require 'gitcopier/decision'
4
+
5
+ module Gitcopier
6
+ class Decisions
7
+ DECISION_FILE = File.dirname(__FILE__) + '/' + 'decisions/decisions.json'
8
+
9
+ def initialize(source_root, des_root)
10
+ @source_root = source_root
11
+ @des_root = des_root
12
+ end
13
+
14
+ def load
15
+ @data = JSON.parse(get_data_from_file)
16
+ @decisions = Hash[@data.map do |pair|
17
+ source = pair[0]
18
+
19
+ decision = Decision.new(
20
+ pair[1]['decision'], pair[1]['source'], pair[1]['des'])
21
+
22
+ [source, decision]
23
+ end]
24
+ end
25
+
26
+ def save
27
+ File.write(DECISION_FILE, JSON.pretty_generate(@decisions))
28
+ end
29
+
30
+ def get(changed_file)
31
+ @decisions[changed_file]
32
+ end
33
+
34
+ def get_data_from_file
35
+ File.read(DECISION_FILE)
36
+ end
37
+
38
+ def prompt_decision(changed_file)
39
+ while true do
40
+ print "Where do you want to copy #{(@source_root + changed_file).colorize(:green)}? (type h for instruction): "
41
+ user_input = Readline.readline("", true).strip
42
+ if user_input == "h"
43
+ puts " #{'h'.colorize(:green)}: For instructions"
44
+ puts " #{'n'.colorize(:green)}: For not copy the file or directory recursively"
45
+ puts "#{'<enter>'.colorize(:green)}: If it's a file, it will ignore the file. If it's a directory, it will prompt you to decide on files/directories inside recursively"
46
+ puts " #{'<path>'.colorize(:green)}: It will copy the file or directory's content to the path."
47
+ elsif user_input == "n"
48
+ decision = Decision.new("n", changed_file, "")
49
+ @decisions[changed_file] = decision
50
+ return decision
51
+ elsif user_input == ""
52
+ decision = Decision.new("", changed_file, "")
53
+ @decisions[changed_file] = decision
54
+ return decision
55
+ else
56
+ unless is_valid?(user_input)
57
+ next
58
+ end
59
+ destination = user_input[@des_root.size..user_input.size]
60
+ decision = Decision.new("y", changed_file, destination)
61
+ @decisions[changed_file] = decision
62
+ return decision
63
+ end
64
+ end
65
+ end
66
+
67
+ def is_valid?(des_string)
68
+ if !des_string.start_with? @des_root
69
+ puts "Your path must be inside #{@des_root}".colorize(:red)
70
+ # TODO: support user to reconfigure destination root
71
+ return false
72
+ end
73
+ true
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,66 @@
1
+ module Gitcopier
2
+ class DirTree
3
+ def initialize(root_path, files)
4
+ @root = DirTreeNode.new "/"
5
+ files.each do |line|
6
+ _, file = line.split("\t")
7
+ add_file(file)
8
+ end
9
+ end
10
+
11
+ def travel(&block)
12
+ raise ArgumentError if !block_given?
13
+ visit(@root, block)
14
+ end
15
+
16
+ def visit(node, block)
17
+ instruction = block.call(node.path)
18
+ if instruction.nil?
19
+ node.children.each do |name, child|
20
+ visit(child, block)
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+ def add_file(file)
27
+ is_dir = file.end_with? '/'
28
+ parts = file.split('/')
29
+ current_node = @root
30
+ part_size = parts.size
31
+ parts.each_with_index do |part, index|
32
+ child = current_node.get_child(part)
33
+ path = current_node.path + part
34
+ # unless the part is the last part and the file is not a dir
35
+ unless index + 1 == part_size && !is_dir
36
+ path = path + '/'
37
+ end
38
+ unless child
39
+ child = current_node.add_child(part, path)
40
+ end
41
+ current_node = child
42
+ end
43
+ end
44
+ end
45
+
46
+ class DirTreeNode
47
+ attr_accessor :path, :type, :children
48
+
49
+ def initialize(path)
50
+ @path = path
51
+ @type = path.end_with?('/') ? 'dir' : 'file'
52
+ @children = {}
53
+ end
54
+
55
+ def get_child(name)
56
+ @children[name]
57
+ end
58
+
59
+ # add or replace a child with given name and path
60
+ def add_child(name, path)
61
+ child = DirTreeNode.new(path)
62
+ @children[name] = child
63
+ return child
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Gitcopier
2
+ VERSION = "0.1.6"
3
+ end
data/lib/gitcopier.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'colorize'
2
+ require "gitcopier/version"
3
+ require "gitcopier/decision"
4
+ require "gitcopier/decisions"
5
+ require "gitcopier/dir_tree"
6
+ require "gitcopier/command_line"
7
+
8
+ module Gitcopier
9
+
10
+ INTEGRATION_FILE_NAME = '.gitcopier'
11
+
12
+ class Integrator
13
+ def initialize(integrate_path, root_path)
14
+ @root_path = root_path
15
+ @integrate_path = integrate_path
16
+ @decisions = Decisions.new(@root_path, @integrate_path)
17
+ @decisions.load
18
+ end
19
+
20
+ # This method get relative path of all changed files to git root
21
+ def changed_files
22
+ commit_log.split("\n").select do |line|
23
+ line.match(/[ACDMRTUXB*]+\t.+/)
24
+ end.map(&:strip)
25
+ end
26
+
27
+ def commit_log
28
+ @commit_log ||= raw_git_commit_log
29
+ end
30
+
31
+ def copy(source, des)
32
+ # source is a directory, copy its content
33
+ if source.end_with? '/'
34
+ puts "Copy #{(source + '*').colorize(:green)} to #{des.colorize(:green)}."
35
+ FileUtils.cp_r(Dir["#{source}*"], des)
36
+ else # source is a file, copy it to destination
37
+ puts "Copy #{source.colorize(:green)} to #{des.colorize(:green)}."
38
+ FileUtils.cp_r(source, des)
39
+ end
40
+ end
41
+
42
+ def print_integration_info
43
+ puts "====================================================="
44
+ puts " Integrating changes to #{@integrate_path}".colorize(:yellow)
45
+ puts "====================================================="
46
+ commit, author, date = commit_log.split("\n")[0..2]
47
+ _, h = commit.split(' ')
48
+ print "Commit: ", h.colorize(:yellow)
49
+ _, a = author.split(': ')
50
+ print "\nAuthor: ", a.colorize(:yellow)
51
+ _, d = date.split(': ')
52
+ print "\nDate: ", d.colorize(:yellow), "\n"
53
+ puts "====================================================="
54
+ end
55
+
56
+ def integrate_all_changes
57
+ print_integration_info
58
+ dir_tree = DirTree.new(@root_path, changed_files)
59
+ dir_tree.travel do |changed_file|
60
+ decision = @decisions.get(changed_file)
61
+ just_decided = decision.nil?
62
+ unless decision
63
+ decision = @decisions.prompt_decision(changed_file) unless decision
64
+ end
65
+ if decision.is_copy?
66
+ print "Follow previous decision: " unless just_decided
67
+ copy(@root_path + decision.source, @integrate_path + decision.des)
68
+ next true
69
+ elsif decision.is_follow?
70
+ next nil
71
+ else
72
+ next false
73
+ end
74
+ end
75
+ ensure
76
+ print "\nSaving decisions for later usage...".colorize(:green)
77
+ @decisions.save
78
+ puts "Done.".colorize(:green)
79
+ end
80
+
81
+ private
82
+ def raw_git_commit_log
83
+ IO.popen(['git', 'log', '--name-status', '-1']).read
84
+ end
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,103 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gitcopier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - Victor Tran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorize
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.7'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.7'
55
+ description: Sometime, you work for a Rails project but its front end is adopted from
56
+ other repositories (such as separated repository from a front end developer who
57
+ is not familiar with Rails) and you need to integrate front end changes to the project.
58
+ You need to see what files were changed, copy them accordingly. This gem will help
59
+ you do the job really fast.
60
+ email:
61
+ - vu.tran54@gmail.com
62
+ executables:
63
+ - gitcopier
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/gitcopier
71
+ - lib/gitcopier.rb
72
+ - lib/gitcopier/command_line.rb
73
+ - lib/gitcopier/decision.rb
74
+ - lib/gitcopier/decisions.rb
75
+ - lib/gitcopier/decisions/decisions.json
76
+ - lib/gitcopier/dir_tree.rb
77
+ - lib/gitcopier/version.rb
78
+ homepage: https://github.com/tranvictor/integrator
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.2.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: Help developers to integrate between multiple repositories
102
+ test_files: []
103
+ has_rdoc: