grsync 0.0.1

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: 24eaeaafd867331a50e60e3fbdf750b603454b17
4
+ data.tar.gz: 0ee53dd899aae8732643d278e38a0277418a8a8f
5
+ SHA512:
6
+ metadata.gz: 2dfbd96e80746a42b98c80c066701844d0b4faccc7e1266850f1a13c48e113086488bf7382b52ce903be6522de245630551d440fa87810f6de97005afbee62fc
7
+ data.tar.gz: 58908813abe97fb53f959d5cd7ca082ff8bcedc0b021197b2e4d70e1815c57e6c23cdeecbe58bff060e9a3b4f6fee08a9bbd96869b98878abc3aab6860030239
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .idea/
2
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/bin/grsync ADDED
@@ -0,0 +1,79 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'net/ssh'
5
+ require 'trollop'
6
+
7
+ require 'grsync'
8
+ require 'grsync/logger'
9
+
10
+ # a sample command of grsync is like:
11
+ # ./grsync
12
+ # --local ~/document/myrepo
13
+ # --remote username@localhost:/home/username/myrepo
14
+ # --passwd barabara
15
+ # --port 2222
16
+
17
+ module GRSync
18
+ # execution entrance
19
+ if __FILE__ == $0
20
+ # parse arguments
21
+ opts = Trollop::options do
22
+ opt :local, 'local source git repository(e.g. /path/to/repo)', :type => :string
23
+ opt :remote, 'remote destination git repository(e.g. username@host:/path/to/repo)', :type => :string
24
+ opt :passwd, 'password for ssh login', :type => :string
25
+ opt :port, 'port for ssh login', :default => 22
26
+ opt :save, 'save this synchronization link'
27
+ end
28
+
29
+ local = opts[:local]
30
+ remote_full = opts[:remote] # location with username, host and path
31
+
32
+ # since save feature is unimplemented
33
+ # both local and remote option have to be specified for an explicit synchronization
34
+ Trollop::die :local, 'must be specified' if local.nil?
35
+ Trollop::die :remote, 'must be specified' if remote_full.nil?
36
+
37
+ # local dir must exist
38
+ # always check existence asap
39
+ unless Dir.exist?(File.expand_path(local))
40
+ $Log.error "Local directory #{local} doesn't exist"
41
+ abort
42
+ end
43
+
44
+ # extract remote, user_name and host from dest_full
45
+ login_str, remote = remote_full.split ':'
46
+ if login_str.split('@').size == 2
47
+ user_name, host = login_str.split('@')
48
+ else
49
+ host = login_str
50
+ user_name = nil
51
+ end
52
+
53
+ # optional options for login
54
+ passwd = opts[:passwd] || ''
55
+ port = opts[:port]
56
+
57
+ # ssh login
58
+ begin
59
+ Net::SSH.start(host, user_name, :password => passwd, :port => port) do |ssh|
60
+ syncer = GRSync::Syncer.new ssh, local, remote
61
+ syncer.sync
62
+ end
63
+ rescue Net::SSH::AuthenticationFailed
64
+ login_info = {
65
+ host: host,
66
+ user_name: user_name,
67
+ port: port,
68
+ passwd: passwd
69
+ }
70
+ $Log.error "Authentication failed. Please check whether the following login information is correct: #{login_info}"
71
+ abort
72
+ rescue Exception => ex
73
+ $Log.error "#{ex.class.name} -- #{ex}"
74
+ abort
75
+ end
76
+ end
77
+ end
78
+
79
+
data/grsync.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'grsync/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'grsync'
8
+ spec.version = GRSync::VERSION
9
+ spec.authors = ['luminocean']
10
+ spec.email = ['luminocean@foxmail.com']
11
+
12
+ spec.summary = 'development code synchronization tool'
13
+ spec.files = `git ls-files`.split("\n")
14
+ spec.bindir = 'bin'
15
+ spec.executables = ['grsync']
16
+ spec.require_paths = ['lib']
17
+ spec.license = 'MIT'
18
+ spec.homepage = 'https://github.com/luminocean/grsync'
19
+
20
+ spec.add_dependency 'trollop', '~> 2.1'
21
+ spec.add_dependency 'net-ssh', '~> 3.1'
22
+ spec.add_dependency 'log4r', '~> 1.1'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ end
data/lib/grsync.rb ADDED
@@ -0,0 +1,108 @@
1
+ require 'open3'
2
+ require 'shellwords'
3
+
4
+ module GRSync
5
+ class Syncer
6
+ # * +ssh+ - ssh object used for issuing commands
7
+ # * +local_path+ - source of the sync link which should be a path of a directory on local machine
8
+ # * +remote_path+ - destination of the sync link which should be a path of a directory on remote machine
9
+ def initialize(ssh, local_path, remote_path)
10
+ @ssh = ssh
11
+ @local_path = local_path # existence should be checked before
12
+ @remote_path = remote_path # existence remains to be checked
13
+ end
14
+
15
+ # sync remote git repository with the local one
16
+ def sync
17
+ # preparation
18
+ check_remote_path_valid
19
+ check_git_repo
20
+ reset_remote_repo
21
+
22
+ diff_text = local_diff
23
+ apply_diff_to_remote diff_text
24
+
25
+ # output = @ssh.exec! 'hostname'
26
+ # puts output
27
+ end
28
+
29
+ private
30
+
31
+ # checkout all files from HEAD to flush all changes made in remote repo
32
+ # thus new git apply can be applied safely :)
33
+ def reset_remote_repo
34
+ @ssh.exec! "cd #{@remote_path} && git reset --hard" do |ch, stream, data|
35
+ if stream == :stderr and data.to_s != '' # check for nil or ''
36
+ raise GitResetException, "reset failed: #{data}"
37
+ end
38
+ end
39
+ end
40
+
41
+ def apply_diff_to_remote(diff_text)
42
+ # be careful, string in ruby may not be used safely in shell directly
43
+ # so here's a conversion
44
+ echoable_text = Shellwords.escape diff_text
45
+
46
+ result = @ssh.exec! "cd #{@remote_path} && echo #{echoable_text} | git apply -"
47
+ # diff fails if something other than empty string returns
48
+ raise GitDiffApplyException, "apply failed: #{result}" if result != ''
49
+ end
50
+
51
+ # diff the local git repository and returns the diff text for later use
52
+ def local_diff
53
+ `cd #{@local_path} && git diff HEAD`
54
+ end
55
+
56
+ # check the followings:
57
+ # 1. whether both local and remote directory are git repositories
58
+ # 2. whether their branches are matched
59
+ def check_git_repo
60
+ # check local dir
61
+
62
+ # use Open3 in order to get stderr output from child process
63
+ # `` syntax only returns stdout output which is not enough
64
+
65
+ # since command are all executed in newly spawned child processes so there's no need to record old dir path
66
+ stdout, stderr = Open3.popen3("cd #{@local_path} && git status")[1..2]
67
+
68
+ local_branch_name = stdout.gets.to_s.match(/^On branch ([^\s]+)/)[1]
69
+ unless local_branch_name
70
+ raise GitRepoException, "local path #{@local_path} is not a git repository"
71
+ end
72
+
73
+ # check remote dir
74
+ valid = true
75
+ remote_branch_name = ''
76
+ @ssh.exec!("cd #{@remote_path} && git status") do |ch, stream, data|
77
+ case stream
78
+ when :stdout
79
+ valid = false unless (remote_branch_name = data.match(/^On branch ([^\s]+)/)[1])
80
+ when :stderr
81
+ valid = false unless (data.nil? or data == '')
82
+ end
83
+ end
84
+ unless valid
85
+ raise GitRepoException, "remote path #{@remote_path} is not a git repository"
86
+ end
87
+
88
+ # check match
89
+ if local_branch_name != remote_branch_name
90
+ raise GitRepoException, "local branch(#{local_branch_name}) and remote branch(#{remote_branch_name}) don't match"
91
+ end
92
+ end
93
+
94
+ # check whether the remote path is valid
95
+ def check_remote_path_valid
96
+ @ssh.exec!("ls #{@remote_path}") do |ch, stream, data|
97
+ if stream == :stderr and not data.nil?
98
+ raise RemotePathInvalidException, "remote path #{@remote_path} not found"
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class RemotePathInvalidException < Exception; end
105
+ class GitRepoException < Exception; end
106
+ class GitDiffApplyException < Exception; end
107
+ class GitResetException < Exception; end
108
+ end
@@ -0,0 +1,8 @@
1
+ require 'log4r'
2
+ include Log4r
3
+
4
+ $Log = Logger.new STDOUT
5
+ $Log.level = Logger::DEBUG
6
+ $Log.formatter = proc do |severity, datetime, progname, msg|
7
+ "[#{severity}] #{datetime} #{progname}: #{msg}\n"
8
+ end
@@ -0,0 +1,3 @@
1
+ module GRSync
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grsync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - luminocean
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: trollop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-ssh
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: log4r
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.10'
69
+ description:
70
+ email:
71
+ - luminocean@foxmail.com
72
+ executables:
73
+ - grsync
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - bin/grsync
80
+ - grsync.gemspec
81
+ - lib/grsync.rb
82
+ - lib/grsync/logger.rb
83
+ - lib/grsync/version.rb
84
+ homepage: https://github.com/luminocean/grsync
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.4.5.1
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: development code synchronization tool
108
+ test_files: []