repo.py revision 13540:da30e62884ee
12221SN/A#!/usr/bin/env python2.7 22221SN/A# 32221SN/A# Copyright (c) 2016 ARM Limited 42221SN/A# All rights reserved 52221SN/A# 62221SN/A# The license below extends only to copyright in the software and shall 72221SN/A# not be construed as granting a license to any other intellectual 82221SN/A# property including but not limited to intellectual property relating 92221SN/A# to a hardware implementation of the functionality of the software 102221SN/A# licensed hereunder. You may use the software subject to the license 112221SN/A# terms below provided that you ensure that this notice is replicated 122221SN/A# unmodified and in its entirety in all distributions of the software, 132221SN/A# modified or unmodified, in source code or in binary form. 142221SN/A# 152221SN/A# Redistribution and use in source and binary forms, with or without 162221SN/A# modification, are permitted provided that the following conditions are 172221SN/A# met: redistributions of source code must retain the above copyright 182221SN/A# notice, this list of conditions and the following disclaimer; 192221SN/A# redistributions in binary form must reproduce the above copyright 202221SN/A# notice, this list of conditions and the following disclaimer in the 212221SN/A# documentation and/or other materials provided with the distribution; 222221SN/A# neither the name of the copyright holders nor the names of its 232221SN/A# contributors may be used to endorse or promote products derived from 242221SN/A# this software without specific prior written permission. 252221SN/A# 262221SN/A# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 272665Ssaidi@eecs.umich.edu# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 282665Ssaidi@eecs.umich.edu# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 292665Ssaidi@eecs.umich.edu# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 302221SN/A# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 312221SN/A# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 323890Ssaidi@eecs.umich.edu# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 333890Ssaidi@eecs.umich.edu# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 342221SN/A# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 354997Sgblack@eecs.umich.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 367678Sgblack@eecs.umich.edu# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 372221SN/A# 382221SN/A# Authors: Andreas Sandberg 392221SN/A 402221SN/Afrom abc import * 412223SN/Aimport os 422221SN/Aimport subprocess 432221SN/A 443415Sgblack@eecs.umich.edufrom region import * 453415Sgblack@eecs.umich.edufrom style import modified_regions 462221SN/A 474997Sgblack@eecs.umich.educlass AbstractRepo(object): 484997Sgblack@eecs.umich.edu __metaclass__ = ABCMeta 493573Sgblack@eecs.umich.edu 502221SN/A def file_path(self, fname): 512221SN/A """Get the absolute path to a file relative within the repository. The 523576Sgblack@eecs.umich.edu input file name must be a valid path within the repository. 533576Sgblack@eecs.umich.edu 543576Sgblack@eecs.umich.edu """ 553576Sgblack@eecs.umich.edu return os.path.join(self.repo_base(), fname) 563576Sgblack@eecs.umich.edu 573576Sgblack@eecs.umich.edu def in_repo(self, fname): 583576Sgblack@eecs.umich.edu """Check if a path points to something within the repository base. Not 593576Sgblack@eecs.umich.edu that this does not check for the presence of the object in the 603576Sgblack@eecs.umich.edu file system as it could exist in the index without being in 613573Sgblack@eecs.umich.edu the file system. 623573Sgblack@eecs.umich.edu 633573Sgblack@eecs.umich.edu """ 643573Sgblack@eecs.umich.edu fname = os.path.abspath(fname) 653573Sgblack@eecs.umich.edu repo_path = os.path.abspath(self.repo_base()) 663576Sgblack@eecs.umich.edu 673573Sgblack@eecs.umich.edu return os.path.commonprefix([repo_path, fname]) == repo_path 683573Sgblack@eecs.umich.edu 692221SN/A def repo_path(self, fname): 707678Sgblack@eecs.umich.edu """Get the path of a file relative to the repository base. The input 717678Sgblack@eecs.umich.edu file name is assumed to be an absolute path or a path relative 722221SN/A to the current working directory. 732223SN/A 742223SN/A """ 752223SN/A return os.path.relpath(fname, self.repo_base()) 763576Sgblack@eecs.umich.edu 772221SN/A def get_file(self, name): 782221SN/A """Get the contents of a file in the file system using a path relative 793573Sgblack@eecs.umich.edu to the repository root. 803573Sgblack@eecs.umich.edu 812221SN/A """ 823573Sgblack@eecs.umich.edu with open(self.file_path(name), "r") as f: 833573Sgblack@eecs.umich.edu return f.read() 842221SN/A 857741Sgblack@eecs.umich.edu @abstractmethod 867741Sgblack@eecs.umich.edu def repo_base(self): 877741Sgblack@eecs.umich.edu """Get the path to the base of the repository""" 887741Sgblack@eecs.umich.edu pass 897741Sgblack@eecs.umich.edu 907741Sgblack@eecs.umich.edu @abstractmethod 917741Sgblack@eecs.umich.edu def staged_files(self): 923576Sgblack@eecs.umich.edu """Get a tuple describing the files that have been staged for a 933576Sgblack@eecs.umich.edu commit: (list of new, list of modified) 943576Sgblack@eecs.umich.edu 953573Sgblack@eecs.umich.edu """ 963573Sgblack@eecs.umich.edu pass 973576Sgblack@eecs.umich.edu 983576Sgblack@eecs.umich.edu @abstractmethod 997678Sgblack@eecs.umich.edu def staged_regions(self, fname, context=0): 1007678Sgblack@eecs.umich.edu """Get modified regions that will be committed by the next commit 1017678Sgblack@eecs.umich.edu command 1027678Sgblack@eecs.umich.edu 1033576Sgblack@eecs.umich.edu """ 1043576Sgblack@eecs.umich.edu pass 1053576Sgblack@eecs.umich.edu 1063576Sgblack@eecs.umich.edu @abstractmethod 1073576Sgblack@eecs.umich.edu def modified_regions(self, fname, context=0): 1083576Sgblack@eecs.umich.edu """Get modified regions that have been staged for commit or are 1093576Sgblack@eecs.umich.edu present in the file system. 1103576Sgblack@eecs.umich.edu 1113576Sgblack@eecs.umich.edu """ 1123576Sgblack@eecs.umich.edu pass 1133576Sgblack@eecs.umich.edu 1143576Sgblack@eecs.umich.educlass GitRepo(AbstractRepo): 1153576Sgblack@eecs.umich.edu def __init__(self): 1163576Sgblack@eecs.umich.edu self.git = "git" 1177741Sgblack@eecs.umich.edu self._head_revision = None 1183576Sgblack@eecs.umich.edu self._repo_base = None 1193576Sgblack@eecs.umich.edu 1203576Sgblack@eecs.umich.edu def repo_base(self): 1213576Sgblack@eecs.umich.edu if self._repo_base is None: 1223576Sgblack@eecs.umich.edu self._repo_base = subprocess.check_output( 1233576Sgblack@eecs.umich.edu [ self.git, "rev-parse", "--show-toplevel" ]).rstrip("\n") 1243576Sgblack@eecs.umich.edu 1257741Sgblack@eecs.umich.edu return self._repo_base 1263576Sgblack@eecs.umich.edu 1277741Sgblack@eecs.umich.edu def staged_files(self): 1283576Sgblack@eecs.umich.edu added = [] 1293576Sgblack@eecs.umich.edu modified = [] 1303576Sgblack@eecs.umich.edu for action, fname in self.status(filter="MA", cached=True): 1313576Sgblack@eecs.umich.edu if action == "M": 1323576Sgblack@eecs.umich.edu modified.append(fname) 1333576Sgblack@eecs.umich.edu elif action == "A": 1343576Sgblack@eecs.umich.edu added.append(fname) 1353576Sgblack@eecs.umich.edu 1363576Sgblack@eecs.umich.edu return added, modified 1373576Sgblack@eecs.umich.edu 1383576Sgblack@eecs.umich.edu def staged_regions(self, fname, context=0): 1393576Sgblack@eecs.umich.edu if self.file_status(fname, cached=True) in ("", "A", ): 1403576Sgblack@eecs.umich.edu return all_regions 1413573Sgblack@eecs.umich.edu 1423573Sgblack@eecs.umich.edu old = self.file_from_head(self.repo_path(fname)).split("\n") 1433573Sgblack@eecs.umich.edu new = self.file_from_index(self.repo_path(fname)).split("\n") 1443573Sgblack@eecs.umich.edu 1457741Sgblack@eecs.umich.edu return modified_regions(old, new, context=context) 1462221SN/A 1472221SN/A def modified_regions(self, fname, context=0): 1487741Sgblack@eecs.umich.edu if self.file_status(fname) in ("", "A", ): 1497741Sgblack@eecs.umich.edu return all_regions 1503576Sgblack@eecs.umich.edu 1513576Sgblack@eecs.umich.edu old = self.file_from_head(self.repo_path(fname)).split("\n") 1523576Sgblack@eecs.umich.edu new = self.get_file(self.repo_path(fname)).split("\n") 1533576Sgblack@eecs.umich.edu 1543576Sgblack@eecs.umich.edu return modified_regions(old, new, context=context) 1557741Sgblack@eecs.umich.edu 1563576Sgblack@eecs.umich.edu 1573576Sgblack@eecs.umich.edu def head_revision(self): 1583576Sgblack@eecs.umich.edu if self._head_revision is not None: 1593576Sgblack@eecs.umich.edu return self._head_revision 1603576Sgblack@eecs.umich.edu 1613573Sgblack@eecs.umich.edu try: 1623573Sgblack@eecs.umich.edu self._head_revision = subprocess.check_output( 1632221SN/A [ self.git, "rev-parse", "--verify", "HEAD" ], 1642221SN/A stderr=subprocess.PIPE).rstrip("\n") 1657741Sgblack@eecs.umich.edu except subprocess.CalledProcessError: 1662221SN/A # Assume that the repo is empty and use the semi-magic 1672221SN/A # empty tree revision if git rev-parse returned an error. 1683576Sgblack@eecs.umich.edu self._head_revision = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" 1693576Sgblack@eecs.umich.edu 1703576Sgblack@eecs.umich.edu return self._head_revision 1713576Sgblack@eecs.umich.edu 1723576Sgblack@eecs.umich.edu def file_status(self, fname, cached=False): 1733576Sgblack@eecs.umich.edu status = self.status(files=[fname], cached=cached) 1743576Sgblack@eecs.umich.edu assert len(status) <= 1 1753576Sgblack@eecs.umich.edu if status: 1763576Sgblack@eecs.umich.edu return status[0][0] 1773576Sgblack@eecs.umich.edu else: 1783576Sgblack@eecs.umich.edu # No information available for the file. This usually 1793576Sgblack@eecs.umich.edu # means that it hasn't been added to the 1803576Sgblack@eecs.umich.edu # repository/commit. 1813576Sgblack@eecs.umich.edu return "" 1823576Sgblack@eecs.umich.edu 1837741Sgblack@eecs.umich.edu def status(self, filter=None, files=[], cached=False): 1843576Sgblack@eecs.umich.edu cmd = [ self.git, "diff-index", "--name-status" ] 1853576Sgblack@eecs.umich.edu if cached: 1863576Sgblack@eecs.umich.edu cmd.append("--cached") 1873576Sgblack@eecs.umich.edu if filter: 1883576Sgblack@eecs.umich.edu cmd += [ "--diff-filter=%s" % filter ] 1893576Sgblack@eecs.umich.edu cmd += [ self.head_revision(), "--" ] + files 1903576Sgblack@eecs.umich.edu status = subprocess.check_output(cmd).rstrip("\n") 1913576Sgblack@eecs.umich.edu 1927741Sgblack@eecs.umich.edu if status: 1933576Sgblack@eecs.umich.edu return [ f.split("\t") for f in status.split("\n") ] 1943576Sgblack@eecs.umich.edu else: 1953576Sgblack@eecs.umich.edu return [] 1963576Sgblack@eecs.umich.edu 1973576Sgblack@eecs.umich.edu def file_from_index(self, name): 1983576Sgblack@eecs.umich.edu return subprocess.check_output( 1997741Sgblack@eecs.umich.edu [ self.git, "show", ":%s" % (name, ) ]) 2003576Sgblack@eecs.umich.edu 2013576Sgblack@eecs.umich.edu def file_from_head(self, name): 2023576Sgblack@eecs.umich.edu return subprocess.check_output( 2033576Sgblack@eecs.umich.edu [ self.git, "show", "%s:%s" % (self.head_revision(), name) ]) 2043576Sgblack@eecs.umich.edu 2053576Sgblack@eecs.umich.educlass MercurialRepo(AbstractRepo): 2064103Ssaidi@eecs.umich.edu def __init__(self): 2074103Ssaidi@eecs.umich.edu self.hg = "hg" 2083576Sgblack@eecs.umich.edu self._repo_base = None 2093576Sgblack@eecs.umich.edu 2103576Sgblack@eecs.umich.edu def repo_base(self): 2113576Sgblack@eecs.umich.edu if self._repo_base is None: 2123576Sgblack@eecs.umich.edu self._repo_base = subprocess.check_output( 2134997Sgblack@eecs.umich.edu [ self.hg, "root" ]).rstrip("\n") 2144997Sgblack@eecs.umich.edu 2154997Sgblack@eecs.umich.edu return self._repo_base 2164997Sgblack@eecs.umich.edu 2174997Sgblack@eecs.umich.edu def staged_files(self): 2184997Sgblack@eecs.umich.edu added = [] 2194997Sgblack@eecs.umich.edu modified = [] 2204997Sgblack@eecs.umich.edu for action, fname in self.status(): 2217678Sgblack@eecs.umich.edu if action == "M": 2227678Sgblack@eecs.umich.edu modified.append(fname) 2234997Sgblack@eecs.umich.edu elif action == "A": 2244997Sgblack@eecs.umich.edu added.append(fname) 2253576Sgblack@eecs.umich.edu 2264997Sgblack@eecs.umich.edu return added, modified 2274997Sgblack@eecs.umich.edu 2284997Sgblack@eecs.umich.edu def staged_regions(self, fname, context=0): 2294997Sgblack@eecs.umich.edu return self.modified_regions(fname, context=context) 2304997Sgblack@eecs.umich.edu 2314997Sgblack@eecs.umich.edu def modified_regions(self, fname, context=0): 2324997Sgblack@eecs.umich.edu old = self.file_from_tip(fname).split("\n") 2334997Sgblack@eecs.umich.edu new = self.get_file(fname).split("\n") 2347678Sgblack@eecs.umich.edu 2357678Sgblack@eecs.umich.edu return modified_regions(old, new, context=context) 2364997Sgblack@eecs.umich.edu 2374997Sgblack@eecs.umich.edu def status(self, filter=None): 2383576Sgblack@eecs.umich.edu files = subprocess.check_output([ self.hg, "status" ]).rstrip("\n") 2393576Sgblack@eecs.umich.edu if files: 2403576Sgblack@eecs.umich.edu return [ f.split(" ") for f in files.split("\n") ] 2413576Sgblack@eecs.umich.edu else: 2423576Sgblack@eecs.umich.edu return [] 2433576Sgblack@eecs.umich.edu 2443576Sgblack@eecs.umich.edu def file_from_tip(self, name): 2453576Sgblack@eecs.umich.edu return subprocess.check_output([ self.hg, "cat", name ]) 2463576Sgblack@eecs.umich.edu 2473893Shsul@eecs.umich.edudef detect_repo(path="."): 2483576Sgblack@eecs.umich.edu """Auto-detect the revision control system used for a source code 2493576Sgblack@eecs.umich.edu directory. The code starts searching for repository meta data 2503576Sgblack@eecs.umich.edu directories in path and then continues towards the root directory 2513576Sgblack@eecs.umich.edu until root is reached or a metadatadirectory has been found. 2523576Sgblack@eecs.umich.edu 2537741Sgblack@eecs.umich.edu Returns: List of repository helper classes that can interface with 2543576Sgblack@eecs.umich.edu the detected revision control system(s). 2557678Sgblack@eecs.umich.edu 2567678Sgblack@eecs.umich.edu """ 2573576Sgblack@eecs.umich.edu 2583576Sgblack@eecs.umich.edu _repo_types = ( 2593576Sgblack@eecs.umich.edu (".git", GitRepo), 2603576Sgblack@eecs.umich.edu (".hg", MercurialRepo), 2613576Sgblack@eecs.umich.edu ) 2623576Sgblack@eecs.umich.edu 2637741Sgblack@eecs.umich.edu repo_types = [] 2647741Sgblack@eecs.umich.edu for repo_dir, repo_class in _repo_types: 2653576Sgblack@eecs.umich.edu if os.path.exists(os.path.join(path, repo_dir)): 2663576Sgblack@eecs.umich.edu repo_types.append(repo_class) 2673576Sgblack@eecs.umich.edu 2683576Sgblack@eecs.umich.edu if repo_types: 2693576Sgblack@eecs.umich.edu return repo_types 2707741Sgblack@eecs.umich.edu else: 2717741Sgblack@eecs.umich.edu parent_dir = os.path.abspath(os.path.join(path, "..")) 2727741Sgblack@eecs.umich.edu if not os.path.samefile(parent_dir, path): 2733576Sgblack@eecs.umich.edu return detect_repo(path=parent_dir) 2747678Sgblack@eecs.umich.edu else: 2757678Sgblack@eecs.umich.edu # We reached the root directory without finding a meta 2763576Sgblack@eecs.umich.edu # data directory. 2773576Sgblack@eecs.umich.edu return [] 2783576Sgblack@eecs.umich.edu