Convert bare repos to working copy repos and vice-versa
authorBruce Williams <bruce@codefluency.com>
Sat, 13 Mar 2010 08:00:11 +0000 (00:00 -0800)
committerBruce Williams <bruce@codefluency.com>
Sat, 13 Mar 2010 08:00:11 +0000 (00:00 -0800)
Modulefile [new file with mode: 0644]
README.GIT.markdown [new file with mode: 0644]
README.SVN.markdown [new file with mode: 0644]
README.markdown [new file with mode: 0644]
lib/puppet/provider/vcsrepo/git.rb
lib/puppet/type/vcsrepo.rb
spec/unit/puppet/provider/vcsrepo/git_spec.rb
spec/unit/puppet/provider/vcsrepo/svn_spec.rb

diff --git a/Modulefile b/Modulefile
new file mode 100644 (file)
index 0000000..4cb86b0
--- /dev/null
@@ -0,0 +1,2 @@
+name 'puppetlabs/vcsrepo'
+version '0.0.1'
diff --git a/README.GIT.markdown b/README.GIT.markdown
new file mode 100644 (file)
index 0000000..08545aa
--- /dev/null
@@ -0,0 +1,20 @@
+Using vcsrepo with Git
+======================
+
+To create a blank repository
+----------------------------
+
+Define a `vcsrepo` without a `source` or `revision`:
+
+    vcsrepo { "/path/to/repo":
+      ensure: present
+    }
+
+If you're defining this for a central/"official" repository, you'll
+probably want to make it a "bare" repository.  Do this by setting
+`ensure` to `bare` instead of `present`:
+
+    vcsrepo { "/path/to/repo":
+        ensure: bare
+    }
+
diff --git a/README.SVN.markdown b/README.SVN.markdown
new file mode 100644 (file)
index 0000000..30aaf54
--- /dev/null
@@ -0,0 +1,2 @@
+Using vcsrepo with Subversion
+=============================
diff --git a/README.markdown b/README.markdown
new file mode 100644 (file)
index 0000000..33fddcb
--- /dev/null
@@ -0,0 +1,30 @@
+vcsrepo
+=======
+
+Purpose
+-------
+
+This provides a single type, `vcsrepo`.
+
+This type can be used to describe:
+
+* A working copy checked out from a (remote or local) source, at an
+  arbitrary revision
+* A "blank" working copy not associated with a source (when it makes
+  sense for the VCS being used) 
+* A "blank" central repository (when the distinction makes sense for the VCS
+  being used) 
+
+Supported Version Control Systems
+---------------------------------
+
+This module supports a wide range of VCS types, each represented by a
+separate provider.
+
+For information on how to use this module with a specific VCS, see
+`README.<VCS>.markdown`.
+
+License
+-------
+
+See LICENSE.
index 670d8de..bdc819e 100644 (file)
@@ -1,3 +1,6 @@
+require 'tmpdir'
+require 'digest/md5'
+
 Puppet::Type.type(:vcsrepo).provide(:git) do
   desc "Supports Git repositories"
 
@@ -13,7 +16,14 @@ Puppet::Type.type(:vcsrepo).provide(:git) do
   end
 
   def exists?
-    File.directory?(@resource.value(:path))
+    case @resource.value(:ensure)
+    when 'present'
+      working_copy_exists?
+    when 'bare'
+      bare_exists?
+    else
+      path_exists?
+    end
   end
 
   def destroy
@@ -37,6 +47,22 @@ Puppet::Type.type(:vcsrepo).provide(:git) do
 
   private
 
+  def bare_exists?
+    bare_git_config_exists? && !working_copy_exists?
+  end
+
+  def working_copy_exists?
+    File.directory?(File.join(@resource.value(:path), '.git'))
+  end
+  
+  def path_exists?
+    File.directory?(@resource.value(:path))
+  end
+
+  def bare_git_config_exists?
+    File.exist?(File.join(@resource.value(:path), 'config'))
+  end
+  
   def clone_repository(source, path)
     git('clone', source, path)
   end
@@ -48,9 +74,51 @@ Puppet::Type.type(:vcsrepo).provide(:git) do
   end
 
   def init_repository(path)
-    FileUtils.mkdir_p(path)
+    if @resource.value(:ensure) == 'bare' && working_copy_exists?
+      convert_working_copy_to_bare
+    elsif @resource.value(:ensure) == 'present' && bare_exists?
+      convert_bare_to_working_copy
+    elsif File.directory?(@resource.value(:path))
+      raise Puppet::Error, "Could not create repository (non-repository at path)"
+    else
+      normal_init
+    end
+  end
+
+  # Convert working copy to bare
+  #
+  # Moves:
+  #   <path>/.git
+  # to:
+  #   <path>/
+  def convert_working_copy_to_bare
+    FileUtils.mv(File.join(@resource.value(:path), '.git'), tempdir)
+    FileUtils.rm_rf(@resource.value(:path))
+    FileUtils.cp_r(tempdir, @resource.value(:path))
+  end
+
+  # Convert bare to working copy
+  #
+  # Moves:
+  #   <path>/
+  # to:
+  #   <path>/.git
+  def convert_bare_to_working_copy
+    FileUtils.mv(@resource.value(:path), tempdir)
+    FileUtils.mkdir(@resource.value(:path))
+    FileUtils.cp_r(tempdir, File.join(@resource.value(:path), '.git'))
+    reset('HEAD')
+    git('checkout', '-f')
+  end
+
+  def normal_init
+    FileUtils.mkdir(@resource.value(:path))
+    args = ['init']
+    if @resource.value(:ensure) == 'bare'
+      args << '--bare'
+    end
     at_path do
-      git('init')
+      git(*args)
     end
   end
 
@@ -70,4 +138,8 @@ Puppet::Type.type(:vcsrepo).provide(:git) do
     value
   end
 
+  def tempdir
+    @tempdir ||= File.join(Dir.tmpdir, 'vcsrepo-' + Digest::MD5.hexdigest(@resource.value(:path)))
+  end
+
 end
index 04c48a3..4d5c2a0 100644 (file)
@@ -3,7 +3,13 @@ require 'pathname'
 Puppet::Type.newtype(:vcsrepo) do
   desc "A local version control repository"
 
-  ensurable
+  ensurable do
+    defaultvalues
+
+    newvalue :bare do
+      provider.create
+    end
+  end
 
   newparam(:path) do
     desc "Absolute path to repository"
index a5ed753..f8a7170 100644 (file)
@@ -34,12 +34,67 @@ describe provider_class do
       end
     end
     context "when a source is not given" do
-      it "should execute 'git init'" do
+      before do
         @resource.expects(:value).with(:path).returns(@path).at_least_once
         @resource.expects(:value).with(:source).returns(nil)
-        Dir.expects(:chdir).with(@path).yields
-        @provider.expects(:git).with('init')
-        @provider.create
+      end
+      context "when ensure = present" do
+        before { @resource.expects(:value).with(:ensure).returns('present').at_least_once }
+        context "when the path does not exist" do
+          it "should execute 'git init'" do
+            Dir.expects(:mkdir).with(@path)
+            Dir.expects(:chdir).with(@path).yields
+            @provider.expects(:bare_exists?).returns(false)
+            File.expects(:directory?).with(@path).returns(false)
+            @provider.expects(:git).with('init')
+            @provider.create
+          end
+        end
+        context "when the path is a bare repository" do
+          it "should convert it to a working copy" do
+            @provider.expects(:bare_exists?).returns(true)
+            @provider.expects(:convert_bare_to_working_copy)
+            @provider.create
+          end
+        end
+        context "when the path is not a repository" do
+          it "should raise an exception" do
+            File.expects(:directory?).with(@path).returns(true)
+            @provider.expects(:bare_exists?).returns(false)
+            proc {
+              @provider.create
+            }.should raise_error(Puppet::Error)
+          end
+        end
+      end
+      context "when ensure = bare" do
+        before { @resource.expects(:value).with(:ensure).returns('bare').at_least_once } 
+        context "when the path does not exist" do
+          it "should execute 'git init --bare'" do
+            Dir.expects(:chdir).with(@path).yields
+            File.expects(:directory?).with(@path).returns(false)
+            FileUtils.expects(:mkdir).with(@path)
+            @provider.expects(:working_copy_exists?).returns(false)
+            @provider.expects(:git).with('init', '--bare')
+            @provider.create
+          end
+        end
+        context "when the path is a working copy repository" do
+          it "should convert it to a bare repository" do
+            @provider.expects(:working_copy_exists?).returns(true)
+            @provider.expects(:convert_working_copy_to_bare)
+            @provider.create
+          end
+        end
+        context "when the path is not a repository" do
+          it "should raise an exception" do
+            File.expects(:directory?).with(@path).returns(true)
+            @provider.expects(:working_copy_exists?).returns(false)
+            proc {
+              @provider.create
+            }.should raise_error(Puppet::Error)
+          end
+        end
       end
     end
   end
@@ -53,10 +108,54 @@ describe provider_class do
   end
 
   describe "when checking existence" do
-    it "should check for the directory" do
-      @resource.expects(:value).with(:path).returns(@path)
-      File.expects(:directory?).with(@path)
-      @provider.exists?
+    context "when ensure = present" do
+      context "when a working copy exists" do
+        it "should be true" do
+          @resource.expects(:value).with(:ensure).returns('present').at_least_once
+          @provider.expects(:working_copy_exists?).returns(true)
+          @provider.should be_exists
+        end
+      end
+      context "when a bare repo exists" do
+        it "should be " do
+          @resource.expects(:value).with(:ensure).returns('present').at_least_once
+          @provider.expects(:working_copy_exists?).returns(false)
+          @provider.should_not be_exists
+        end
+      end
+    end
+    context "when ensure = bare" do
+      context "when a working copy exists" do
+        it "should be false" do
+          @resource.expects(:value).with(:ensure).returns('bare').at_least_once
+          @provider.expects(:bare_exists?).returns(false)          
+          @provider.should_not be_exists
+        end
+      end
+      context "when a bare repo exists" do
+        it "should be true" do
+          @resource.expects(:value).with(:ensure).returns('bare').at_least_once
+          @provider.expects(:bare_exists?).returns(true)
+          @provider.should be_exists
+        end
+      end
+    end
+    context "when ensure = absent" do
+      before { @resource.expects(:value).with(:ensure).returns('absent') }
+      context "when the path exists" do
+        it "should be true" do
+          @resource.expects(:value).with(:path).returns(@path)
+          File.expects(:directory?).with(@path).returns(true)
+          @provider.should be_exists
+        end
+      end
+      context "when the path does not exist" do
+        it "should be false" do
+          @resource.expects(:value).with(:path).returns(@path)
+          File.expects(:directory?).with(@path).returns(false)
+          @provider.should_not be_exists
+        end
+      end
     end
   end
 
@@ -106,7 +205,7 @@ describe provider_class do
       end
     end
   end
-  
+
   describe "when setting the revision property" do
     it "should use 'git fetch' and 'git reset'" do
       @resource.expects(:value).with(:path).returns(@path).at_least_once
index 3af06f2..fc5c37b 100644 (file)
@@ -10,7 +10,7 @@ describe provider_class do
     @path = '/tmp/vcsrepo'
   end
 
-  context 'when creating' do
+  describe 'when creating' do
     context "when a source is given" do
       context "and when a revision is given" do
         it "should execute 'svn checkout' with a revision" do
@@ -53,7 +53,7 @@ describe provider_class do
     end
   end
 
-  context 'when destroying' do
+  describe 'when destroying' do
     it "it should remove the directory" do
       @resource.expects(:value).with(:path).returns(@path).at_least_once
       FileUtils.expects(:rm_rf).with(@path)
@@ -61,7 +61,7 @@ describe provider_class do
     end
   end
 
-  context "when checking existence" do
+  describe "when checking existence" do
     it "should check for the directory" do
       @resource.expects(:value).with(:path).returns(@path)
       File.expects(:directory?).with(@path)
@@ -69,23 +69,21 @@ describe provider_class do
     end
   end
 
-  describe "revision property" do
-    context "when checking" do
-      it "should use 'svn info'" do
-        @resource.expects(:value).with(:path).returns(@path)
-        p fixture(:svn_info)[/^Revision:\s+(\d+)/m, 1]
-        @provider.expects('svn').with('info').returns(fixture(:svn_info))
-        Dir.expects(:chdir).with(@path).yields
-        @provider.revision.should == '4'
-      end
+  describe "when checking the revision property" do
+    it "should use 'svn info'" do
+      @resource.expects(:value).with(:path).returns(@path)
+      @provider.expects('svn').with('info').returns(fixture(:svn_info))
+      Dir.expects(:chdir).with(@path).yields
+      @provider.revision.should == '4'
     end
-    context "when setting" do
-      it "should use 'svn update'" do
-        @resource.expects(:value).with(:path).returns(@path)
-        @provider.expects('svn').with('update', '-r', '30')
-        Dir.expects(:chdir).with(@path).yields
-        @provider.revision = '30'
-      end
+  end
+  
+  describe "when setting the revision property" do
+    it "should use 'svn update'" do
+      @resource.expects(:value).with(:path).returns(@path)
+      @provider.expects('svn').with('update', '-r', '30')
+      Dir.expects(:chdir).with(@path).yields
+      @provider.revision = '30'
     end
   end