scvcs 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ desc 'Merges two branches to the working directory'
2
+ arg_name '<branch name>'
3
+ command :merge do |c|
4
+ c.desc 'Abort the current merge. Resets the working dir and the next commit parents'
5
+ c.arg_name 'abort'
6
+ c.switch :abort
7
+
8
+ c.desc 'Squashes the changes to a single commit. The next commit will have one parent'
9
+ c.arg_name 'squash'
10
+ c.switch :squash
11
+
12
+ c.action do |global_options, options, args|
13
+ repository = global_options[:repository]
14
+ commit = repository[:head, :commit]
15
+
16
+ if options[:abort]
17
+ repository.config['merge'] = {}
18
+ repository.config.save
19
+
20
+ repository.restore '.', commit
21
+
22
+ next
23
+ end
24
+
25
+ raise 'No branch specified to merge from' if args.empty?
26
+
27
+ commit_two = repository[args.first, :commit]
28
+
29
+ raise 'There is no branch with that name' if commit_two.nil?
30
+
31
+ merge_status = repository.merge commit, commit_two
32
+
33
+ unless options[:squash]
34
+ # Save the merged commit ids so that the next
35
+ # commit has both as parents.
36
+ repository.config['merge'] = {
37
+ 'parents' => [
38
+ commit.id,
39
+ commit_two.id,
40
+ ]
41
+ }
42
+ repository.config.save
43
+ end
44
+
45
+ puts "# Merged #{args.first} into #{repository.head}"
46
+ puts
47
+
48
+ SCV::Formatters::MergeReport.print merge_status
49
+
50
+ unless merge_status[:conflicted].any?
51
+ puts "# You can now use `scv commit` to create the merge commit"
52
+ puts "# The next commit will have two parents" unless options[:squash]
53
+ puts "# To abort the merge (not create a merge commit) use `scv merge --abort`"
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,157 @@
1
+ desc 'A set of commands to manage remotes and push/pull to them.'
2
+ arg_name ''
3
+ command :remote do |c|
4
+
5
+ c.desc 'Add a remote'
6
+ c.arg_name '<remote name> <remote HTTP url>'
7
+ c.command [:add, :new] do |add|
8
+ add.action do |global_options, options, args|
9
+ raise 'Please specify remote name and address' unless args.size == 2
10
+
11
+ name = args[0]
12
+ url = args[1]
13
+
14
+ repository = global_options[:repository]
15
+ repository.config['remotes'] = {} unless repository.config['remotes']
16
+
17
+ raise 'There is already a remote with that name' if repository.config['remotes'].key? name
18
+
19
+ repository.config['remotes'][name] = {
20
+ 'url' => url
21
+ }
22
+ repository.config.save
23
+ end
24
+ end
25
+
26
+ c.desc 'Delete a remote'
27
+ c.arg_name '<remote name>'
28
+ c.command [:delete, :remove] do |remove|
29
+ remove.action do |global_options, options, args|
30
+ raise 'Please specify remote name' unless args.size == 1
31
+
32
+ name = args[0]
33
+
34
+ repository = global_options[:repository]
35
+
36
+ unless repository.config['remotes'] and repository.config['remotes'].key? name
37
+ raise 'There is no remote with that name'
38
+ end
39
+
40
+ repository.config['remotes'].delete name
41
+ repository.config.save
42
+ end
43
+ end
44
+
45
+ c.desc 'Push local history to remote'
46
+ c.arg_name '<remote> <local branch> [<remote branch>]'
47
+ c.command :push do |push|
48
+ push.action do |global_options, options, args|
49
+ raise 'Please specify remote name and local branch' unless args.size >= 2
50
+
51
+ repository = global_options[:repository]
52
+
53
+ remote_name = args[0]
54
+ local_branch = args[1]
55
+
56
+ raise 'There is no remote with this name' unless repository.config['remotes'][remote_name]
57
+ raise 'There is no branch with this name' unless repository[local_branch]
58
+
59
+ if args.size > 2
60
+ remote_branch = args[2]
61
+ else
62
+ remote_branch = local_branch
63
+ end
64
+
65
+ remote_file_store = SCV::HTTPFileStore.new repository.config['remotes'][remote_name]['url']
66
+ remote_object_store = SCV::ObjectStore.new remote_file_store
67
+
68
+ repository.push remote_object_store, local_branch, remote_branch
69
+ end
70
+ end
71
+
72
+ c.desc 'Fetch remote history objects to local branch. Does not do merging, only Fast-Forward'
73
+ c.arg_name '<remote> <remote branch> [<local branch>]'
74
+ c.command :fetch do |fetch|
75
+ fetch.action do |global_options, options, args|
76
+ raise 'Please specify remote name and remote branch' unless args.size >= 2
77
+
78
+ repository = global_options[:repository]
79
+
80
+ remote_name = args[0]
81
+ remote_branch = args[1]
82
+
83
+ if args.size > 2
84
+ local_branch = args[2]
85
+ else
86
+ local_branch = remote_branch
87
+ end
88
+
89
+ unless repository[local_branch]
90
+ # There is no local branch with this name, so create it
91
+ repository.set_label local_branch, nil
92
+ end
93
+
94
+ raise 'There is no remote with this name' unless repository.config['remotes'][remote_name]
95
+
96
+ remote_file_store = SCV::HTTPFileStore.new repository.config['remotes'][remote_name]['url']
97
+ remote_object_store = SCV::ObjectStore.new remote_file_store
98
+
99
+ repository.fetch remote_object_store, remote_branch, local_branch
100
+ end
101
+ end
102
+
103
+ c.desc 'Pull remote history objects to local branch. Merges automatically'
104
+ c.arg_name '<remote> <remote branch> [<local branch>]'
105
+ c.command :pull do |pull|
106
+ pull.action do |global_options, options, args|
107
+ raise 'Please specify remote name and remote branch' unless args.size >= 2
108
+
109
+ repository = global_options[:repository]
110
+
111
+ remote_name = args[0]
112
+ remote_branch = args[1]
113
+
114
+ if args.size > 2
115
+ local_branch = args[2]
116
+ else
117
+ local_branch = remote_branch
118
+ end
119
+
120
+ unless repository[local_branch]
121
+ # There is no local branch with this name, so create it
122
+ repository.set_label local_branch, nil
123
+ end
124
+
125
+ raise 'There is no remote with this name' unless repository.config['remotes'][remote_name]
126
+
127
+ remote_file_store = SCV::HTTPFileStore.new repository.config['remotes'][remote_name]['url']
128
+ remote_object_store = SCV::ObjectStore.new remote_file_store
129
+
130
+ # Step 1: Fetch to another branch
131
+ temp_branch = "#{remote_name}~#{remote_branch}"
132
+
133
+ # Create the temp branch if not existing
134
+ repository.set_label temp_branch, nil unless repository[temp_branch]
135
+
136
+ repository.fetch remote_object_store, remote_branch, temp_branch
137
+
138
+ # Step 2: Merge temp_branch into local_branch
139
+ dest_commit = repository[temp_branch, :commit]
140
+ source_commit = repository[local_branch, :commit]
141
+ merge_status = repository.merge source_commit, dest_commit
142
+
143
+ # Save the merged commit ids so that the next
144
+ # commit has both as parents.
145
+ repository.config['merge'] = {
146
+ 'parents' => [
147
+ source_commit.id,
148
+ dest_commit.id,
149
+ ]
150
+ }
151
+ repository.config.save
152
+
153
+ SCV::Formatters::MergeReport.print merge_status
154
+ end
155
+ end
156
+
157
+ end
@@ -0,0 +1,17 @@
1
+ desc 'Resets files to the state they were in the specified commit'
2
+ arg_name '<file> ...'
3
+ command [:restore] do |c|
4
+ c.desc 'Use this specify which commit should be the used as reference'
5
+ c.arg_name 'source'
6
+ c.default_value 'head'
7
+ c.flag [:s, :source]
8
+
9
+ c.action do |global_options, options, args|
10
+ repository = global_options[:repository]
11
+ commit = repository[options[:source], :commit]
12
+
13
+ args.each do |path|
14
+ repository.restore path, commit
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ desc 'Starts a barebones HTTP server to act as a remote repo'
2
+ arg_name ''
3
+ command [:server] do |c|
4
+ c.action do |global_options, options, args|
5
+ repository = global_options[:repository]
6
+
7
+ server = SCV::Server.new File.join(global_options[:dir], '.scv')
8
+ server.start
9
+ end
10
+ end
@@ -0,0 +1,52 @@
1
+ desc 'Shows the created, modified and deleted files'
2
+ arg_name ''
3
+ command :status do |c|
4
+ c.action do |global_options, options, args|
5
+ repository = global_options[:repository]
6
+ commit = repository[:head, :commit]
7
+ status = repository.status commit,
8
+ ignore: [/^\.|\/\./]
9
+
10
+ if repository.head.nil?
11
+ puts "# No commits"
12
+ else
13
+ puts "# On branch #{repository.head}"
14
+ puts "# On commit #{commit.id.yellow}"
15
+ end
16
+
17
+ if repository.config['merge'] and repository.config['merge']['parents']
18
+ parents = repository.config['merge']['parents']
19
+
20
+ puts
21
+ puts "# Next commit parents:"
22
+
23
+ parents.each do |commit_id|
24
+ puts "# - #{commit_id.yellow}"
25
+ end
26
+ end
27
+
28
+ puts
29
+
30
+ if status.none? { |_, files| files.any? }
31
+ puts "# No changes"
32
+ end
33
+
34
+ if status[:changed].any?
35
+ puts "# Changed:"
36
+ puts status[:changed].map { |file| "+- #{file}".yellow }
37
+ puts
38
+ end
39
+
40
+ if status[:created].any?
41
+ puts "# New:"
42
+ puts status[:created].map { |file| "+ #{file}".green }
43
+ puts
44
+ end
45
+
46
+ if status[:deleted].any?
47
+ puts "# Deleted:"
48
+ puts status[:deleted].map { |file| "- #{file}".red }
49
+ puts
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ module SCV::Formatters
2
+ module Changeset
3
+ def self.print(file_path, changeset)
4
+ added_count = changeset.count { |change| change.adding? }
5
+ changed_count = changeset.count { |change| change.changed? }
6
+ deleted_count = changeset.count { |change| change.deleting? }
7
+
8
+ diff_lines = changeset.flat_map do |change|
9
+ if change.unchanged?
10
+ [" #{change.new_element}"]
11
+ elsif change.deleting?
12
+ ["-#{change.old_element}".red]
13
+ elsif change.adding?
14
+ ["+#{change.new_element}".green]
15
+ elsif change.changed?
16
+ ["-#{change.old_element}".red, "+#{change.new_element}".green]
17
+ else
18
+ raise "Unknown change in the diff #{change.action}"
19
+ end
20
+ end
21
+
22
+ puts
23
+ puts "##".bold
24
+ puts "# #{file_path}".bold
25
+ puts '#'.bold
26
+
27
+ if changeset.all? { |change| change.adding? }
28
+ puts "# New file".bold
29
+ puts "# #{added_count} #{added_count == 1 ? 'line' : 'lines'}".bold
30
+ elsif changeset.all? { |change| change.deleting? }
31
+ puts "# Deleted file".bold
32
+ puts "# #{deleted_count} #{deleted_count == 1 ? 'line' : 'lines'}".bold
33
+ else
34
+ puts "# #{added_count} #{added_count == 1 ? 'line' : 'lines'} added".bold unless added_count.zero?
35
+ puts "# #{changed_count} #{changed_count == 1 ? 'line' : 'lines'} changed".bold unless changed_count.zero?
36
+ puts "# #{deleted_count} #{deleted_count == 1 ? 'line' : 'lines'} deleted".bold unless deleted_count.zero?
37
+ end
38
+
39
+ puts "##".bold
40
+ puts diff_lines.join('')
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ module SCV::Formatters
2
+ module Hierarchy
3
+ def self.print(hash, indent_size: 3, prefix: '')
4
+ hash.each_with_index do |(name, value), index|
5
+ is_last = index == hash.size - 1
6
+
7
+ Kernel.print prefix
8
+
9
+ unless prefix.empty?
10
+ if is_last
11
+ Kernel.print ' └'
12
+ else
13
+ Kernel.print ' ├'
14
+ end
15
+
16
+ Kernel.print '─' * indent_size.pred.pred
17
+ Kernel.print ' '
18
+ end
19
+ Kernel.print name
20
+
21
+ if value.is_a? Hash
22
+ if value.size.zero?
23
+ puts ": {}"
24
+ else
25
+ puts
26
+
27
+ if prefix.empty?
28
+ inner_prefix = ' ' * indent_size.pred.pred
29
+ else
30
+ inner_prefix = prefix + ' ' + (is_last ? ' ' : '│') + (' ' * indent_size.pred.pred)
31
+ end
32
+
33
+ print value, indent_size: indent_size, prefix: inner_prefix
34
+ end
35
+ elsif value.is_a? Array
36
+ puts ": [#{value.join(', ')}]"
37
+ else
38
+ puts ": #{value}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ module SCV::Formatters
2
+ module MergeReport
3
+ def self.print(merge_status)
4
+ if merge_status[:merged].any?
5
+ puts "# Automatic merges:"
6
+
7
+ merge_status[:merged].each do |file|
8
+ puts " #{file}".yellow
9
+ end
10
+
11
+ puts
12
+ end
13
+
14
+ if merge_status[:conflicted].any?
15
+ puts "# Conflicted files:"
16
+
17
+ merge_status[:conflicted].each do |file|
18
+ puts " #{file}".red
19
+ end
20
+
21
+ puts
22
+ end
23
+ end
24
+ end
25
+ end
data/bin/scv ADDED
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scv'
4
+ require 'gli'
5
+ require 'colorize'
6
+
7
+ include GLI::App
8
+
9
+ program_desc 'A simple VCS implemented in Ruby on top of the VCSToolkit library'
10
+
11
+ version SCV::VERSION
12
+ subcommand_option_handling :normal
13
+
14
+ desc 'Print more stuff'
15
+ switch [:v, :verbose]
16
+
17
+ desc 'Colorize output if running in an interactive shell'
18
+ default_value true
19
+ switch [:colors]
20
+
21
+ desc 'The path to the working directory'
22
+ default_value '.'
23
+ arg_name 'directory path'
24
+ flag [:dir]
25
+
26
+ # Require utilities
27
+ require_relative 'utilities/shell'
28
+ require_relative 'utilities/output'
29
+
30
+ # Require formatters
31
+ require_relative 'formatters/changeset'
32
+ require_relative 'formatters/hierarchy'
33
+ require_relative 'formatters/merge_report'
34
+
35
+ # Require all available commands
36
+ require_relative 'commands/init'
37
+ require_relative 'commands/commit'
38
+ require_relative 'commands/status'
39
+ require_relative 'commands/history'
40
+ require_relative 'commands/diff'
41
+ require_relative 'commands/restore'
42
+ require_relative 'commands/branch'
43
+ require_relative 'commands/merge'
44
+ require_relative 'commands/config'
45
+ require_relative 'commands/server'
46
+ require_relative 'commands/remote'
47
+
48
+ pre do |global, command, options, args|
49
+ # Pre logic here
50
+ # Return true to proceed; false to abort and not call the
51
+ # chosen command
52
+ # Use skips_pre before a command to skip this block
53
+ # on that command only
54
+
55
+ # Check if the current directory is an SCV repository
56
+ # or is a subdirectory of one.
57
+ unless command.name == :init
58
+ working_dir = Pathname.new(global[:dir]).realpath
59
+ raise 'The working directory cannot be found' unless working_dir.directory?
60
+
61
+ working_dir = working_dir.enum_for(:ascend).find { |dir| dir.join('.scv').directory? }
62
+ raise "This directory is not (in) an SCV repository" if working_dir.nil?
63
+
64
+ global[:dir] = working_dir.to_path
65
+ end
66
+
67
+ # Initialize the repository object.
68
+ unless command.name == :init
69
+ repository = SCV::Repository.new global[:dir]
70
+ global[:repository] = repository
71
+ end
72
+
73
+ # Check if there is a commit in the repository
74
+ # as most commands expect one to exist.
75
+ unless [:init, :status, :commit, :server].include? command.name
76
+ raise "There are no commits in the repository" if repository.branch_head.nil?
77
+ end
78
+
79
+ # Switch colors on or off
80
+ unless global[:colors] and $stdout.isatty
81
+ # Disable colorize
82
+ String.class_eval do
83
+ alias_method :original_colorize, :colorize
84
+
85
+ def colorize(*args)
86
+ # Ignore color requests
87
+ self
88
+ end
89
+ end
90
+ end
91
+
92
+ true
93
+ end
94
+
95
+ post do |global, command, options, args|
96
+ # Post logic here
97
+ # Use skips_post before a command to skip this
98
+ # block on that command only
99
+ end
100
+
101
+ on_error do |exception|
102
+ # Error logic here
103
+ # return false to skip default error handling
104
+ true
105
+ end
106
+
107
+ exit run(ARGV)