text: # -*-python-*-
  #
  # Copyright (C) 1999-2018 The ViewCVS Group. All Rights Reserved.
  #
  # By using this file, you agree to the terms and conditions set forth in
  # the LICENSE.html file which can be found at the top level of the ViewVC
  # distribution or at http://viewvc.org/license-1.html.
  #
  # For more information, visit http://viewvc.org/
  #
  # -----------------------------------------------------------------------
  
  """Version Control lib is an abstract API to access versioning systems
  such as CVS.
  """
  
  import types
  
  
  # item types returned by Repository.itemtype().
  FILE = 'FILE'
  DIR = 'DIR'
  
  # diff types recognized by Repository.rawdiff().
  UNIFIED = 1
  CONTEXT = 2
  SIDE_BY_SIDE = 3
  
  # root types returned by Repository.roottype().
  CVS = 'cvs'
  SVN = 'svn'
  
  # action kinds found in ChangedPath.action
  ADDED      = 'added'
  DELETED    = 'deleted'
  REPLACED   = 'replaced'
  MODIFIED   = 'modified'
  
  # log sort keys
  SORTBY_DEFAULT = 0  # default/no sorting
  SORTBY_DATE    = 1  # sorted by date, youngest first
  SORTBY_REV     = 2  # sorted by revision, youngest first
  
  
  # ======================================================================
  #
  class Repository:
    """Abstract class representing a repository."""
  
    def rootname(self):
      """Return the name of this repository."""
  
    def roottype(self):
      """Return the type of this repository (vclib.CVS, vclib.SVN, ...)."""
  
    def rootpath(self):
      """Return the location of this repository."""
  
    def authorizer(self):
      """Return the vcauth.Authorizer object associated with this
      repository, or None if no such association has been made."""
      
    def open(self):
      """Open a connection to the repository."""
      
    def itemtype(self, path_parts, rev):
      """Return the type of the item (file or dir) at the given path and revision
  
      The result will be vclib.DIR or vclib.FILE
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to check
      """
      pass
  
    def openfile(self, path_parts, rev, options):
      """Open a file object to read file contents at a given path and revision.
  
      The return value is a 2-tuple of containg the file object and revision
      number in canonical form.
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the file to check out
  
      options is a dictionary of implementation specific options
      """
  
    def listdir(self, path_parts, rev, options):
      """Return list of files in a directory
  
      The result is a list of DirEntry objects
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the directory to list
  
      options is a dictionary of implementation specific options
      """
  
    def dirlogs(self, path_parts, rev, entries, options):
      """Augment directory entries with log information
  
      New properties will be set on all of the DirEntry objects in the entries
      list. At the very least, a "rev" property will be set to a revision
      number or None if the entry doesn't have a number. Other properties that
      may be set include "date", "author", "log", "size", and "lockinfo".
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the directory listing and will effect which log
      messages are returned
  
      entries is a list of DirEntry objects returned from a previous call to
      the listdir() method
  
      options is a dictionary of implementation specific options
      """
    
    def itemlog(self, path_parts, rev, sortby, first, limit, options):
      """Retrieve an item's log information
  
      The result is a list of Revision objects
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to return information about
  
      sortby indicates the way in which the returned list should be
      sorted (SORTBY_DEFAULT, SORTBY_DATE, SORTBY_REV)
  
      first is the 0-based index of the first Revision returned (after
      sorting, if any, has occured)
  
      limit is the maximum number of returned Revisions, or 0 to return
      all available data
      
      options is a dictionary of implementation specific options
      """
  
    def itemprops(self, path_parts, rev):
      """Return a dictionary mapping property names to property values
      for properties stored on an item.
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to return information about.
      """
      
    def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
      """Return a diff (in GNU diff format) of two file revisions
  
      type is the requested diff type (UNIFIED, CONTEXT, etc)
  
      options is a dictionary that can contain the following options plus
      implementation-specific options
  
        context - integer, number of context lines to include
        funout - boolean, include C function names
        ignore_white - boolean, ignore whitespace
  
      Return value is a python file object
      """
  
    def annotate(self, path_parts, rev, include_text=False):
      """Return a list of Annotation object, sorted by their
      "line_number" components, which describe the lines of given
      version of a file.
  
      The file path is specified as a list of components, relative to
      the root of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to return information about.
      
      If include_text is true, populate the Annotation objects' "text"
      members with the corresponding line of file content; otherwise,
      leave that member set to None."""
  
    def revinfo(self, rev):
      """Return information about a global revision
  
      rev is the revision of the item to return information about
      
      Return value is a 5-tuple containing: the date, author, log
      message, a list of ChangedPath items representing paths changed,
      and a dictionary mapping property names to property values for
      properties stored on an item.
  
      Raise vclib.UnsupportedFeature if the version control system
      doesn't support a global revision concept.
      """
  
    def isexecutable(self, path_parts, rev):
      """Return true iff a given revision of a versioned file is to be
      considered an executable program or script.
  
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to return information about
      """
  
    def filesize(self, path_parts, rev):
      """Return the size of a versioned file's contents if it can be
      obtained without a brute force measurement; -1 otherwise.
  
      NOTE: Callers that require a filesize answer when this function
      returns -1 may obtain it by measuring the data returned via
      openfile().
      
      The path is specified as a list of components, relative to the root
      of the repository. e.g. ["subdir1", "subdir2", "filename"]
  
      rev is the revision of the item to return information about
      """
  
      
  # ======================================================================
  class DirEntry:
    """Instances represent items in a directory listing"""
  
    def __init__(self, name, kind, errors=[]):
      """Create a new DirEntry() item:
            NAME:  The name of the directory entry
            KIND:  The path kind of the entry (vclib.DIR, vclib.FILE)
            ERRORS:  A list of error strings representing problems encountered
                     while determining the other info about this entry
      """
      self.name = name
      self.kind = kind
      self.errors = errors
  
  class Revision:
    """Instances holds information about revisions of versioned resources"""
  
    def __init__(self, number, string, date, author, changed, log, size, lockinfo):
      """Create a new Revision() item:
            NUMBER:  Revision in an integer-based, sortable format
            STRING:  Revision as a string
            DATE:  Seconds since Epoch (GMT) that this revision was created
            AUTHOR:  Author of the revision
            CHANGED:  Lines-changed (contextual diff) information
            LOG:  Log message associated with the creation of this revision
            SIZE:  Size (in bytes) of this revision's fulltext (files only)
            LOCKINFO:  Information about locks held on this revision
      """
      self.number = number
      self.string = string
      self.date = date
      self.author = author
      self.changed = changed
      self.log = log
      self.size = size
      self.lockinfo = lockinfo
  
    def __cmp__(self, other):
      return cmp(self.number, other.number)
  
  class Annotation:
    """Instances represent per-line file annotation information"""
  
    def __init__(self, text, line_number, rev, prev_rev, author, date):
      """Create a new Annotation() item:
            TEXT:  Raw text of a line of file contents
            LINE_NUMBER:  Line number on which the line is found
            REV:  Revision in which the line was last modified
            PREV_REV:  Revision prior to 'rev'
            AUTHOR:  Author who last modified the line
            DATE:  Date on which the line was last modified, in seconds since
                   the epoch, GMT
      """
      self.text = text
      self.line_number = line_number
      self.rev = rev
      self.prev_rev = prev_rev
      self.author = author
      self.date = date
  
  class ChangedPath:
    """Instances represent changes to paths"""
  
    def __init__(self, path_parts, rev, pathtype, base_path_parts,
                 base_rev, action, copied, text_changed, props_changed):
      """Create a new ChangedPath() item:
            PATH_PARTS:       Path that was changed
            REV:              Revision represented by this change
            PATHTYPE:         Type of this path (vclib.DIR, vclib.FILE, ...)
            BASE_PATH_PARTS:  Previous path for this changed item
            BASE_REV:         Previous revision for this changed item
            ACTION:           Kind of change (vclib.ADDED, vclib.DELETED, ...)
            COPIED:           Boolean -- was this path copied from elsewhere?
            TEXT_CHANGED:     Boolean -- did the file's text change?
            PROPS_CHANGED:    Boolean -- did the item's metadata change?
      """
      self.path_parts = path_parts
      self.rev = rev
      self.pathtype = pathtype
      self.base_path_parts = base_path_parts
      self.base_rev = base_rev
      self.action = action
      self.copied = copied
      self.text_changed = text_changed
      self.props_changed = props_changed
  
  
  # ======================================================================
  
  class Error(Exception):
    pass
  
  class ReposNotFound(Error):
    pass
  
  class UnsupportedFeature(Error):
    pass
  
  class ItemNotFound(Error):
    def __init__(self, path):
      # use '/' rather than os.sep because this is for user consumption, and
      # it was defined using URL separators
      if type(path) in (types.TupleType, types.ListType):
        path = '/'.join(path)
      Error.__init__(self, path)
  
  class InvalidRevision(Error):
    def __init__(self, revision=None):
      if revision is None:
        Error.__init__(self, "Invalid revision")
      else:
        Error.__init__(self, "Invalid revision " + str(revision))
  
  class NonTextualFileContents(Error):
    pass
  
  # ======================================================================
  # Implementation code used by multiple vclib modules
  
  import popen
  import os
  import time
  
  def _diff_args(type, options):
    """generate argument list to pass to diff or rcsdiff"""
    args = []
    if type == CONTEXT:
      if options.has_key('context'):
        if options['context'] is None:
          args.append('--context=-1')
        else:
          args.append('--context=%i' % options['context'])
      else:
        args.append('-c')
    elif type == UNIFIED:
      if options.has_key('context'):
        if options['context'] is None:
          args.append('--unified=-1')
        else:
          args.append('--unified=%i' % options['context'])
      else:
        args.append('-u')
    elif type == SIDE_BY_SIDE:
      args.append('--side-by-side')
      args.append('--width=164')
    else:
      raise NotImplementedError
  
    if options.get('funout', 0):
      args.append('-p')
  
    if options.get('ignore_white', 0):
      args.append('-w')
  
    return args
  
  class _diff_fp:
    """File object reading a diff between temporary files, cleaning up
    on close"""
  
    def __init__(self, temp1, temp2, info1=None, info2=None, diff_cmd='diff', diff_opts=[]):
      self.temp1 = temp1
      self.temp2 = temp2
      args = diff_opts[:]
      if info1 and info2:
        args.extend(["-L", self._label(info1), "-L", self._label(info2)])
      args.extend([temp1, temp2])
      self.fp = popen.popen(diff_cmd, args, "r")
  
    def read(self, bytes):
      return self.fp.read(bytes)
  
    def readline(self):
      return self.fp.readline()
  
    def close(self):
      try:
        if self.fp:
          self.fp.close()
          self.fp = None
      finally:
        try:
          if self.temp1:
            os.remove(self.temp1)
            self.temp1 = None
        finally:
          if self.temp2:
            os.remove(self.temp2)
            self.temp2 = None
  
    def __del__(self):
      self.close()
  
    def _label(self, (path, date, rev)):
      date = date and time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date))
      return "%s\t%s\t%s" % (path, date, rev)
  
  
  def check_root_access(repos):
    """Return 1 iff the associated username is permitted to read REPOS,
    as determined by consulting REPOS's Authorizer object (if any)."""
  
    auth = repos.authorizer()
    if not auth:
      return 1
    return auth.check_root_access(repos.rootname())
    
  def check_path_access(repos, path_parts, pathtype=None, rev=None):
    """Return 1 iff the associated username is permitted to read
    revision REV of the path PATH_PARTS (of type PATHTYPE) in repository
    REPOS, as determined by consulting REPOS's Authorizer object (if any)."""
  
    auth = repos.authorizer()
    if not auth:
      return 1
    if not pathtype:
      pathtype = repos.itemtype(path_parts, rev)
    return auth.check_path_access(repos.rootname(), path_parts, pathtype, rev)
  
  