Commits

Anonymous committed 84250b4

Added push/pull/fetch/clone, fixed bugs

Comments (0)

Files changed (4)

     >>> repo.git_init()
     >>> repo.git_add("file.txt") #already created but not added file
     >>> repo.git_commit("Adding file.txt", user="me <me@example.com>")
-    >>> str(repo['tip'].desc)
+    >>> str(repo['HEAD'].desc)
     'Adding file.txt'
 
 Installation
     import simplejson as json
 
 
+class GitException(Exception):
+    """Exception class allowing a exit_code parameter and member
+    to be used when calling Git to return exit code"""
+    def __init__(self, msg, exit_code=None):
+        super(GitException, self).__init__(msg)
+        self.exit_code = exit_code
+        
 class Revision(object):
     """A representation of a revision.
     Available fields are::
     """
     def __init__(self, json_log):
         """Create a Revision object from a JSON representation"""
-        print(json_log)
         rev = json.loads(json_log)
         
         for key in rev.keys():
         """Get a Revision object for the revision identifed by rev"""
         return self.revision(rev)
 
-    def git_command(self, *args):
-        """Run a git command in path and return the result.
-        Throws on error."""    
-
-        allargs = ["--git-dir", self.path+"/.git", "--work-tree", self.path] + list(args)
-        proc = Popen(["git"] + allargs, stdout=PIPE, stderr=PIPE)
-        print("Executed " + " ".join(allargs))
-
+    @classmethod
+    def command(cls, path, *args):
+        """Run a git command in path and return the result. Throws on error."""
+        if not path:
+            path = '.'
+        proc = Popen(["git"] + list(args), stdout=PIPE, stderr=PIPE, cwd=path)
+  
         out, err = [x.decode("utf-8") for x in  proc.communicate()]
 
         if proc.returncode:
-            cmd = "git " + " ".join(allargs)
-            raise Exception("Error running %s:\n\tErr: %s\n\tOut: %s\n\tExit: %s" 
-                            % (cmd,err,out,proc.returncode))
+            cmd = "git " + " ".join(args)
+            raise GitException("Error running %s:\n\tErr: %s\n\tOut: %s\n\tExit: %s" 
+                            % (cmd,err,out,proc.returncode), exit_code=proc.returncode)
         return out
+    def git_command(self, *args):
+        """Run a git command on this repo and return the result.
+        Throws on error."""    
+        return Repo.command(self.path, *args)   
 
     def git_init(self):
         """Initialize a new repo"""
         for change, path in [status_split.match(x.strip()).groups() for x in lines]:
             changes.setdefault(change, []).append(path)
         return changes
+
+    def git_push(self, destination=None):
+        """Push changes from this repo."""
+        if destination is None:
+            self.git_command("push")
+        else:
+            self.git_command("push", destination)
+
+    def git_pull(self, source=None):
+        """Pull changes to this repo."""
+        if source is None:
+            self.git_command("pull")
+        else:
+            self.git_command("pull", source)
+
+    def git_fetch(self, source=None):
+        """Fetch changes to this repo."""
+        if source is None:
+            self.git_command("fetch")
+        else:
+            self.git_command("fetch", source)
+
+    @classmethod
+    def git_clone(cls, url, path, *args):
+        """Clone repository at given `url` to `path`,
+        then return repo object to `path`."""
+        Repo.command(None, "clone", url, path, *args)
+        return Repo(path)
         
-    rev_log_tpl = '--pretty=format:{"node":"%h","author":"%an", "parents":"%p","date":"%ci","desc":"%f"}'
+    rev_log_tpl = '--pretty=format:{"node":"%h","author":"%an", "parents":"%p","date":"%ci","desc":"%s"}'
 
     def revision(self, identifier):
         """Get the identified revision as a Revision object"""
 
 class TestGitAPI(unittest.TestCase):
     """Tests for gitapi.py
-    Uses and wipes subfolder named 'test'
+    Uses and wipes subfolder named 'test' and 'test-clone'
     Tests are dependant on each other; named test_<number>_name for sorting
     """
     repo = gitapi.Repo("./test", user="Testuser <test@example.com>")
-    
+    clone = gitapi.Repo("./test-clone", user="Testuser <test@example.com>")
+    bareclone = gitapi.Repo("./test-clone-bare", user="Testuser <test@example.com>")
+
+
+    @classmethod
+    def _delete_and_create(cls, path):
+        if os.path.exists(path):
+            shutil.rmtree(path)
+        os.mkdir(path)
+        assert os.path.exists(path)
+
     @classmethod
     def setUpClass(cls):
-        #Patch Python 3
+        # patch for Python 3
         if hasattr(cls, "assertEqual"):
             setattr(cls, "assertEquals", cls.assertEqual)
             setattr(cls, "assertNotEquals", cls.assertNotEqual)
-        if os.path.exists("./test"):
-            shutil.rmtree("./test", onerror=onfserror)
-        os.mkdir("./test")
-        assert os.path.exists("./test")
+        TestGitAPI._delete_and_create("./test")
+        TestGitAPI._delete_and_create("./test-clone")
+        TestGitAPI._delete_and_create("./test-clone-bare")
+
 
     @classmethod
     def tearDownClass(self):
-        shutil.rmtree("test", onerror=onfserror)
+        shutil.rmtree("test", ignore_errors=True)
+        shutil.rmtree("test-clone", ignore_errors=True)
+        shutil.rmtree("test-clone-bare", ignore_errors=True)
 
     def test_005_Init(self):
         self.repo.git_init()
         self.assertTrue(os.path.exists("test/.git"))
 
-    #def test_040_Identity(self):
-
-#        gitid = self.repo.git_id()
-
-#       self.assertEquals("000000000000", gitid)
 
     def test_020_Add(self):
         with open("test/file.txt", "w") as out:
         with open("test/file3.txt", "r") as src:
             self.assertEqual(src.read(), "this is more stuff")
 
-        
+    def test_300_clone(self):
+        # clone test to test clone
+        self.clone = gitapi.Repo.git_clone("./test", "./test-clone")
+        self.assertTrue(isinstance(self.clone, gitapi.Repo))
+        self.assertEquals(self.clone.path, self.repo.path + "-clone")
+
+    def test_310_pull(self):
+        # add a new directory with some files in test repo first
+        os.mkdir("./test/cities")
+        with open("./test/cities/brussels.txt", "w") as out:
+            out.write("brussel")
+        with open("./test/cities/antwerp.txt", "w") as out:
+            out.write("antwerpen")
+        self.repo.git_add("cities")
+        message = "[TEST] Added two cities."
+        self.repo.git_commit(message)
+        self.clone.git_pull("../test")
+
+        self.assertEquals(self.clone.git_id(), self.repo.git_id())
+        # check summary of pulled tip
+        self.assertTrue(message in self.clone.git_log(identifier="HEAD"))
+
+    def test_320_push(self):
+        #Make a bare clone of test
+        gitapi.Repo.git_clone('test', 'test-clone-bare', '--bare')
+        # add another file in test-clone first
+        with open("./test-clone/cities/ghent.txt", "w") as out:
+            out.write("gent")
+        self.clone.git_add('cities')
+        message = "[CLONE] Added one file."
+        self.clone.git_commit(message)
+        self.clone.git_push("../test-clone-bare")
+
+        self.assertEquals(self.clone.git_id(), self.bareclone.git_id())
+        # check summary of pushed tip
+        self.assertTrue(message in self.bareclone.git_log(identifier="HEAD"))
+      
 
 def test_doc():
     #Prepare for doctest