113540Sandrea.mondelli@ucf.edu#!/usr/bin/env python2.7
211406Sandreas.sandberg@arm.com#
311406Sandreas.sandberg@arm.com# Copyright (c) 2016 ARM Limited
411406Sandreas.sandberg@arm.com# All rights reserved
511406Sandreas.sandberg@arm.com#
611406Sandreas.sandberg@arm.com# The license below extends only to copyright in the software and shall
711406Sandreas.sandberg@arm.com# not be construed as granting a license to any other intellectual
811406Sandreas.sandberg@arm.com# property including but not limited to intellectual property relating
911406Sandreas.sandberg@arm.com# to a hardware implementation of the functionality of the software
1011406Sandreas.sandberg@arm.com# licensed hereunder.  You may use the software subject to the license
1111406Sandreas.sandberg@arm.com# terms below provided that you ensure that this notice is replicated
1211406Sandreas.sandberg@arm.com# unmodified and in its entirety in all distributions of the software,
1311406Sandreas.sandberg@arm.com# modified or unmodified, in source code or in binary form.
1411406Sandreas.sandberg@arm.com#
1511406Sandreas.sandberg@arm.com# Redistribution and use in source and binary forms, with or without
1611406Sandreas.sandberg@arm.com# modification, are permitted provided that the following conditions are
1711406Sandreas.sandberg@arm.com# met: redistributions of source code must retain the above copyright
1811406Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer;
1911406Sandreas.sandberg@arm.com# redistributions in binary form must reproduce the above copyright
2011406Sandreas.sandberg@arm.com# notice, this list of conditions and the following disclaimer in the
2111406Sandreas.sandberg@arm.com# documentation and/or other materials provided with the distribution;
2211406Sandreas.sandberg@arm.com# neither the name of the copyright holders nor the names of its
2311406Sandreas.sandberg@arm.com# contributors may be used to endorse or promote products derived from
2411406Sandreas.sandberg@arm.com# this software without specific prior written permission.
2511406Sandreas.sandberg@arm.com#
2611406Sandreas.sandberg@arm.com# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
2711406Sandreas.sandberg@arm.com# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
2811406Sandreas.sandberg@arm.com# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
2911406Sandreas.sandberg@arm.com# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
3011406Sandreas.sandberg@arm.com# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
3111406Sandreas.sandberg@arm.com# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
3211406Sandreas.sandberg@arm.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
3311406Sandreas.sandberg@arm.com# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
3411406Sandreas.sandberg@arm.com# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
3511406Sandreas.sandberg@arm.com# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
3611406Sandreas.sandberg@arm.com# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3711406Sandreas.sandberg@arm.com#
3811406Sandreas.sandberg@arm.com# Authors: Andreas Sandberg
3911406Sandreas.sandberg@arm.com
4011406Sandreas.sandberg@arm.comfrom abc import *
4111406Sandreas.sandberg@arm.comimport os
4211406Sandreas.sandberg@arm.comimport subprocess
4311406Sandreas.sandberg@arm.com
4411406Sandreas.sandberg@arm.comfrom region import *
4511406Sandreas.sandberg@arm.comfrom style import modified_regions
4611406Sandreas.sandberg@arm.com
4711406Sandreas.sandberg@arm.comclass AbstractRepo(object):
4811406Sandreas.sandberg@arm.com    __metaclass__ = ABCMeta
4911406Sandreas.sandberg@arm.com
5011406Sandreas.sandberg@arm.com    def file_path(self, fname):
5111406Sandreas.sandberg@arm.com        """Get the absolute path to a file relative within the repository. The
5211406Sandreas.sandberg@arm.com        input file name must be a valid path within the repository.
5311406Sandreas.sandberg@arm.com
5411406Sandreas.sandberg@arm.com        """
5511406Sandreas.sandberg@arm.com        return os.path.join(self.repo_base(), fname)
5611406Sandreas.sandberg@arm.com
5711406Sandreas.sandberg@arm.com    def in_repo(self, fname):
5811406Sandreas.sandberg@arm.com        """Check if a path points to something within the repository base. Not
5911406Sandreas.sandberg@arm.com        that this does not check for the presence of the object in the
6011406Sandreas.sandberg@arm.com        file system as it could exist in the index without being in
6111406Sandreas.sandberg@arm.com        the file system.
6211406Sandreas.sandberg@arm.com
6311406Sandreas.sandberg@arm.com        """
6411406Sandreas.sandberg@arm.com        fname = os.path.abspath(fname)
6511406Sandreas.sandberg@arm.com        repo_path = os.path.abspath(self.repo_base())
6611406Sandreas.sandberg@arm.com
6711406Sandreas.sandberg@arm.com        return os.path.commonprefix([repo_path, fname]) == repo_path
6811406Sandreas.sandberg@arm.com
6911406Sandreas.sandberg@arm.com    def repo_path(self, fname):
7011406Sandreas.sandberg@arm.com        """Get the path of a file relative to the repository base. The input
7111406Sandreas.sandberg@arm.com        file name is assumed to be an absolute path or a path relative
7211406Sandreas.sandberg@arm.com        to the current working directory.
7311406Sandreas.sandberg@arm.com
7411406Sandreas.sandberg@arm.com        """
7511406Sandreas.sandberg@arm.com        return os.path.relpath(fname, self.repo_base())
7611406Sandreas.sandberg@arm.com
7711406Sandreas.sandberg@arm.com    def get_file(self, name):
7811406Sandreas.sandberg@arm.com        """Get the contents of a file in the file system using a path relative
7911406Sandreas.sandberg@arm.com        to the repository root.
8011406Sandreas.sandberg@arm.com
8111406Sandreas.sandberg@arm.com        """
8211406Sandreas.sandberg@arm.com        with open(self.file_path(name), "r") as f:
8311406Sandreas.sandberg@arm.com            return f.read()
8411406Sandreas.sandberg@arm.com
8511406Sandreas.sandberg@arm.com    @abstractmethod
8611406Sandreas.sandberg@arm.com    def repo_base(self):
8711406Sandreas.sandberg@arm.com        """Get the path to the base of the repository"""
8811406Sandreas.sandberg@arm.com        pass
8911406Sandreas.sandberg@arm.com
9011406Sandreas.sandberg@arm.com    @abstractmethod
9111406Sandreas.sandberg@arm.com    def staged_files(self):
9211406Sandreas.sandberg@arm.com        """Get a tuple describing the files that have been staged for a
9311406Sandreas.sandberg@arm.com        commit: (list of new, list of modified)
9411406Sandreas.sandberg@arm.com
9511406Sandreas.sandberg@arm.com        """
9611406Sandreas.sandberg@arm.com        pass
9711406Sandreas.sandberg@arm.com
9811406Sandreas.sandberg@arm.com    @abstractmethod
9911406Sandreas.sandberg@arm.com    def staged_regions(self, fname, context=0):
10011406Sandreas.sandberg@arm.com        """Get modified regions that will be committed by the next commit
10111406Sandreas.sandberg@arm.com        command
10211406Sandreas.sandberg@arm.com
10311406Sandreas.sandberg@arm.com        """
10411406Sandreas.sandberg@arm.com        pass
10511406Sandreas.sandberg@arm.com
10611406Sandreas.sandberg@arm.com    @abstractmethod
10711406Sandreas.sandberg@arm.com    def modified_regions(self, fname, context=0):
10811406Sandreas.sandberg@arm.com        """Get modified regions that have been staged for commit or are
10911406Sandreas.sandberg@arm.com        present in the file system.
11011406Sandreas.sandberg@arm.com
11111406Sandreas.sandberg@arm.com        """
11211406Sandreas.sandberg@arm.com        pass
11311406Sandreas.sandberg@arm.com
11411406Sandreas.sandberg@arm.comclass GitRepo(AbstractRepo):
11511406Sandreas.sandberg@arm.com    def __init__(self):
11611406Sandreas.sandberg@arm.com        self.git = "git"
11711406Sandreas.sandberg@arm.com        self._head_revision = None
11811406Sandreas.sandberg@arm.com        self._repo_base = None
11911406Sandreas.sandberg@arm.com
12011406Sandreas.sandberg@arm.com    def repo_base(self):
12111406Sandreas.sandberg@arm.com        if self._repo_base is None:
12211406Sandreas.sandberg@arm.com            self._repo_base = subprocess.check_output(
12311406Sandreas.sandberg@arm.com                [ self.git, "rev-parse", "--show-toplevel" ]).rstrip("\n")
12411406Sandreas.sandberg@arm.com
12511406Sandreas.sandberg@arm.com        return self._repo_base
12611406Sandreas.sandberg@arm.com
12711406Sandreas.sandberg@arm.com    def staged_files(self):
12811406Sandreas.sandberg@arm.com        added = []
12911406Sandreas.sandberg@arm.com        modified = []
13011406Sandreas.sandberg@arm.com        for action, fname in self.status(filter="MA", cached=True):
13111406Sandreas.sandberg@arm.com            if action == "M":
13211406Sandreas.sandberg@arm.com                modified.append(fname)
13311406Sandreas.sandberg@arm.com            elif action == "A":
13411406Sandreas.sandberg@arm.com                added.append(fname)
13511406Sandreas.sandberg@arm.com
13611406Sandreas.sandberg@arm.com        return added, modified
13711406Sandreas.sandberg@arm.com
13811406Sandreas.sandberg@arm.com    def staged_regions(self, fname, context=0):
13911406Sandreas.sandberg@arm.com        if self.file_status(fname, cached=True) in ("", "A", ):
14011406Sandreas.sandberg@arm.com            return all_regions
14111406Sandreas.sandberg@arm.com
14211406Sandreas.sandberg@arm.com        old = self.file_from_head(self.repo_path(fname)).split("\n")
14311406Sandreas.sandberg@arm.com        new = self.file_from_index(self.repo_path(fname)).split("\n")
14411406Sandreas.sandberg@arm.com
14511406Sandreas.sandberg@arm.com        return modified_regions(old, new, context=context)
14611406Sandreas.sandberg@arm.com
14711406Sandreas.sandberg@arm.com    def modified_regions(self, fname, context=0):
14811406Sandreas.sandberg@arm.com        if self.file_status(fname) in ("", "A", ):
14911406Sandreas.sandberg@arm.com            return all_regions
15011406Sandreas.sandberg@arm.com
15111406Sandreas.sandberg@arm.com        old = self.file_from_head(self.repo_path(fname)).split("\n")
15211406Sandreas.sandberg@arm.com        new = self.get_file(self.repo_path(fname)).split("\n")
15311406Sandreas.sandberg@arm.com
15411406Sandreas.sandberg@arm.com        return modified_regions(old, new, context=context)
15511406Sandreas.sandberg@arm.com
15611406Sandreas.sandberg@arm.com
15711406Sandreas.sandberg@arm.com    def head_revision(self):
15811406Sandreas.sandberg@arm.com        if self._head_revision is not None:
15911406Sandreas.sandberg@arm.com            return self._head_revision
16011406Sandreas.sandberg@arm.com
16111406Sandreas.sandberg@arm.com        try:
16211406Sandreas.sandberg@arm.com            self._head_revision = subprocess.check_output(
16311406Sandreas.sandberg@arm.com                [ self.git, "rev-parse", "--verify", "HEAD" ],
16411406Sandreas.sandberg@arm.com                stderr=subprocess.PIPE).rstrip("\n")
16511406Sandreas.sandberg@arm.com        except subprocess.CalledProcessError:
16611406Sandreas.sandberg@arm.com            # Assume that the repo is empty and use the semi-magic
16711406Sandreas.sandberg@arm.com            # empty tree revision if git rev-parse returned an error.
16811406Sandreas.sandberg@arm.com            self._head_revision = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
16911406Sandreas.sandberg@arm.com
17011406Sandreas.sandberg@arm.com        return self._head_revision
17111406Sandreas.sandberg@arm.com
17211406Sandreas.sandberg@arm.com    def file_status(self, fname, cached=False):
17311406Sandreas.sandberg@arm.com        status = self.status(files=[fname], cached=cached)
17411406Sandreas.sandberg@arm.com        assert len(status) <= 1
17511406Sandreas.sandberg@arm.com        if status:
17611406Sandreas.sandberg@arm.com            return status[0][0]
17711406Sandreas.sandberg@arm.com        else:
17811406Sandreas.sandberg@arm.com            # No information available for the file. This usually
17911406Sandreas.sandberg@arm.com            # means that it hasn't been added to the
18011406Sandreas.sandberg@arm.com            # repository/commit.
18111406Sandreas.sandberg@arm.com            return ""
18211406Sandreas.sandberg@arm.com
18311406Sandreas.sandberg@arm.com    def status(self, filter=None, files=[], cached=False):
18411406Sandreas.sandberg@arm.com        cmd = [ self.git, "diff-index", "--name-status" ]
18511406Sandreas.sandberg@arm.com        if cached:
18611406Sandreas.sandberg@arm.com            cmd.append("--cached")
18711406Sandreas.sandberg@arm.com        if filter:
18811466SCurtis.Dunham@arm.com            cmd += [ "--diff-filter=%s" % filter ]
18911406Sandreas.sandberg@arm.com        cmd += [ self.head_revision(), "--" ] + files
19011406Sandreas.sandberg@arm.com        status = subprocess.check_output(cmd).rstrip("\n")
19111406Sandreas.sandberg@arm.com
19211406Sandreas.sandberg@arm.com        if status:
19311406Sandreas.sandberg@arm.com            return [ f.split("\t") for f in status.split("\n") ]
19411406Sandreas.sandberg@arm.com        else:
19511406Sandreas.sandberg@arm.com            return []
19611406Sandreas.sandberg@arm.com
19711406Sandreas.sandberg@arm.com    def file_from_index(self, name):
19811406Sandreas.sandberg@arm.com        return subprocess.check_output(
19911406Sandreas.sandberg@arm.com            [ self.git, "show", ":%s" % (name, ) ])
20011406Sandreas.sandberg@arm.com
20111406Sandreas.sandberg@arm.com    def file_from_head(self, name):
20211406Sandreas.sandberg@arm.com        return subprocess.check_output(
20311406Sandreas.sandberg@arm.com            [ self.git, "show", "%s:%s" % (self.head_revision(), name) ])
20411406Sandreas.sandberg@arm.com
20511406Sandreas.sandberg@arm.comclass MercurialRepo(AbstractRepo):
20611406Sandreas.sandberg@arm.com    def __init__(self):
20711406Sandreas.sandberg@arm.com        self.hg = "hg"
20811406Sandreas.sandberg@arm.com        self._repo_base = None
20911406Sandreas.sandberg@arm.com
21011406Sandreas.sandberg@arm.com    def repo_base(self):
21111406Sandreas.sandberg@arm.com        if self._repo_base is None:
21211406Sandreas.sandberg@arm.com            self._repo_base = subprocess.check_output(
21311406Sandreas.sandberg@arm.com                [ self.hg, "root" ]).rstrip("\n")
21411406Sandreas.sandberg@arm.com
21511406Sandreas.sandberg@arm.com        return self._repo_base
21611406Sandreas.sandberg@arm.com
21711406Sandreas.sandberg@arm.com    def staged_files(self):
21811406Sandreas.sandberg@arm.com        added = []
21911406Sandreas.sandberg@arm.com        modified = []
22011406Sandreas.sandberg@arm.com        for action, fname in self.status():
22111406Sandreas.sandberg@arm.com            if action == "M":
22211406Sandreas.sandberg@arm.com                modified.append(fname)
22311406Sandreas.sandberg@arm.com            elif action == "A":
22411406Sandreas.sandberg@arm.com                added.append(fname)
22511406Sandreas.sandberg@arm.com
22611406Sandreas.sandberg@arm.com        return added, modified
22711406Sandreas.sandberg@arm.com
22811406Sandreas.sandberg@arm.com    def staged_regions(self, fname, context=0):
22911406Sandreas.sandberg@arm.com        return self.modified_regions(fname, context=context)
23011406Sandreas.sandberg@arm.com
23111406Sandreas.sandberg@arm.com    def modified_regions(self, fname, context=0):
23211406Sandreas.sandberg@arm.com        old = self.file_from_tip(fname).split("\n")
23311406Sandreas.sandberg@arm.com        new = self.get_file(fname).split("\n")
23411406Sandreas.sandberg@arm.com
23511406Sandreas.sandberg@arm.com        return modified_regions(old, new, context=context)
23611406Sandreas.sandberg@arm.com
23711406Sandreas.sandberg@arm.com    def status(self, filter=None):
23811406Sandreas.sandberg@arm.com        files = subprocess.check_output([ self.hg, "status" ]).rstrip("\n")
23911406Sandreas.sandberg@arm.com        if files:
24011406Sandreas.sandberg@arm.com            return [ f.split(" ") for f in files.split("\n") ]
24111406Sandreas.sandberg@arm.com        else:
24211406Sandreas.sandberg@arm.com            return []
24311406Sandreas.sandberg@arm.com
24411406Sandreas.sandberg@arm.com    def file_from_tip(self, name):
24511406Sandreas.sandberg@arm.com        return subprocess.check_output([ self.hg, "cat", name ])
24611406Sandreas.sandberg@arm.com
24711406Sandreas.sandberg@arm.comdef detect_repo(path="."):
24811406Sandreas.sandberg@arm.com    """Auto-detect the revision control system used for a source code
24911406Sandreas.sandberg@arm.com    directory. The code starts searching for repository meta data
25011406Sandreas.sandberg@arm.com    directories in path and then continues towards the root directory
25111406Sandreas.sandberg@arm.com    until root is reached or a metadatadirectory has been found.
25211406Sandreas.sandberg@arm.com
25311406Sandreas.sandberg@arm.com    Returns: List of repository helper classes that can interface with
25411406Sandreas.sandberg@arm.com    the detected revision control system(s).
25511406Sandreas.sandberg@arm.com
25611406Sandreas.sandberg@arm.com    """
25711406Sandreas.sandberg@arm.com
25811406Sandreas.sandberg@arm.com    _repo_types = (
25911406Sandreas.sandberg@arm.com        (".git", GitRepo),
26011406Sandreas.sandberg@arm.com        (".hg", MercurialRepo),
26111406Sandreas.sandberg@arm.com    )
26211406Sandreas.sandberg@arm.com
26311406Sandreas.sandberg@arm.com    repo_types = []
26411406Sandreas.sandberg@arm.com    for repo_dir, repo_class in _repo_types:
26511406Sandreas.sandberg@arm.com        if os.path.exists(os.path.join(path, repo_dir)):
26611406Sandreas.sandberg@arm.com            repo_types.append(repo_class)
26711406Sandreas.sandberg@arm.com
26811406Sandreas.sandberg@arm.com    if repo_types:
26911406Sandreas.sandberg@arm.com        return repo_types
27011406Sandreas.sandberg@arm.com    else:
27111406Sandreas.sandberg@arm.com        parent_dir = os.path.abspath(os.path.join(path, ".."))
27211406Sandreas.sandberg@arm.com        if not os.path.samefile(parent_dir, path):
27311406Sandreas.sandberg@arm.com            return detect_repo(path=parent_dir)
27411406Sandreas.sandberg@arm.com        else:
27511406Sandreas.sandberg@arm.com            # We reached the root directory without finding a meta
27611406Sandreas.sandberg@arm.com            # data directory.
27711406Sandreas.sandberg@arm.com            return []
278