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 +7 -0
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/bin/grsync +79 -0
- data/grsync.gemspec +25 -0
- data/lib/grsync.rb +108 -0
- data/lib/grsync/logger.rb +8 -0
- data/lib/grsync/version.rb +3 -0
- metadata +108 -0
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
data/Gemfile
ADDED
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
|
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: []
|