metaverse 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +109 -22
- data/bin/meta +2 -4
- data/lib/metaverse/base.rb +134 -84
- data/lib/metaverse/cli.rb +65 -13
- data/lib/metaverse/errors.rb +21 -0
- data/lib/metaverse/iterator.rb +4 -2
- data/lib/metaverse/repo.rb +212 -6
- data/lib/metaverse/version.rb +1 -1
- data/lib/metaverse.rb +1 -0
- data/metaverse.gemspec +2 -0
- metadata +31 -3
- data/lib/metaverse/git.rb +0 -84
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69efcf0bc6d8443154da8f032a5f86442cf98da7
|
4
|
+
data.tar.gz: c60b16984a090cb2fb1508f902cb55db3994bd0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85da87fd9f3f070243eb859a55bf56f97250853830f3df18c62eb5d98bd36465e05fa07b3b1f7a69ffdd5ac4bf25340d4f4bb8d765283b7d6a2542be77980c69
|
7
|
+
data.tar.gz: 24f9fbcb26df13afea52723773765a8cf3d5df6bd8cf8aaa7a1a154d0262e2f07d3591793b2eb46a20ce93167b3851091ed6a1dfd749753d2d647a82e602b8a8
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,39 +1,126 @@
|
|
1
1
|
# Metaverse
|
2
2
|
|
3
|
-
|
3
|
+
Metaverse multirepo management revolves around two main axes of functionality:
|
4
4
|
|
5
|
-
|
5
|
+
* Workflow related functionality
|
6
|
+
* State Management
|
6
7
|
|
7
|
-
|
8
|
+
# Installation
|
8
9
|
|
9
|
-
|
10
|
+
Metaverse requires `rugged` which in turns relies on a bundled `libgit2` which
|
11
|
+
is a Ruby native extension.
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
13
|
+
### OSX
|
14
|
+
|
15
|
+
You need Cmake and pkg-config. If you have brew you can install by running :
|
16
|
+
|
17
|
+
`brew install cmake pkg-config`
|
18
|
+
|
19
|
+
# 1 - Workflow
|
20
|
+
## Utilities
|
21
|
+
### Checkout
|
22
|
+
`meta checkout REF_OR_SHA`
|
23
|
+
|
24
|
+
Checks out every repo to `REF_OR_SHA` - similar to `git checkout REF_OR_SHA`
|
25
|
+
### Develop
|
26
|
+
`meta develop`
|
27
|
+
|
28
|
+
Checks out every repo to `develop` - ( shortcut for `meta checkout develop` )
|
29
|
+
### Update
|
30
|
+
`meta update REMOTE_NAME`
|
31
|
+
|
32
|
+
Fetches from `REMOTE_NAME` in every repo - similar to `git remote update REMOTE_NAME`
|
33
|
+
If `REMOTE_NAME` is omitted, defaults to fetching from all the remotes
|
34
|
+
|
35
|
+
### Pull
|
36
|
+
`meta pull`
|
37
|
+
|
38
|
+
Pulls the changes from the main remote in every repo - similar to `git pull origin`
|
39
|
+
|
40
|
+
### Branches
|
41
|
+
`meta branches`
|
42
|
+
|
43
|
+
Displays the branch at which every repo is.
|
44
|
+
|
45
|
+
### Exec
|
46
|
+
`meta exec COMMAND`
|
47
|
+
|
48
|
+
Runs the specified shell command in every repo's working directory. Also passes
|
49
|
+
the ENV used to invoke it to the shell command.
|
50
|
+
|
51
|
+
## Features
|
52
|
+
### New
|
53
|
+
`meta feature new FEATURE_NAME`
|
14
54
|
|
15
|
-
|
55
|
+
Creates a feature branch named `feature/FEATURE_NAME` on every repo managed by metaverse.
|
16
56
|
|
17
|
-
|
57
|
+
### Load
|
58
|
+
`meta feature load FEATURE_NAME REMOTE_NAME`
|
18
59
|
|
19
|
-
|
60
|
+
Checks out every repo to `feature/FEATURE_NAME` in a **detached HEAD** mode.
|
61
|
+
If the remote is set, tries to load the feature from that specific remote. Else, defaults to local features.
|
20
62
|
|
21
|
-
|
63
|
+
### Send
|
64
|
+
`meta feature send FEATURE_NAME REMOTE_NAME`
|
22
65
|
|
23
|
-
|
66
|
+
Pushes the feature branch of the repos that have changes to the specified remote.
|
67
|
+
It also automatically delete the branches that have no changes compared to develop.
|
24
68
|
|
25
|
-
|
69
|
+
## Releases and bugfixes
|
26
70
|
|
27
|
-
|
71
|
+
Releases and bugfixes follow the same structure than the features, except that they are executed respectively using `meta release` and `meta bugtfix`.
|
28
72
|
|
29
|
-
|
73
|
+
# 2 - State Management
|
74
|
+
## Sanity checks
|
75
|
+
meta can be used to perform some sanity checks on the state of the system.
|
30
76
|
|
31
|
-
|
77
|
+
### Dirtiness
|
78
|
+
`meta check dirtiness`
|
32
79
|
|
33
|
-
|
80
|
+
Checks if the system is dirty.
|
81
|
+
The system is considered dirty if a repo contains unstaged or uncommitted changes ( think about updating your `.gitignore`s :) )
|
82
|
+
In such a case, it displays the list of affected repos.
|
34
83
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
84
|
+
### Status
|
85
|
+
` meta status`
|
86
|
+
|
87
|
+
Checks the status of the system by running a dirtiness check, displays the output and the current state of the system.
|
88
|
+
The branch at which the system is is deduced by counting the occurrences of every branch and picking the most frequent one.
|
89
|
+
|
90
|
+
## System state
|
91
|
+
meta provides a set of features aimed toward taking a snapshot of the system, and reloading it afterwards.
|
92
|
+
|
93
|
+
### How does it work ?
|
94
|
+
Snapshots are stored in a separate ref namespace in order to avoid collision with branches. Snapshots' refs in particular follow this refspec : `refs/meta/local/snapshot/SNAPSHOT_NAME`
|
95
|
+
Similarly, refs are also used in addition to branches, to load features, releases, and bugfixes.
|
96
|
+
|
97
|
+
### New
|
98
|
+
`meta snapshot new SNAPSHOT_NAME`
|
99
|
+
|
100
|
+
Take a snapshot of the system. The system stays in the same branch where it was before.
|
101
|
+
|
102
|
+
### Load
|
103
|
+
`meta snapshot load SNAPSHOT_NAME`
|
104
|
+
|
105
|
+
Loads a previous snapshot of the system in a git **detached HEAD** mode. A good practice would be to create a feature, a release or a bugfix branch after loading a snapshot, prior to any modification on it.
|
106
|
+
|
107
|
+
### Send
|
108
|
+
`meta snapshot send SNAPSHOT_NAME REMOTE_NAME`
|
109
|
+
|
110
|
+
Pushes a snapshot to the specified remote.
|
111
|
+
|
112
|
+
# Configuration
|
113
|
+
|
114
|
+
meta stores it's configuration in a file named .meta.yml in the root folder of your workspace.
|
115
|
+
|
116
|
+
This is an example of a `.meta.yml` file :
|
117
|
+
|
118
|
+
```
|
119
|
+
remotes:
|
120
|
+
own: YOUR_OWN_REMOTE
|
121
|
+
main: YOUR_MAIN_REMOTE
|
122
|
+
ignore:
|
123
|
+
- A_REPO_TO_IGNORE
|
124
|
+
- A_REPO_TO_IGNORE
|
125
|
+
- ..
|
126
|
+
```
|
data/bin/meta
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
begin
|
4
|
-
require
|
5
|
-
require 'metaverse'
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'metaverse'))
|
6
5
|
rescue LoadError
|
7
|
-
require "bundler/setup"
|
8
6
|
require 'rubygems'
|
9
|
-
require 'metaverse'
|
7
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'metaverse'))
|
10
8
|
end
|
11
9
|
|
12
10
|
Metaverse::Cli.start
|
data/lib/metaverse/base.rb
CHANGED
@@ -1,140 +1,190 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'colorize'
|
3
3
|
require 'yaml'
|
4
|
+
require 'transacted'
|
4
5
|
require 'metaverse/repo'
|
5
6
|
require 'metaverse/iterator'
|
7
|
+
require 'metaverse/errors'
|
6
8
|
|
7
9
|
module Metaverse
|
8
10
|
class Base
|
9
11
|
def initialize path
|
10
12
|
@logger = Logger.new STDOUT
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
|
13
|
+
@base_path = path
|
14
|
+
read_config "#{path}/.meta.yml"
|
15
|
+
@repos_paths = Metaverse::Iterator.new path, @ignored_repos, @repos_paths
|
16
|
+
if @repos_paths.empty?
|
17
|
+
@repos_paths.build
|
18
|
+
save_config "#{path}/.meta.yml"
|
19
|
+
puts "Repos cache built.".green
|
20
|
+
end
|
21
|
+
@repos = @repos_paths.map {|repo| Metaverse::Repo.new repo}
|
15
22
|
end
|
16
23
|
|
17
24
|
def status
|
18
|
-
|
19
|
-
checkDirtiness
|
25
|
+
check_dirtiness
|
20
26
|
end
|
21
27
|
|
22
|
-
def repos
|
23
|
-
@repos
|
24
|
-
end
|
25
28
|
|
26
29
|
def checkout name
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
action_options = -> (repo) {
|
31
|
+
{
|
32
|
+
up: -> {
|
33
|
+
puts "\n # #{repo.name}".blue
|
34
|
+
repo.checkout name
|
35
|
+
},
|
36
|
+
down: -> {
|
37
|
+
puts "\n # Rolling back in #{repo.name}".blue
|
38
|
+
repo.checkout repo.pop_previous_branch
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
if check_dirtiness
|
44
|
+
actions = @repos.map {|repo|
|
45
|
+
puts "\n # #{repo.name}".blue
|
46
|
+
Transacted::Action.new action_options.call(repo)
|
47
|
+
}
|
48
|
+
|
49
|
+
checkout_transaction = Transacted::Transaction.new actions
|
50
|
+
case checkout_transaction.execute
|
51
|
+
when :execution_success then puts "Checkout successful".green
|
52
|
+
when :rollback_success then puts "An error prevented checking out the system. Rolled back successfully".yellow
|
53
|
+
when :rollback_failure then puts "An error was encountered during the checkout. In addition, an error happened while trying to rollback the system. Please fix the state of your system manually.".red
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
36
57
|
end
|
37
58
|
|
38
|
-
def
|
39
|
-
branchName = "feature/#{name}"
|
59
|
+
def create_state prefix, state
|
40
60
|
@repos.each { |repo|
|
41
61
|
puts "\n # #{repo.name}".blue
|
42
|
-
repo.
|
43
|
-
repo.checkout
|
44
|
-
|
62
|
+
repo.create_state prefix, state
|
63
|
+
repo.checkout "#{prefix}/#{state}" if not prefix == "snapshot"
|
64
|
+
} if check_dirtiness
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def load_state prefix, state, remote = nil, should_create_branch = false
|
69
|
+
action_options = -> (repo) {
|
70
|
+
{
|
71
|
+
up: -> {
|
72
|
+
puts "\n # #{repo.name}".blue
|
73
|
+
repo.load_state prefix, state, remote, should_create_branch
|
74
|
+
},
|
75
|
+
down: -> {
|
76
|
+
puts "\n # Rolling back in #{repo.name}".blue
|
77
|
+
puts repo.checkout repo.pop_previous_branch
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
if check_dirtiness
|
83
|
+
|
84
|
+
actions = @repos.map { |repo|
|
85
|
+
Transacted::Action.new action_options.call repo
|
86
|
+
}
|
87
|
+
|
88
|
+
load_state_transaction = Transacted::Transaction.new actions
|
89
|
+
case load_state_transaction.execute
|
90
|
+
when :execution_success then puts "Loading state successful".green
|
91
|
+
when :rollback_success then puts "An error prevented loading the state of the system. Rolled back successfully".yellow
|
92
|
+
when :rollback_failure then puts "An error was encountered during the loading of the state. In addition, an error happened while trying to rollback the system. Please fix the state of your system manually.".red
|
93
|
+
end
|
94
|
+
end
|
45
95
|
end
|
46
96
|
|
47
|
-
def loadFeature name
|
48
|
-
@repos.each { |repo|
|
49
|
-
puts "\n # #{repo.name}".blue
|
50
|
-
repo.checkout "feature/#{name}"
|
51
|
-
} if checkConsistency && checkDirtiness
|
52
|
-
end
|
53
97
|
|
54
|
-
def
|
98
|
+
def send_state prefix, state, remote, should_clean = false
|
55
99
|
@repos.each { |repo|
|
56
100
|
puts "\n # #{repo.name}".blue
|
57
|
-
repo.
|
58
|
-
|
101
|
+
is_branch = repo.current_branch.match /refs\/heads\/(.*)/
|
102
|
+
repo.send_state prefix, state, remote, !!is_branch, should_clean
|
103
|
+
} if check_dirtiness
|
59
104
|
end
|
60
105
|
|
61
|
-
def getBranches
|
62
|
-
@repos.each { |repo| repo.branch }
|
63
|
-
end
|
64
106
|
|
65
|
-
def update remote
|
107
|
+
def update remote = nil
|
66
108
|
@repos.each { |repo|
|
67
109
|
puts "\n # #{repo.name}".blue
|
68
|
-
repo.
|
69
|
-
|
70
|
-
} if checkConsistency && checkDirtiness
|
71
|
-
end
|
72
|
-
|
73
|
-
def deploy state = nil
|
74
|
-
@repos.each { |repo|
|
75
|
-
if state === nil
|
76
|
-
state = repo.currentBranch
|
77
|
-
end
|
78
|
-
repo.checkout state
|
79
|
-
repo.deploy state
|
80
|
-
} if checkConsistency && checkDirtiness
|
110
|
+
repo.update remote
|
111
|
+
} if check_dirtiness
|
81
112
|
end
|
82
113
|
|
83
|
-
def diff branch = nil
|
84
|
-
if branch === nil
|
85
|
-
branch = 'develop'
|
86
|
-
end
|
87
|
-
|
88
|
-
@repos.each { |repo| repo.countDiffBranches branch, repo.currentBranch}
|
89
|
-
end
|
90
114
|
|
91
115
|
def branches
|
92
|
-
@repos.each { |repo| puts "#{repo.name}@#{repo.
|
116
|
+
@repos.each { |repo| puts "#{repo.name}@#{repo.current_branch}" }
|
93
117
|
end
|
94
118
|
|
95
|
-
def
|
119
|
+
def system_state
|
96
120
|
branches = Hash.new 0
|
97
|
-
@repos.each {|repo| branches[repo.
|
121
|
+
@repos.each {|repo| branches[repo.current_branch] += 1}
|
98
122
|
|
99
123
|
state = branches.max{|a,b| a[1] <=> b[1]}
|
100
124
|
state[0] = state.first.strip
|
101
125
|
state
|
102
126
|
end
|
103
127
|
|
104
|
-
def
|
105
|
-
|
106
|
-
if currentBranch.last != @repos.length
|
107
|
-
branchName = currentBranch.first
|
108
|
-
inconsistentRepos = @repos.reject { |repo|
|
109
|
-
repo.currentBranch == branchName
|
110
|
-
}.map &:name
|
111
|
-
|
112
|
-
puts 'The system is in an inconsistent state. Please check the following repos :'.red, inconsistentRepos
|
113
|
-
return false
|
114
|
-
end
|
115
|
-
puts 'The system is consistent'.green
|
116
|
-
true
|
117
|
-
|
118
|
-
end
|
119
|
-
|
120
|
-
def checkDirtiness
|
121
|
-
dirtyRepos = @repos.reject { |repo|
|
128
|
+
def check_dirtiness
|
129
|
+
dirty_repos = @repos.reject { |repo|
|
122
130
|
!repo.dirty?
|
123
131
|
}.map &:name
|
124
132
|
|
125
|
-
if
|
133
|
+
if dirty_repos.length == 0
|
126
134
|
puts 'The system is clean'.green
|
127
135
|
else
|
128
|
-
puts 'The system is dirty. Please check the following repos :'.red ,
|
136
|
+
puts 'The system is dirty. Please check the following repos :'.red , dirty_repos
|
129
137
|
end
|
130
|
-
|
138
|
+
dirty_repos.length == 0
|
131
139
|
end
|
132
140
|
|
133
|
-
def
|
141
|
+
def read_config path
|
142
|
+
Errors::config_not_found! if not File.exist? path
|
143
|
+
|
134
144
|
config = YAML.load File.open(path)
|
135
|
-
@
|
136
|
-
@
|
137
|
-
@
|
145
|
+
@repos_paths = config['repos'] || []
|
146
|
+
@origin_remote = config['remotes']['main']
|
147
|
+
@own_remote = config['remotes']['own']
|
148
|
+
@ignored_repos = config['ignore'] || []
|
149
|
+
end
|
150
|
+
|
151
|
+
def save_config path
|
152
|
+
config = {
|
153
|
+
'repos' => @repos_paths.map{ |repo| repo },
|
154
|
+
'remotes'=> {
|
155
|
+
'main' => @origin_remote,
|
156
|
+
'own' => @own_remote
|
157
|
+
},
|
158
|
+
'ignore' => @ignored_repos
|
159
|
+
}
|
160
|
+
File.open(path, 'w') {|f| f.write config.to_yaml }
|
161
|
+
end
|
162
|
+
|
163
|
+
def clear_repos
|
164
|
+
@repos_paths = []
|
165
|
+
save_config "#{@base_path}/.meta.yml"
|
166
|
+
puts "Repos cache cleared.".green
|
167
|
+
end
|
168
|
+
|
169
|
+
def pull
|
170
|
+
@repos.each { |repo|
|
171
|
+
puts "\n # #{repo.name}".blue
|
172
|
+
repo.pull @origin_remote
|
173
|
+
} if check_dirtiness
|
174
|
+
end
|
175
|
+
|
176
|
+
def add_remote name, base_url
|
177
|
+
@repos.each { |repo|
|
178
|
+
puts "\n # #{repo.name}".blue
|
179
|
+
repo.add_remote name, "#{base_url}/#{repo.name}"
|
180
|
+
}
|
181
|
+
end
|
182
|
+
|
183
|
+
def exec env, command
|
184
|
+
@repos.each { |repo|
|
185
|
+
puts "\n # #{repo.name}".blue
|
186
|
+
repo.exec env, command
|
187
|
+
}
|
138
188
|
end
|
139
189
|
end
|
140
190
|
end
|
data/lib/metaverse/cli.rb
CHANGED
@@ -8,6 +8,12 @@ module Metaverse
|
|
8
8
|
@meta = Base.new Dir.pwd
|
9
9
|
end
|
10
10
|
|
11
|
+
desc "reset", "Empties the repos cache"
|
12
|
+
def reset
|
13
|
+
init
|
14
|
+
@meta.clear_repos
|
15
|
+
end
|
16
|
+
|
11
17
|
desc "branches", "Displays the branch in which each repo is"
|
12
18
|
def branches
|
13
19
|
init
|
@@ -15,34 +21,61 @@ module Metaverse
|
|
15
21
|
end
|
16
22
|
|
17
23
|
desc "snapshot [COMMAND] [SNAPSHOT_NAME]", "Creates or loads a snapshot of the system"
|
18
|
-
def snapshot command, name
|
24
|
+
def snapshot command, name, remote = nil
|
19
25
|
init
|
20
26
|
case command
|
21
|
-
when 'new' then @meta.
|
22
|
-
when 'load' then @meta.
|
23
|
-
when '
|
27
|
+
when 'new' then @meta.create_state 'snapshot', name
|
28
|
+
when 'load' then @meta.load_state 'snapshot', name, remote
|
29
|
+
when 'send' then @meta.send_state 'snapshot', name, remote
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
27
33
|
desc "feature [COMMAND] [FEATURE_NAME]", "Creates a new feature branch or loads a previous one"
|
28
|
-
|
34
|
+
option :branch, type: :boolean, aliases: '-b'
|
35
|
+
def feature command, name, remote = nil
|
36
|
+
init
|
37
|
+
case command
|
38
|
+
when 'new' then @meta.create_state 'feature', name
|
39
|
+
when 'load' then @meta.load_state 'feature', name, remote, options[:branch]
|
40
|
+
when 'send' then @meta.send_state 'feature', name, remote
|
41
|
+
when 'close' then @meta.send_state 'feature', name, remote, true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
desc "release [COMMAND] [FEATURE_NAME]", "Creates a new release branch or loads a previous one"
|
47
|
+
option :branch, type: :boolean, aliases: '-b'
|
48
|
+
def release command, name, remote = nil
|
49
|
+
init
|
50
|
+
case command
|
51
|
+
when 'new' then @meta.create_state 'release', name
|
52
|
+
when 'load' then @meta.load_state 'release', name, remote, options[:branch]
|
53
|
+
when 'send' then @meta.send_state 'release', name, remote
|
54
|
+
when 'close' then @meta.send_state 'release', name, remote, true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
desc "bugfix [COMMAND] [FEATURE_NAME]", "Creates a new bugfix branch or loads a previous one"
|
60
|
+
option :branch, type: :boolean, aliases: '-b'
|
61
|
+
def bugfix command, name, remote = nil
|
29
62
|
init
|
30
63
|
case command
|
31
|
-
when 'new' then @meta.
|
32
|
-
when 'load' then @meta.
|
33
|
-
when '
|
34
|
-
when '
|
64
|
+
when 'new' then @meta.create_state 'bugfix', name
|
65
|
+
when 'load' then @meta.load_state 'bugfix', name, remote, options[:branch]
|
66
|
+
when 'send' then @meta.send_state 'bugfix', name, remote
|
67
|
+
when 'close' then @meta.send_state 'bugfix', name, remote, true
|
35
68
|
end
|
36
69
|
end
|
37
70
|
|
38
71
|
desc "status", "Displays the status of the system"
|
39
72
|
def status
|
40
73
|
init
|
41
|
-
puts "System is at #{@meta.
|
74
|
+
puts "System is at #{@meta.system_state.first}"
|
42
75
|
@meta.status
|
43
76
|
end
|
44
77
|
|
45
|
-
desc "update [REMOTE_NAME]", "
|
78
|
+
desc "update [REMOTE_NAME]", "Fetches the latest code corresponding to the current branch. Remote is optional"
|
46
79
|
def update remote = nil
|
47
80
|
init
|
48
81
|
@meta.update remote
|
@@ -64,9 +97,28 @@ module Metaverse
|
|
64
97
|
def check command
|
65
98
|
init
|
66
99
|
case command
|
67
|
-
when '
|
68
|
-
when 'dirtiness' then @meta.checkDirtiness
|
100
|
+
when 'dirtiness' then @meta.check_dirtiness
|
69
101
|
end
|
70
102
|
end
|
103
|
+
|
104
|
+
desc "pull", "Pulls the latest changes from the main origin"
|
105
|
+
def pull
|
106
|
+
init
|
107
|
+
@meta.pull
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "remote [COMMAND]", "manages repos remotes such as adding new ones"
|
111
|
+
def remote command, name, url
|
112
|
+
init
|
113
|
+
case command
|
114
|
+
when 'add' then @meta.add_remote name, url
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "exec [COMMAND]", "runs the specified shell command in every repo"
|
119
|
+
def exec *command
|
120
|
+
init
|
121
|
+
@meta.exec ENV, command.join(' ')
|
122
|
+
end
|
71
123
|
end
|
72
124
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
module Metaverse
|
4
|
+
class Errors
|
5
|
+
def self.config_not_found!
|
6
|
+
abort ".meta.yml file was not found in the current working directory. Exiting ...".red
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.ref_not_found ref
|
10
|
+
return Exception.new "Reference '#{ref}' does not exist.".red
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.remote_not_found remote
|
14
|
+
puts "Remote '#{remote}' does not exist. Skipping ...".red
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.remote_exists remote
|
18
|
+
puts "Remote '#{remote}' exists already. Skipping ...".red
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/metaverse/iterator.rb
CHANGED
data/lib/metaverse/repo.rb
CHANGED
@@ -1,18 +1,224 @@
|
|
1
|
-
require '
|
1
|
+
require 'rugged'
|
2
|
+
require 'iniparse'
|
2
3
|
|
3
4
|
module Metaverse
|
4
5
|
class Repo
|
5
|
-
|
6
|
-
|
6
|
+
|
7
|
+
# Initialize a git repository
|
8
|
+
# path - An absolute path to the repo root folder
|
9
|
+
|
7
10
|
def initialize path
|
8
11
|
load path
|
12
|
+
@previous_branches = []
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def load path
|
17
|
+
@repo = Rugged::Repository.new(path)
|
18
|
+
@repo.remotes.each_name do |remote|
|
19
|
+
fetch_refspec = "+refs/meta/local/*:refs/meta/remotes/#{remote}/*"
|
20
|
+
push_refspec = "+refs/meta/local/*:refs/meta/local/*"
|
21
|
+
add_option_to_remote remote, fetch_refspec, 'fetch'
|
22
|
+
add_option_to_remote remote, push_refspec, 'push'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def name
|
28
|
+
File.basename @repo.workdir
|
29
|
+
end
|
30
|
+
|
31
|
+
def workdir
|
32
|
+
@repo.workdir
|
33
|
+
end
|
34
|
+
|
35
|
+
def checkout ref
|
36
|
+
if @repo.branches[ref].nil?
|
37
|
+
is_reference = false
|
38
|
+
if Rugged::Reference.valid_name?(ref) and @repo.references[ref].nil?
|
39
|
+
raise Errors::ref_not_found ref
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@previous_branches.push current_branch
|
44
|
+
Dir.chdir(@repo.workdir) do
|
45
|
+
`git checkout #{ref}`#TODO: find out what's going on, check out a branch
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def current_branch
|
51
|
+
branch_name = ''
|
52
|
+
|
53
|
+
Dir.chdir(@repo.workdir) do
|
54
|
+
branch_name = `git branch | sed -n '/\* /s///p'`
|
55
|
+
end
|
56
|
+
|
57
|
+
if branch_name.include? "detached"
|
58
|
+
match = /[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+/.match(branch_name)
|
59
|
+
if not match.nil?
|
60
|
+
return match[0]
|
61
|
+
end
|
62
|
+
return 'HEAD'
|
63
|
+
end
|
64
|
+
|
65
|
+
branch_name.strip
|
66
|
+
end
|
67
|
+
|
68
|
+
def peek_previous_branch
|
69
|
+
@previous_branches.last
|
70
|
+
end
|
71
|
+
|
72
|
+
def pop_previous_branch
|
73
|
+
@previous_branches.pop
|
74
|
+
end
|
75
|
+
|
76
|
+
def head
|
77
|
+
@repo.head
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def branches
|
82
|
+
@repo.branches
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def create_ref ref_name, ref_target
|
87
|
+
@repo.references.create ref_name, ref_target.target_id
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def update_ref ref_name, ref_target
|
92
|
+
@repo.references.update ref_name, ref_target.target_id
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def create_state prefix, state
|
97
|
+
ref_target = @repo.head
|
98
|
+
if not prefix == 'snapshot'
|
99
|
+
branch_name = "#{prefix}/#{state}"
|
100
|
+
ref_target = @repo.create_branch branch_name
|
101
|
+
end
|
102
|
+
ref_name = "refs/meta/local/#{prefix}/#{state}"
|
103
|
+
create_ref ref_name, ref_target
|
9
104
|
end
|
10
105
|
|
11
|
-
|
12
|
-
|
13
|
-
|
106
|
+
|
107
|
+
def load_state prefix, state, remote = nil, should_create_branch = false
|
108
|
+
ref_name = "refs/meta"
|
109
|
+
ref_name += remote.nil? ? "/local" : "/remotes/#{remote}"
|
110
|
+
ref_name += "/#{prefix}/#{state}"
|
111
|
+
if should_create_branch
|
112
|
+
branch_name = "#{prefix}/#{state}"
|
113
|
+
# TODO: Decide what to do when a branch exists previous. Should we :
|
114
|
+
# - Replace it's ref by the saved state ? ( We lose the previous branch reference)
|
115
|
+
# - Load it as it is ( We may get an inconsistent state since the branch could point to a different commit than the state reference )
|
116
|
+
if @repo.branches[branch_name].nil?
|
117
|
+
puts "Creating branch #{branch_name} based on #{ref_name}"
|
118
|
+
@repo.create_branch branch_name, ref_name
|
119
|
+
end
|
120
|
+
return checkout branch_name
|
121
|
+
end
|
122
|
+
checkout ref_name
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def send_state prefix, state, remote, is_branch, should_clean = false
|
127
|
+
refs = ["refs/meta/local/#{prefix}/#{state}"]
|
128
|
+
branch_name = "#{prefix}/#{state}"
|
129
|
+
branch = @repo.branches[branch_name]
|
130
|
+
develop = @repo.branches['develop']
|
131
|
+
|
132
|
+
if branch.nil?
|
133
|
+
Errors::ref_not_found branch_name
|
134
|
+
return false
|
135
|
+
end
|
136
|
+
|
137
|
+
if develop.nil?
|
138
|
+
Errors::ref_not_found 'develop'
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
if (not prefix == 'snapshot') and is_branch and ahead_of_develop? branch
|
143
|
+
refs << branch_name
|
144
|
+
update_ref refs[0], @repo.branches[branch_name]
|
145
|
+
end
|
146
|
+
|
147
|
+
clean_branch branch if should_clean
|
148
|
+
|
149
|
+
Dir.chdir(@repo.workdir) do
|
150
|
+
`git push #{remote} #{refs.join(' ')}`
|
14
151
|
end
|
15
152
|
end
|
16
153
|
|
154
|
+
|
155
|
+
def clean_branch branch
|
156
|
+
|
157
|
+
changed = ahead_of_develop? branch
|
158
|
+
if not changed
|
159
|
+
checkout 'develop'
|
160
|
+
@repo.branches.delete branch
|
161
|
+
end
|
162
|
+
changed
|
163
|
+
end
|
164
|
+
|
165
|
+
def dirty?
|
166
|
+
is_dirty = false
|
167
|
+
@repo.status { |file, data|
|
168
|
+
next if data == [:ignored]
|
169
|
+
is_dirty = true
|
170
|
+
}
|
171
|
+
is_dirty
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def update remote = nil
|
176
|
+
if @repo.remotes[remote].nil?
|
177
|
+
return Errors::remote_not_found remote
|
178
|
+
end
|
179
|
+
Dir.chdir(@repo.workdir) do
|
180
|
+
`git remote update #{remote}`
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def add_option_to_remote remote, value, key
|
186
|
+
config_path = "#{@repo.path}config"
|
187
|
+
cfg = IniParse.parse(File.read(config_path))
|
188
|
+
if not [*cfg["remote \"#{remote}\""][key]].include? value
|
189
|
+
cfg["remote \"#{remote}\""][key] = [*cfg["remote \"#{remote}\""][key], value]
|
190
|
+
cfg.save config_path
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
def has_changes? ref1, ref2
|
196
|
+
not ref1.target_id == ref2.target_id
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def ahead_of_develop? branch
|
201
|
+
has_changes? branch, @repo.branches['develop']
|
202
|
+
end
|
203
|
+
|
204
|
+
def pull remote
|
205
|
+
if @repo.remotes[remote].nil?
|
206
|
+
return Errors::remote_not_found remote
|
207
|
+
end
|
208
|
+
Dir.chdir(@repo.workdir) do
|
209
|
+
`git pull #{remote} #{current_branch}`
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def add_remote name, url
|
214
|
+
return Errors::remote_exists name if not @repo.remotes[name].nil?
|
215
|
+
@repo.remotes.create name, url
|
216
|
+
end
|
217
|
+
|
218
|
+
def exec env, command
|
219
|
+
Dir.chdir(@repo.workdir) do
|
220
|
+
system env, command
|
221
|
+
end
|
222
|
+
end
|
17
223
|
end
|
18
224
|
end
|
data/lib/metaverse/version.rb
CHANGED
data/lib/metaverse.rb
CHANGED
data/metaverse.gemspec
CHANGED
@@ -28,4 +28,6 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency "rspec"
|
29
29
|
spec.add_runtime_dependency "thor", "~> 0.19"
|
30
30
|
spec.add_runtime_dependency "colorize", "~> 0.7"
|
31
|
+
spec.add_runtime_dependency "rugged", "~> 0.21"
|
32
|
+
spec.add_runtime_dependency "transacted", "~> 0.1"
|
31
33
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: metaverse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Omar Kamali
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,6 +80,34 @@ dependencies:
|
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0.7'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rugged
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.21'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.21'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: transacted
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.1'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.1'
|
83
111
|
description:
|
84
112
|
email:
|
85
113
|
- okamali@productivemobile.com
|
@@ -101,7 +129,7 @@ files:
|
|
101
129
|
- lib/metaverse.rb
|
102
130
|
- lib/metaverse/base.rb
|
103
131
|
- lib/metaverse/cli.rb
|
104
|
-
- lib/metaverse/
|
132
|
+
- lib/metaverse/errors.rb
|
105
133
|
- lib/metaverse/iterator.rb
|
106
134
|
- lib/metaverse/repo.rb
|
107
135
|
- lib/metaverse/version.rb
|
data/lib/metaverse/git.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
require 'find'
|
3
|
-
|
4
|
-
module Metaverse
|
5
|
-
module Git
|
6
|
-
def load path
|
7
|
-
if !File.directory? path + '/.git'
|
8
|
-
raise "#{path} is not a git repo"
|
9
|
-
end
|
10
|
-
@path = path
|
11
|
-
end
|
12
|
-
|
13
|
-
def name
|
14
|
-
File.basename @path
|
15
|
-
end
|
16
|
-
|
17
|
-
def checkout branch
|
18
|
-
Dir.chdir(@path) do
|
19
|
-
puts `git checkout #{branch}`
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def currentBranch
|
24
|
-
Dir.chdir(@path) do
|
25
|
-
branchName = `git rev-parse --abbrev-ref HEAD`;
|
26
|
-
branchName.strip
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def branch branch
|
31
|
-
Dir.chdir(@path) do
|
32
|
-
puts `git branch #{branch}`
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def status
|
37
|
-
Dir.chdir(@path) do
|
38
|
-
puts `git status`
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def tag name
|
43
|
-
Dir.chdir(@path) do
|
44
|
-
puts `git tag #{name}`
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def pull remote
|
49
|
-
Dir.chdir(@path) do
|
50
|
-
`git pull #{remote} #{currentBranch}:#{currentBranch}`
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def push remote, branch
|
55
|
-
Dir.chdir(@path) do
|
56
|
-
puts `git push #{remote} #{branch}:#{branch}`
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
def fetch remote
|
61
|
-
Dir.chdir(@path) do
|
62
|
-
`git fetch #{remote}`
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def reset branch
|
67
|
-
Dir.chdir(@path) do
|
68
|
-
puts `git reset --hard #{branch}`
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def countDiffBranches branch1, branch2
|
73
|
-
Dir.chdir(@path) do
|
74
|
-
`git rev-list --count #{branch1}..#{branch2}`
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def dirty?
|
79
|
-
Dir.chdir(@path) do
|
80
|
-
`git status --porcelain`.length != 0
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|