grsync 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []