repo.py revision 11406
17008Snate@binkert.org#!/usr/bin/env python 27008Snate@binkert.org# 37008Snate@binkert.org# Copyright (c) 2016 ARM Limited 47008Snate@binkert.org# All rights reserved 57008Snate@binkert.org# 67008Snate@binkert.org# The license below extends only to copyright in the software and shall 77008Snate@binkert.org# not be construed as granting a license to any other intellectual 87008Snate@binkert.org# property including but not limited to intellectual property relating 97008Snate@binkert.org# to a hardware implementation of the functionality of the software 107008Snate@binkert.org# licensed hereunder. You may use the software subject to the license 117008Snate@binkert.org# terms below provided that you ensure that this notice is replicated 127008Snate@binkert.org# unmodified and in its entirety in all distributions of the software, 137008Snate@binkert.org# modified or unmodified, in source code or in binary form. 147008Snate@binkert.org# 157008Snate@binkert.org# Redistribution and use in source and binary forms, with or without 167008Snate@binkert.org# modification, are permitted provided that the following conditions are 177008Snate@binkert.org# met: redistributions of source code must retain the above copyright 187008Snate@binkert.org# notice, this list of conditions and the following disclaimer; 197008Snate@binkert.org# redistributions in binary form must reproduce the above copyright 207008Snate@binkert.org# notice, this list of conditions and the following disclaimer in the 217008Snate@binkert.org# documentation and/or other materials provided with the distribution; 227008Snate@binkert.org# neither the name of the copyright holders nor the names of its 237008Snate@binkert.org# contributors may be used to endorse or promote products derived from 247008Snate@binkert.org# this software without specific prior written permission. 257008Snate@binkert.org# 267008Snate@binkert.org# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 277008Snate@binkert.org# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 286285Snate@binkert.org# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 297039Snate@binkert.org# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 307039Snate@binkert.org# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 316285Snate@binkert.org# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 3210706Spower.jg@gmail.com# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 336285Snate@binkert.org# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 347039Snate@binkert.org# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 359104Shestness@cs.utexas.edu# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 366285Snate@binkert.org# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 3711339SMichael.Lebeane@amd.com# 386876Ssteve.reinhardt@amd.com# Authors: Andreas Sandberg 396876Ssteve.reinhardt@amd.com 407039Snate@binkert.orgfrom abc import * 417039Snate@binkert.orgimport os 427039Snate@binkert.orgimport subprocess 437039Snate@binkert.org 447039Snate@binkert.orgfrom region import * 457039Snate@binkert.orgfrom style import modified_regions 467039Snate@binkert.org 479208Snilay@cs.wisc.educlass AbstractRepo(object): 487039Snate@binkert.org __metaclass__ = ABCMeta 496285Snate@binkert.org 506285Snate@binkert.org def file_path(self, fname): 5111339SMichael.Lebeane@amd.com """Get the absolute path to a file relative within the repository. The 527039Snate@binkert.org input file name must be a valid path within the repository. 537039Snate@binkert.org 546876Ssteve.reinhardt@amd.com """ 557039Snate@binkert.org return os.path.join(self.repo_base(), fname) 5611169Sandreas.hansson@arm.com 5710518Snilay@cs.wisc.edu def in_repo(self, fname): 587039Snate@binkert.org """Check if a path points to something within the repository base. Not 5911347Sandreas.hansson@arm.com that this does not check for the presence of the object in the 607039Snate@binkert.org file system as it could exist in the index without being in 6111347Sandreas.hansson@arm.com the file system. 6211347Sandreas.hansson@arm.com 6311347Sandreas.hansson@arm.com """ 646285Snate@binkert.org fname = os.path.abspath(fname) 657039Snate@binkert.org repo_path = os.path.abspath(self.repo_base()) 667039Snate@binkert.org 677039Snate@binkert.org return os.path.commonprefix([repo_path, fname]) == repo_path 686285Snate@binkert.org 699104Shestness@cs.utexas.edu def repo_path(self, fname): 709104Shestness@cs.utexas.edu """Get the path of a file relative to the repository base. The input 717039Snate@binkert.org file name is assumed to be an absolute path or a path relative 727039Snate@binkert.org to the current working directory. 7310518Snilay@cs.wisc.edu 747039Snate@binkert.org """ 757039Snate@binkert.org return os.path.relpath(fname, self.repo_base()) 767039Snate@binkert.org 776285Snate@binkert.org def get_file(self, name): 786285Snate@binkert.org """Get the contents of a file in the file system using a path relative 797039Snate@binkert.org to the repository root. 80 81 """ 82 with open(self.file_path(name), "r") as f: 83 return f.read() 84 85 @abstractmethod 86 def repo_base(self): 87 """Get the path to the base of the repository""" 88 pass 89 90 @abstractmethod 91 def staged_files(self): 92 """Get a tuple describing the files that have been staged for a 93 commit: (list of new, list of modified) 94 95 """ 96 pass 97 98 @abstractmethod 99 def staged_regions(self, fname, context=0): 100 """Get modified regions that will be committed by the next commit 101 command 102 103 """ 104 pass 105 106 @abstractmethod 107 def modified_regions(self, fname, context=0): 108 """Get modified regions that have been staged for commit or are 109 present in the file system. 110 111 """ 112 pass 113 114class GitRepo(AbstractRepo): 115 def __init__(self): 116 self.git = "git" 117 self._head_revision = None 118 self._repo_base = None 119 120 def repo_base(self): 121 if self._repo_base is None: 122 self._repo_base = subprocess.check_output( 123 [ self.git, "rev-parse", "--show-toplevel" ]).rstrip("\n") 124 125 return self._repo_base 126 127 def staged_files(self): 128 added = [] 129 modified = [] 130 for action, fname in self.status(filter="MA", cached=True): 131 if action == "M": 132 modified.append(fname) 133 elif action == "A": 134 added.append(fname) 135 136 return added, modified 137 138 def staged_regions(self, fname, context=0): 139 if self.file_status(fname, cached=True) in ("", "A", ): 140 return all_regions 141 142 old = self.file_from_head(self.repo_path(fname)).split("\n") 143 new = self.file_from_index(self.repo_path(fname)).split("\n") 144 145 return modified_regions(old, new, context=context) 146 147 def modified_regions(self, fname, context=0): 148 if self.file_status(fname) in ("", "A", ): 149 return all_regions 150 151 old = self.file_from_head(self.repo_path(fname)).split("\n") 152 new = self.get_file(self.repo_path(fname)).split("\n") 153 154 return modified_regions(old, new, context=context) 155 156 157 def head_revision(self): 158 if self._head_revision is not None: 159 return self._head_revision 160 161 try: 162 self._head_revision = subprocess.check_output( 163 [ self.git, "rev-parse", "--verify", "HEAD" ], 164 stderr=subprocess.PIPE).rstrip("\n") 165 except subprocess.CalledProcessError: 166 # Assume that the repo is empty and use the semi-magic 167 # empty tree revision if git rev-parse returned an error. 168 self._head_revision = "4b825dc642cb6eb9a060e54bf8d69288fbee4904" 169 170 return self._head_revision 171 172 def file_status(self, fname, cached=False): 173 status = self.status(files=[fname], cached=cached) 174 assert len(status) <= 1 175 if status: 176 return status[0][0] 177 else: 178 # No information available for the file. This usually 179 # means that it hasn't been added to the 180 # repository/commit. 181 return "" 182 183 def status(self, filter=None, files=[], cached=False): 184 cmd = [ self.git, "diff-index", "--name-status" ] 185 if cached: 186 cmd.append("--cached") 187 if filter: 188 cmd += [ "--diff-filter", filter ] 189 cmd += [ self.head_revision(), "--" ] + files 190 status = subprocess.check_output(cmd).rstrip("\n") 191 192 if status: 193 return [ f.split("\t") for f in status.split("\n") ] 194 else: 195 return [] 196 197 def file_from_index(self, name): 198 return subprocess.check_output( 199 [ self.git, "show", ":%s" % (name, ) ]) 200 201 def file_from_head(self, name): 202 return subprocess.check_output( 203 [ self.git, "show", "%s:%s" % (self.head_revision(), name) ]) 204 205class MercurialRepo(AbstractRepo): 206 def __init__(self): 207 self.hg = "hg" 208 self._repo_base = None 209 210 def repo_base(self): 211 if self._repo_base is None: 212 self._repo_base = subprocess.check_output( 213 [ self.hg, "root" ]).rstrip("\n") 214 215 return self._repo_base 216 217 def staged_files(self): 218 added = [] 219 modified = [] 220 for action, fname in self.status(): 221 if action == "M": 222 modified.append(fname) 223 elif action == "A": 224 added.append(fname) 225 226 return added, modified 227 228 def staged_regions(self, fname, context=0): 229 return self.modified_regions(fname, context=context) 230 231 def modified_regions(self, fname, context=0): 232 old = self.file_from_tip(fname).split("\n") 233 new = self.get_file(fname).split("\n") 234 235 return modified_regions(old, new, context=context) 236 237 def status(self, filter=None): 238 files = subprocess.check_output([ self.hg, "status" ]).rstrip("\n") 239 if files: 240 return [ f.split(" ") for f in files.split("\n") ] 241 else: 242 return [] 243 244 def file_from_tip(self, name): 245 return subprocess.check_output([ self.hg, "cat", name ]) 246 247def detect_repo(path="."): 248 """Auto-detect the revision control system used for a source code 249 directory. The code starts searching for repository meta data 250 directories in path and then continues towards the root directory 251 until root is reached or a metadatadirectory has been found. 252 253 Returns: List of repository helper classes that can interface with 254 the detected revision control system(s). 255 256 """ 257 258 _repo_types = ( 259 (".git", GitRepo), 260 (".hg", MercurialRepo), 261 ) 262 263 repo_types = [] 264 for repo_dir, repo_class in _repo_types: 265 if os.path.exists(os.path.join(path, repo_dir)): 266 repo_types.append(repo_class) 267 268 if repo_types: 269 return repo_types 270 else: 271 parent_dir = os.path.abspath(os.path.join(path, "..")) 272 if not os.path.samefile(parent_dir, path): 273 return detect_repo(path=parent_dir) 274 else: 275 # We reached the root directory without finding a meta 276 # data directory. 277 return [] 278