diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6c4e33f..ffd878e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ All pull requests must meet these requirements: ### Unit tests * All changes must be accompanied by new or modified RSpec unit tests * The entire test suite must pass when `bundle exec rake spec` is run from the - project's local working copy + project's local working tree * The unit test suite must maintain 100% code coverage to pass ### Documentation diff --git a/README.md b/README.md index 8b17d47..58699e3 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,34 @@ [![Test Coverage](https://api.codeclimate.com/v1/badges/5403e4613b7518f70da7/test_coverage)](https://codeclimate.com/github/main-branch/ruby_git/test_coverage) [![Slack](https://img.shields.io/badge/slack-main--branch/ruby__git-yellow.svg?logo=slack)](https://main-branch.slack.com/archives/C01CHR7TMM2) -RubyGit is an object-oriented wrapper for the `git` command line tool for working with Worktrees -and Repositories. It tries to make more sense out of the Git command line. See the object model -in [this Lucid chart diagram](https://app.lucidchart.com/invitations/accept/7df13bab-3383-4683-8cb4-e76d539de93d) -(requires sign in). +Git Is Hard™ but it doesn't have to be that way. Git has this reputation because it has an +underlying model that is more complex than other popular revision control systems +such as CVS or Subversion. To make matters worse, the `git` command line is vast, +inconsistently implemented, and does not have a clear mapping between the command-line +actions and Git's underlying model. + +Because of this complexity, beginners tend to memorize a few `git` commands in +order to get by with a simple workflow without really understanding how Git works +and the rich set of features it offers. + +The RubyGit module provides a Ruby API that is an object-oriented wrapper around +the `git` command line. It is intended to make automating both simple and complex Git +interactions easier. To accomplish this, it ties each action you can do with `git` to +the type of object that action operates on. + +There are three main objects in RubyGit: + * [WorkingTree](lib/ruby_git/working_tree.rb): The directory tree of actual checked + out files. The working tree normally contains the contents of the HEAD commit’s + tree, plus any local changes that you have made but not yet committed. + * [Index](lib/ruby_git/index.rb): The index is used as a staging area between your + working tree and your repository. You can use the index to build up a set of changes + that you want to commit together. When you create a commit, what is committed is what is + currently in the index, not what is in your working directory. + * [Repository](lib/ruby_git/repository.rb): The repository stores the files in a project, + their history, and other meta data like commit information, tags, and branches. + +The [RubyGit Class Diagram](RubyGit%20Class%20Diagram.svg) shows the main abstractions in +RubyGit, how they are related, and what actions each can perform. ## Installation @@ -41,24 +65,24 @@ RubyGit.git.path #=> '/usr/local/bin/git' RubyGit.git.version #=> [2,28,0] ``` -To work with an existing Worktree: +To work with an existing WorkingTree: ```Ruby -worktree = RubyGit.open(worktree_path) -worktree.append_to_file('README.md', 'New line in README.md') -worktree.add('README.md') -worktree.commit('Add a line to the README.md') -worktree.push +working_tree = RubyGit.open(working_tree_path) +working_tree.append_to_file('README.md', 'New line in README.md') +working_tree.add('README.md') +working_tree.commit('Add a line to the README.md') +working_tree.push ``` -To create a new Worktree: +To create a new WorkingTree: ```Ruby -worktree = RubyGit.init(worktree_path) -worktree.write_to_file('README.md', '# My New Project') -worktree.add('README.md') -worktree.repository.add_remote(remote_name: 'origin', url: 'https://github.com/jcouball/test', default_branch: 'main') -worktree.push(remote_name: 'origin') +working_tree = RubyGit.init(working_tree_path) +working_tree.write_to_file('README.md', '# My New Project') +working_tree.add('README.md') +working_tree.repository.add_remote(remote_name: 'origin', url: 'https://github.com/jcouball/test', default_branch: 'main') +working_tree.push(remote_name: 'origin') ``` To tell what version of Git is being used: diff --git a/RELEASING.md b/RELEASING.md index de40a99..56519bd 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -46,7 +46,7 @@ select `Draft a new release` ## Build and release the gem Clone [main-branch/ruby_git](https://github.com/main-branch/ruby_git) directly (not a -fork) and ensure your local working copy is on the main branch +fork) and ensure your local working tree is on the main branch * Verify that you are not on a fork with the command `git remote -v` * Verify that the version number is correct by running `rake -T` and inspecting diff --git a/RubyGit Class Diagram.svg b/RubyGit Class Diagram.svg new file mode 100644 index 0000000..4b8ef32 --- /dev/null +++ b/RubyGit Class Diagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/ruby_git.rb b/lib/ruby_git.rb index 80db117..0457597 100644 --- a/lib/ruby_git.rb +++ b/lib/ruby_git.rb @@ -4,13 +4,25 @@ require 'ruby_git/file_helpers' require 'ruby_git/git_binary' require 'ruby_git/version' -require 'ruby_git/worktree' +require 'ruby_git/working_tree' require 'null_logger' -# RubyGit is an object-oriented wrapper for the `git` command line tool for -# working with Worktrees and Repositories. It tries to make more sense out -# of the Git command line. +# The RubyGit module provides a Ruby API that is an object-oriented wrapper around +# the `git` command line. It is intended to make automating both simple and complex Git +# interactions easier. To accomplish this, it ties each action you can do with `git` to +# the type of object that action operates on. +# +# There are three main objects in RubyGit: +# * {WorkingTree}: The directory tree of actual checked +# out files. The working tree normally contains the contents of the HEAD commit's +# tree, plus any local changes that you have made but not yet committed. +# * Index: The index is used as a staging area between your +# working tree and your repository. You can use the index to build up a set of changes +# that you want to commit together. When you create a commit, what is committed is what is +# currently in the index, not what is in your working directory. +# * Repository: The repository stores the files in a project, +# their history, and other meta data like commit information, tags, and branches. # # @api public # @@ -55,68 +67,69 @@ class << self attr_accessor :logger end - # Create an empty Git repository under the root worktree `path` + # Create an empty Git repository under the root working tree `path` # # If the repository already exists, it will not be overwritten. # # @see https://git-scm.com/docs/git-init git-init # # @example - # worktree = Worktree.init(worktree_path) + # working_tree = WorkingTree.init(working_tree_path) # - # @param [String] worktree_path the root path of a worktree + # @param [String] working_tree_path the root path of a working_tree # - # @raise [RubyGit::Error] if worktree_path is not a directory + # @raise [RubyGit::Error] if working_tree_path is not a directory # - # @return [RubyGit::Worktree] the worktree whose root is at `path` + # @return [RubyGit::WorkingTree] the working_tree whose root is at `path` # - def self.init(worktree_path) - RubyGit::Worktree.init(worktree_path) + def self.init(working_tree_path) + RubyGit::WorkingTree.init(working_tree_path) end - # Open an existing Git worktree that contains worktree_path + # Open an existing Git working tree that contains working_tree_path # # @see https://git-scm.com/docs/git-open git-open # # @example - # worktree = Worktree.open(worktree_path) + # working_tree = WorkingTree.open(working_tree_path) # - # @param [String] worktree_path the root path of a worktree + # @param [String] working_tree_path the root path of a working_tree # - # @raise [RubyGit::Error] if `worktree_path` does not exist, is not a directory, or is not within a Git worktree. + # @raise [RubyGit::Error] if `working_tree_path` does not exist, is not a directory, or is not within + # a Git working_tree. # - # @return [RubyGit::Worktree] the worktree that contains `worktree_path` + # @return [RubyGit::WorkingTree] the working_tree that contains `working_tree_path` # - def self.open(worktree_path) - RubyGit::Worktree.open(worktree_path) + def self.open(working_tree_path) + RubyGit::WorkingTree.open(working_tree_path) end # Copy the remote repository and checkout the default branch # # Clones the repository referred to by `repository_url` into a newly created # directory, creates remote-tracking branches for each branch in the cloned repository, - # and checks out the default branch in the worktree whose root directory is `to_path`. + # and checks out the default branch in the working_tree whose root directory is `to_path`. # # @see https://git-scm.com/docs/git-clone git-clone # - # @example Using default for Worktree path + # @example Using default for WorkingTree path # FileUtils.pwd # => "/Users/jsmith" - # worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git') - # worktree.path + # working_tree = WorkingTree.clone('https://github.com/main-branch/ruby_git.git') + # working_tree.path # => "/Users/jsmith/ruby_git" # - # @example Using a specified worktree_path + # @example Using a specified working_tree_path # FileUtils.pwd # => "/Users/jsmith" - # worktree_path = '/tmp/project' - # worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git', to_path: worktree_path) - # worktree.path + # working_tree_path = '/tmp/project' + # working_tree = WorkingTree.clone('https://github.com/main-branch/ruby_git.git', to_path: working_tree_path) + # working_tree.path # => "/tmp/project" # # @param [String] repository_url a reference to a Git repository # - # @param [String] to_path where to put the checked out worktree once the repository is cloned + # @param [String] to_path where to put the checked out working tree once the repository is cloned # # `to_path` will be created if it does not exist. An error is raised if `to_path` exists and # not an empty directory. @@ -124,9 +137,9 @@ def self.open(worktree_path) # @raise [RubyGit::Error] if (1) `repository_url` is not valid or does not point to a valid repository OR # (2) `to_path` is not an empty directory. # - # @return [RubyGit::Worktree] the worktree checked out from the cloned repository + # @return [RubyGit::WorkingTree] the working tree checked out from the cloned repository # def self.clone(repository_url, to_path: '') - RubyGit::Worktree.clone(repository_url, to_path: to_path) + RubyGit::WorkingTree.clone(repository_url, to_path: to_path) end end diff --git a/lib/ruby_git/working_tree.rb b/lib/ruby_git/working_tree.rb new file mode 100644 index 0000000..b0a34dd --- /dev/null +++ b/lib/ruby_git/working_tree.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require 'open3' + +module RubyGit + # The working tree is a directory tree consisting of the checked out files that + # you are currently working on. + # + # Create a new WorkingTree using {.init}, {.clone}, or {.open}. + # + class WorkingTree + # The root path of the working tree + # + # @example + # working_tree_path = '/Users/James/myproject' + # working_tree = WorkingTree.open(working_tree_path) + # working_tree.path + # => '/Users/James/myproject' + # + # @return [Pathname] the root path of the working_tree + # + attr_reader :path + + # Create an empty Git repository under the root working tree `path` + # + # If the repository already exists, it will not be overwritten. + # + # @see https://git-scm.com/docs/git-init git-init + # + # @example + # working_tree = WorkingTree.init(working_tree_path) + # + # @param [String] working_tree_path the root path of a Git working tree + # + # @raise [RubyGit::Error] if working_tree_path is not a directory + # + # @return [RubyGit::WorkingTree] the working tree whose root is at `path` + # + def self.init(working_tree_path) + raise RubyGit::Error, "Path '#{working_tree_path}' not valid." unless File.directory?(working_tree_path) + + command = [RubyGit.git.path.to_s, 'init'] + _out, err, status = Open3.capture3(*command, chdir: working_tree_path) + raise RubyGit::Error, err unless status.success? + + WorkingTree.new(working_tree_path) + end + + # Open an existing Git working tree that contains working_tree_path + # + # @see https://git-scm.com/docs/git-open git-open + # + # @example + # working_tree = WorkingTree.open(working_tree_path) + # + # @param [String] working_tree_path the root path of a Git working tree + # + # @raise [RubyGit::Error] if `working_tree_path` does not exist, is not a directory, or is not within + # a Git working tree. + # + # @return [RubyGit::WorkingTree] the Git working tree that contains `working_tree_path` + # + def self.open(working_tree_path) + new(working_tree_path) + end + + # Copy the remote repository and checkout the default branch + # + # Clones the repository referred to by `repository_url` into a newly created + # directory, creates remote-tracking branches for each branch in the cloned repository, + # and checks out the default branch in the Git working tree whose root directory is `to_path`. + # + # @see https://git-scm.com/docs/git-clone git-clone + # + # @example Using default for WorkingTree path + # FileUtils.pwd + # => "/Users/jsmith" + # working_tree = WorkingTree.clone('https://github.com/main-branch/ruby_git.git') + # working_tree.path + # => "/Users/jsmith/ruby_git" + # + # @example Using a specified working_tree_path + # FileUtils.pwd + # => "/Users/jsmith" + # working_tree_path = '/tmp/project' + # working_tree = WorkingTree.clone('https://github.com/main-branch/ruby_git.git', to_path: working_tree_path) + # working_tree.path + # => "/tmp/project" + # + # @param [String] repository_url a reference to a Git repository + # + # @param [String] to_path where to put the checked out Git working tree once the repository is cloned + # + # `to_path` will be created if it does not exist. An error is raised if `to_path` exists and + # not an empty directory. + # + # @raise [RubyGit::Error] if (1) `repository_url` is not valid or does not point to a valid repository OR + # (2) `to_path` is not an empty directory. + # + # @return [RubyGit::WorkingTree] the Git working tree checked out from the cloned repository + # + def self.clone(repository_url, to_path: '') + command = [RubyGit.git.path.to_s, 'clone', '--', repository_url, to_path] + _out, err, status = Open3.capture3(*command) + raise RubyGit::Error, err unless status.success? + + new(to_path) + end + + private + + # Create a WorkingTree object + # @api private + # + def initialize(working_tree_path) + raise RubyGit::Error, "Path '#{working_tree_path}' not valid." unless File.directory?(working_tree_path) + + @path = root_path(working_tree_path) + RubyGit.logger.debug("Created #{inspect}") + end + + # Find the root path of a Git working tree containing `path` + # + # @raise [RubyGit::Error] if the path is not in a Git working tree + # + # @return [String] the root path of the Git working tree containing `path` + # + # @api private + # + def root_path(working_tree_path) + command = [RubyGit.git.path.to_s, 'rev-parse', '--show-toplevel'] + out, err, status = Open3.capture3(*command, chdir: working_tree_path) + raise RubyGit::Error, err unless status.success? + + out.chomp + end + end +end diff --git a/lib/ruby_git/worktree.rb b/lib/ruby_git/worktree.rb deleted file mode 100644 index 6aaa0e9..0000000 --- a/lib/ruby_git/worktree.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -require 'open3' - -module RubyGit - # The Worktree is a directory tree consisting of the checked out files that - # you are currently working on. - # - # Create a new Worktree using {.init}, {.clone}, or {.open}. - # - class Worktree - # The root path of the worktree - # - # @example - # worktree_path = '/Users/James/myproject' - # worktree = Worktree.open(worktree_path) - # worktree.path - # => '/Users/James/myproject' - # - # @return [Pathname] the root path of the worktree - # - attr_reader :path - - # Create an empty Git repository under the root worktree `path` - # - # If the repository already exists, it will not be overwritten. - # - # @see https://git-scm.com/docs/git-init git-init - # - # @example - # worktree = Worktree.init(worktree_path) - # - # @param [String] worktree_path the root path of a worktree - # - # @raise [RubyGit::Error] if worktree_path is not a directory - # - # @return [RubyGit::Worktree] the worktree whose root is at `path` - # - def self.init(worktree_path) - raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path) - - command = [RubyGit.git.path.to_s, 'init'] - _out, err, status = Open3.capture3(*command, chdir: worktree_path) - raise RubyGit::Error, err unless status.success? - - Worktree.new(worktree_path) - end - - # Open an existing Git worktree that contains worktree_path - # - # @see https://git-scm.com/docs/git-open git-open - # - # @example - # worktree = Worktree.open(worktree_path) - # - # @param [String] worktree_path the root path of a worktree - # - # @raise [RubyGit::Error] if `worktree_path` does not exist, is not a directory, or is not within a Git worktree. - # - # @return [RubyGit::Worktree] the worktree that contains `worktree_path` - # - def self.open(worktree_path) - new(worktree_path) - end - - # Copy the remote repository and checkout the default branch - # - # Clones the repository referred to by `repository_url` into a newly created - # directory, creates remote-tracking branches for each branch in the cloned repository, - # and checks out the default branch in the worktree whose root directory is `to_path`. - # - # @see https://git-scm.com/docs/git-clone git-clone - # - # @example Using default for Worktree path - # FileUtils.pwd - # => "/Users/jsmith" - # worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git') - # worktree.path - # => "/Users/jsmith/ruby_git" - # - # @example Using a specified worktree_path - # FileUtils.pwd - # => "/Users/jsmith" - # worktree_path = '/tmp/project' - # worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git', to_path: worktree_path) - # worktree.path - # => "/tmp/project" - # - # @param [String] repository_url a reference to a Git repository - # - # @param [String] to_path where to put the checked out worktree once the repository is cloned - # - # `to_path` will be created if it does not exist. An error is raised if `to_path` exists and - # not an empty directory. - # - # @raise [RubyGit::Error] if (1) `repository_url` is not valid or does not point to a valid repository OR - # (2) `to_path` is not an empty directory. - # - # @return [RubyGit::Worktree] the worktree checked out from the cloned repository - # - def self.clone(repository_url, to_path: '') - command = [RubyGit.git.path.to_s, 'clone', '--', repository_url, to_path] - _out, err, status = Open3.capture3(*command) - raise RubyGit::Error, err unless status.success? - - new(to_path) - end - - private - - # Create a Worktree object - # @api private - # - def initialize(worktree_path) - raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path) - - @path = root_path(worktree_path) - RubyGit.logger.debug("Created #{inspect}") - end - - # Find the root path of a worktree containing `path` - # - # @raise [RubyGit::Error] if the path is not in a worktree - # - # @return [String] the root path of the worktree containing `path` - # - # @api private - # - def root_path(worktree_path) - command = [RubyGit.git.path.to_s, 'rev-parse', '--show-toplevel'] - out, err, status = Open3.capture3(*command, chdir: worktree_path) - raise RubyGit::Error, err unless status.success? - - out.chomp - end - end -end diff --git a/ruby_git.gemspec b/ruby_git.gemspec index f58a7d6..819d3bf 100644 --- a/ruby_git.gemspec +++ b/ruby_git.gemspec @@ -9,11 +9,11 @@ Gem::Specification.new do |spec| spec.email = ['jcouball@yahoo.com'] spec.license = 'MIT' - spec.summary = 'A Ruby library to work with Git Worktrees and Respositories' + spec.summary = 'A Ruby library to work with Git Respositories' spec.description = <<~DESCRIPTION THIS PROJECT IS A WORK IN PROGRESS AND IS NOT USEFUL IN ITS CURRENT STATE - An object-oriented interface to working with Git Worktrees and Repositories that + An object-oriented interface to working with Git Repositories that tries to make sense out of the Git command line. DESCRIPTION spec.homepage = 'https://github.com/main-branch/ruby_git/' diff --git a/spec/lib/ruby_git/worktree_clone_spec.rb b/spec/lib/ruby_git/working_tree_clone_spec.rb similarity index 66% rename from spec/lib/ruby_git/worktree_clone_spec.rb rename to spec/lib/ruby_git/working_tree_clone_spec.rb index f984a42..501290e 100644 --- a/spec/lib/ruby_git/worktree_clone_spec.rb +++ b/spec/lib/ruby_git/working_tree_clone_spec.rb @@ -15,9 +15,9 @@ def make_bare_repository(repository_path) repository_path end -RSpec.describe RubyGit::Worktree do - describe '.clone(url, to_path: worktree_path)' do - subject { described_class.clone(repository_url, to_path: worktree_path) } +RSpec.describe RubyGit::WorkingTree do + describe '.clone(url, to_path: working_tree_path)' do + subject { described_class.clone(repository_url, to_path: working_tree_path) } let(:tmpdir) { Dir.mktmpdir } let(:repository_url) { make_bare_repository(Dir.mktmpdir) } after do @@ -27,36 +27,36 @@ def make_bare_repository(repository_path) context 'the url is not valid' do before { FileUtils.rm_rf(repository_url) } - let(:worktree_path) { tmpdir } + let(:working_tree_path) { tmpdir } it 'should raise RubyGit::Error' do expect { subject }.to raise_error(RubyGit::Error, /does not exist/) end end context 'the url is valid' do - let(:worktree_path) { tmpdir } + let(:working_tree_path) { tmpdir } - context 'and worktree_path exists' do + context 'and working_tree_path exists' do context 'and is not an empty directory' do - before { FileUtils.touch(File.join(worktree_path, 'README.md')) } + before { FileUtils.touch(File.join(working_tree_path, 'README.md')) } it 'should raise RubyGit::Error' do expect { subject }.to raise_error(RubyGit::Error, /not an empty directory/) end end context 'and is an empty directory' do - it 'should return a Worktree object' do - expect(subject).to be_kind_of(RubyGit::Worktree) - expect(subject).to have_attributes(path: File.realpath(worktree_path)) + it 'should return a WorkingTree object' do + expect(subject).to be_kind_of(RubyGit::WorkingTree) + expect(subject).to have_attributes(path: File.realpath(working_tree_path)) end end end - context 'and worktree_path does not exist' do - before { FileUtils.rmdir(worktree_path) } - it 'should create the worktree path and return a Worktree object' do - expect(subject).to be_kind_of(RubyGit::Worktree) - expect(Dir.exist?(worktree_path)).to eq(true) - expect(subject).to have_attributes(path: File.realpath(worktree_path)) + context 'and working_tree_path does not exist' do + before { FileUtils.rmdir(working_tree_path) } + it 'should create the working tree path and return a WorkingTree object' do + expect(subject).to be_kind_of(RubyGit::WorkingTree) + expect(Dir.exist?(working_tree_path)).to eq(true) + expect(subject).to have_attributes(path: File.realpath(working_tree_path)) end end end diff --git a/spec/lib/ruby_git/working_tree_init_spec.rb b/spec/lib/ruby_git/working_tree_init_spec.rb new file mode 100644 index 0000000..b6e5348 --- /dev/null +++ b/spec/lib/ruby_git/working_tree_init_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'tmpdir' + +RSpec.describe RubyGit::WorkingTree do + describe '.init(working_tree_path)' do + subject { described_class.init(working_tree_path) } + let(:tmpdir) { Dir.mktmpdir } + after { FileUtils.rm_rf(tmpdir) if File.exist?(tmpdir) } + + context 'when working_tree_path does not exist' do + let(:working_tree_path) { tmpdir } + before { FileUtils.rmdir(tmpdir) } + it 'should raise a RubyGit::Error' do + expect { subject }.to raise_error(RubyGit::Error) + end + end + + context 'when working_tree_path exists' do + let(:working_tree_path) { tmpdir } + context 'and is not a directory' do + before do + FileUtils.rmdir(working_tree_path) + FileUtils.touch(working_tree_path) + end + it 'should raise RubyGit::Error' do + expect { subject }.to raise_error(RubyGit::Error) + end + end + + context 'and is a directory' do + context 'and is in a working tree' do + before do + raise RuntimeError unless system('git init', chdir: working_tree_path, %i[out err] => IO::NULL) + end + it 'should return a WorkingTree object to the existing working tree' do + expect(subject).to be_kind_of(RubyGit::WorkingTree) + expect(subject).to have_attributes(path: File.realpath(working_tree_path)) + end + end + + context 'and is not in the working tree' do + it 'should initialize the working tree and return a WorkingTree object' do + expect(subject).to be_kind_of(RubyGit::WorkingTree) + expect(subject).to have_attributes(path: File.realpath(working_tree_path)) + end + end + end + end + end +end diff --git a/spec/lib/ruby_git/worktree_initialize_spec.rb b/spec/lib/ruby_git/working_tree_initialize_spec.rb similarity index 60% rename from spec/lib/ruby_git/worktree_initialize_spec.rb rename to spec/lib/ruby_git/working_tree_initialize_spec.rb index 20f33ad..c4de1dd 100644 --- a/spec/lib/ruby_git/worktree_initialize_spec.rb +++ b/spec/lib/ruby_git/working_tree_initialize_spec.rb @@ -4,24 +4,24 @@ require 'stringio' require 'tmpdir' -RSpec.describe RubyGit::Worktree do +RSpec.describe RubyGit::WorkingTree do describe '.initialize' do - subject { described_class.open(worktree_path) } + subject { described_class.open(working_tree_path) } let(:tmpdir) { Dir.mktmpdir } after { FileUtils.rm_rf(tmpdir) if File.exist?(tmpdir) } - context 'with a valid worktree path' do - let(:worktree_path) { tmpdir } + context 'with a valid working tree path' do + let(:working_tree_path) { tmpdir } before do - raise RuntimeError unless system('git init', chdir: worktree_path, %i[out err] => IO::NULL) + raise RuntimeError unless system('git init', chdir: working_tree_path, %i[out err] => IO::NULL) end - it 'should log that a Worktree object was created at debug level' do + it 'should log that a WorkingTree object was created at debug level' do log_device = StringIO.new saved_logger = RubyGit.logger RubyGit.logger = Logger.new(log_device, level: Logger::DEBUG) - RubyGit::Worktree.new(worktree_path) + RubyGit::WorkingTree.new(working_tree_path) RubyGit.logger = saved_logger - expect(log_device.string).to include(' : Created # IO::NULL) + end + it 'should return a WorkingTree object whose path is the root of the working tree' do + expect(subject).to be_kind_of(RubyGit::WorkingTree) + expect(subject).to have_attributes(path: File.realpath(working_tree_path)) + end + end + + context 'when working_tree_path is a working tree path not at the root of the working tree' do + let(:root_working_tree_path) { tmpdir } + let(:working_tree_path) { File.join(root_working_tree_path, 'subdir') } + before do + raise RuntimeError unless system('git init', chdir: root_working_tree_path, %i[out err] => IO::NULL) + + FileUtils.mkdir(working_tree_path) + end + it 'should return a WorkingTree object whose path is the root of the working_tree' do + expect(subject).to have_attributes(path: File.realpath(root_working_tree_path)) + end + end + end +end diff --git a/spec/lib/ruby_git/worktree_init_spec.rb b/spec/lib/ruby_git/worktree_init_spec.rb deleted file mode 100644 index 470f8ad..0000000 --- a/spec/lib/ruby_git/worktree_init_spec.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -require 'tmpdir' - -RSpec.describe RubyGit::Worktree do - describe '.init(worktree_path)' do - subject { described_class.init(worktree_path) } - let(:tmpdir) { Dir.mktmpdir } - after { FileUtils.rm_rf(tmpdir) if File.exist?(tmpdir) } - - context 'when worktree_path does not exist' do - let(:worktree_path) { tmpdir } - before { FileUtils.rmdir(tmpdir) } - it 'should raise a RubyGit::Error' do - expect { subject }.to raise_error(RubyGit::Error) - end - end - - context 'when worktree_path exists' do - let(:worktree_path) { tmpdir } - context 'and is not a directory' do - before do - FileUtils.rmdir(worktree_path) - FileUtils.touch(worktree_path) - end - it 'should raise RubyGit::Error' do - expect { subject }.to raise_error(RubyGit::Error) - end - end - - context 'and is a directory' do - context 'and is in a worktree' do - before do - raise RuntimeError unless system('git init', chdir: worktree_path, %i[out err] => IO::NULL) - end - it 'should return a Worktree object to the existing worktree ' do - expect(subject).to be_kind_of(RubyGit::Worktree) - expect(subject).to have_attributes(path: File.realpath(worktree_path)) - end - end - - context 'and is not in a worktree' do - it 'should initialize the worktree and return a Worktree object' do - expect(subject).to be_kind_of(RubyGit::Worktree) - expect(subject).to have_attributes(path: File.realpath(worktree_path)) - end - end - end - end - end -end diff --git a/spec/lib/ruby_git/worktree_open_spec.rb b/spec/lib/ruby_git/worktree_open_spec.rb deleted file mode 100644 index db69239..0000000 --- a/spec/lib/ruby_git/worktree_open_spec.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true - -require 'tmpdir' - -RSpec.describe RubyGit::Worktree do - describe '.open' do - subject { described_class.open(worktree_path) } - let(:tmpdir) { Dir.mktmpdir } - after { FileUtils.rm_rf(tmpdir) if File.exist?(tmpdir) } - - context 'when worktree_path does not exist' do - let(:worktree_path) { tmpdir } - before { FileUtils.rmdir(worktree_path) } - it 'should raise RubyGit::Error' do - expect { subject }.to raise_error(RubyGit::Error) - end - end - - context 'when worktree_path is not a directory' do - let(:worktree_path) { tmpdir } - before do - FileUtils.rmdir(worktree_path) - FileUtils.touch(worktree_path) - end - it 'should raise RubyGit::Error' do - expect { subject }.to raise_error(RubyGit::Error) - end - end - - context 'when worktree_path exists but is not a git worktree' do - let(:worktree_path) { tmpdir } - it 'should raise RubyGit::Error' do - expect { subject }.to raise_error(RubyGit::Error, /not a git repository/) - end - end - - context 'when worktree_path is a worktree path at the root of the worktree' do - let(:worktree_path) { tmpdir } - before do - raise RuntimeError unless system('git init', chdir: worktree_path, %i[out err] => IO::NULL) - end - it 'should return a Worktree object whose path is the root of the worktree' do - expect(subject).to be_kind_of(RubyGit::Worktree) - expect(subject).to have_attributes(path: File.realpath(worktree_path)) - end - end - - context 'when worktree_path is a worktree path not at the root of the worktree' do - let(:root_worktree_path) { tmpdir } - let(:worktree_path) { File.join(root_worktree_path, 'subdir') } - before do - raise RuntimeError unless system('git init', chdir: root_worktree_path, %i[out err] => IO::NULL) - - FileUtils.mkdir(worktree_path) - end - it 'should return a Worktree object whose path is the root of the worktree' do - expect(subject).to have_attributes(path: File.realpath(root_worktree_path)) - end - end - end -end diff --git a/spec/lib/ruby_git_spec.rb b/spec/lib/ruby_git_spec.rb index ab872a4..2c4d1a2 100644 --- a/spec/lib/ruby_git_spec.rb +++ b/spec/lib/ruby_git_spec.rb @@ -28,12 +28,12 @@ end describe '.init' do - let(:worktree_path) { '/Users/jsmith/my_project' } - subject { RubyGit.init(worktree_path) } - it 'should call RubyGit::Worktree.init with the same arguments' do - worktree_class = class_double('RubyGit::Worktree') - stub_const('RubyGit::Worktree', worktree_class) - expect(worktree_class).to receive(:init).with(worktree_path) + let(:working_tree_path) { '/Users/jsmith/my_project' } + subject { RubyGit.init(working_tree_path) } + it 'should call RubyGit::WorkingTree.init with the same arguments' do + working_tree_class = class_double('RubyGit::WorkingTree') + stub_const('RubyGit::WorkingTree', working_tree_class) + expect(working_tree_class).to receive(:init).with(working_tree_path) subject end end @@ -41,21 +41,21 @@ describe '.clone' do let(:repository_url) { 'https://github.com/main-branch/ruby_git.git' } subject { RubyGit.clone(repository_url) } - it 'should call RubyGit::Worktree.clone with the same arguments' do - worktree_class = class_double('RubyGit::Worktree') - stub_const('RubyGit::Worktree', worktree_class) - expect(worktree_class).to receive(:clone).with(repository_url, to_path: '') + it 'should call RubyGit::WorkingTree.clone with the same arguments' do + working_tree_class = class_double('RubyGit::WorkingTree') + stub_const('RubyGit::WorkingTree', working_tree_class) + expect(working_tree_class).to receive(:clone).with(repository_url, to_path: '') subject end end describe '.open' do - let(:worktree_path) { '/Users/jsmith/my_project' } - subject { RubyGit.open(worktree_path) } - it 'should call RubyGit::Worktree.open with the same arguments' do - worktree_class = class_double('RubyGit::Worktree') - stub_const('RubyGit::Worktree', worktree_class) - expect(worktree_class).to receive(:open).with(worktree_path) + let(:working_tree_path) { '/Users/jsmith/my_project' } + subject { RubyGit.open(working_tree_path) } + it 'should call RubyGit::WorkingTree.open with the same arguments' do + working_tree_class = class_double('RubyGit::WorkingTree') + stub_const('RubyGit::WorkingTree', working_tree_class) + expect(working_tree_class).to receive(:open).with(working_tree_path) subject end end