scvcs 0.2.0

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.
@@ -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)