[meld: 1/2] vc: Add a refactored CVS plugin (#170)



commit cc9d23f0fe9d0d542d425cf66209f27c75fceaab
Author: gitqlt <vcsqlt gmail com>
Date:   Sun Jul 21 07:54:35 2019 +0000

    vc: Add a refactored CVS plugin (#170)
    
    The new plugin is based on the existing Mercurial support.

 README.md           |   2 +-
 maint               |   2 +-
 meld/vc/__init__.py |   4 +-
 meld/vc/cvs.py      | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 164 insertions(+), 4 deletions(-)
---
diff --git a/README.md b/README.md
index 1ae0af55..8d0f6aed 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ About Meld
 Meld is a visual diff and merge tool targeted at developers. Meld helps you
 compare files, directories, and version controlled projects. It provides
 two- and three-way comparison of both files and directories, and supports
-many version control systems including Git, Mercurial, Bazaar and Subversion.
+many version control systems including Git, Mercurial, Bazaar, CVS and Subversion.
 
 Meld helps you review code changes, understand patches, and makes enormous
 merge conflicts slightly less painful.
diff --git a/maint b/maint
index 31ad25f0..72db0370 100755
--- a/maint
+++ b/maint
@@ -97,7 +97,7 @@ Meld is a visual diff and merge tool. It lets you compare two or three files,
 and updates the comparisons while you edit them in-place. You can also compare
 folders, launching comparisons of individual files as desired. Last but by no
 means least, Meld lets you work with your current changes in a wide variety of
-version control systems, including Git, Bazaar, Mercurial and Subversion.
+version control systems, including Git, Bazaar, Mercurial, CVS and Subversion.
 
 """
 
diff --git a/meld/vc/__init__.py b/meld/vc/__init__.py
index 07e7caab..43d35ca4 100644
--- a/meld/vc/__init__.py
+++ b/meld/vc/__init__.py
@@ -22,11 +22,11 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from . import bzr, darcs, git, mercurial, svn
+from . import bzr, cvs, darcs, git, mercurial, svn
 
 # Tuple of plugins, ordered according to best-guess as to which VC a
 # user is likely to want by default in a multiple-VC situation.
-VC_PLUGINS = (git, mercurial, bzr, svn, darcs)
+VC_PLUGINS = (git, mercurial, bzr, svn, darcs, cvs)
 
 
 def get_vcs(location):
diff --git a/meld/vc/cvs.py b/meld/vc/cvs.py
new file mode 100644
index 00000000..b4c3c96c
--- /dev/null
+++ b/meld/vc/cvs.py
@@ -0,0 +1,160 @@
+# -*- coding: utf-8 -*-
+# Copyright (C) 2002-2005 Stephen Kennedy <stevek gnome org>
+# Copyright (C) 2015 Kai Willadsen <kai willadsen gmail com>
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import errno
+import os
+import shutil
+
+from . import _vc
+
+
+class Vc(_vc.Vc):
+
+    # CVSNT is a drop-in replacement for CVS; if found, it is used instead
+    CMD = "cvsnt" if shutil.which("cvsnt") else "cvs"
+    NAME = "CVS"
+    VC_DIR = "CVS"
+    VC_ROOT_WALK = False
+
+    # According to the output of the 'status' command
+    state_map = {
+        "Unknown":          _vc.STATE_NONE,
+        "Locally Added":    _vc.STATE_NEW,
+        "Up-to-date":       _vc.STATE_NORMAL,
+        "!":                _vc.STATE_MISSING,
+        "I":                _vc.STATE_IGNORED,
+        "Locally Modified": _vc.STATE_MODIFIED,
+        "Locally Removed":  _vc.STATE_REMOVED
+    }
+
+    def commit(self, runner, files, message):
+        command = [self.CMD, 'commit', '-m', message]
+        runner(command, files, refresh=True, working_dir=self.root)
+
+    def update(self, runner):
+        command = [self.CMD, 'update']
+        runner(command, [], refresh=True, working_dir=self.root)
+
+    def add(self, runner, afiles):
+        # CVS needs to add files together with all the parents
+        # (if those are Unversioned yet)
+        relfiles = [os.path.relpath(s, self.root)
+                    for s in afiles if os.path.isfile(s)]
+        command = [self.CMD, 'add']
+
+        relargs = []
+        for f1 in relfiles:
+            positions = [i for i, ch in enumerate(f1+os.sep) if ch == os.sep]
+            arg1 = [f1[:pos] for pos in positions]
+            relargs += arg1
+
+        absargs = [os.path.join(self.root, a1) for a1 in relargs]
+        runner(command, absargs, refresh=True, working_dir=self.root)
+
+    def remove(self, runner, files):
+        command = [self.CMD, 'remove', '-f']
+        runner(command, files, refresh=True, working_dir=self.root)
+
+    def revert(self, runner, files):
+        command = [self.CMD, 'update', '-C']
+        runner(command, files, refresh=True, working_dir=self.root)
+
+    @classmethod
+    def valid_repo(cls, path):
+        return not _vc.call([cls.CMD, 'ls'], cwd=path)
+
+    def get_path_for_repo_file(self, path, commit=None):
+        if commit is not None:
+            raise NotImplementedError()
+
+        if not path.startswith(self.root + os.path.sep):
+            raise _vc.InvalidVCPath(self, path, "Path not in repository")
+        path = path[len(self.root) + 1:]
+
+        args = [self.CMD, "-q", "update", "-p", path]
+        return _vc.call_temp_output(args, cwd=self.root)
+
+    def _find_files(self, path):
+        relfiles = []
+        loc = os.path.join(self.location, path)
+        for step in os.walk(loc):
+            if not step[0].endswith(self.VC_DIR):
+                ff = [os.path.join(step[0], f1) for f1 in step[2]]
+                relfiles += [os.path.relpath(ff1, loc) for ff1 in ff]
+        return relfiles
+
+    def _update_tree_state_cache(self, path):
+        """ Update the state of the file(s) at self._tree_cache['path'] """
+        while 1:
+            try:
+                # Get the status of files
+
+                path_isdir = os.path.isdir(path)
+                files = self._find_files(path) if path_isdir else [path]
+
+                # Should suppress stderr here
+                proc = _vc.popen([self.CMD, "-Q", "status"] + files,
+                                 cwd=self.location)
+                entries = [li for li in proc.read().splitlines()
+                           if li.startswith('File:')]
+                break
+            except OSError as e:
+                if e.errno != errno.EAGAIN:
+                    raise
+
+        if len(entries) == 0 and os.path.isfile(path):
+            # If we're just updating a single file there's a chance that
+            # it was previously modified, and now has been edited so that
+            # it is un-modified.  This will result in an empty 'entries' list,
+            # and self._tree_cache['path'] will still contain stale data.
+            # When this corner case occurs we force self._tree_cache['path']
+            # to STATE_NORMAL.
+            self._tree_cache[path] = _vc.STATE_NORMAL
+        else:
+            # There are 1 or more [modified] files, parse their state
+            for entry in zip(files, entries):
+                statekey = entry[1].split(':')[-1].strip()
+                name = entry[0].strip()
+
+                if os.path.basename(name) not in entry[1]:
+                    # ? The short filename got from
+                    # 'cvs -Q status <path/file>' does not match <file>
+                    raise
+
+                path = os.path.join(self.location, name)
+                state = self.state_map.get(statekey, _vc.STATE_NONE)
+                self._tree_cache[path] = state
+                self._add_missing_cache_entry(path, state)
+
+        """
+        # Setting the state of dirs also might be relevant, but not sure
+        # Heuristic to find out the state ('CVS' subdir exists or not)
+        for entry in zip(dirs, [os.path.isdir(os.path.join(d1, self.VC_DIR))
+                                for d1 in dirs]):
+            path = os.path.join(self.location, entry[0])
+            state = _vc.STATE_NORMAL if entry[1] else _vc.STATE_NONE
+            self._tree_cache[path] = state
+            self._add_missing_cache_entry(path, state)
+        """


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]