3 # Copyright (c) 2016 ARM Limited
6 # The license below extends only to copyright in the software and shall
7 # not be construed as granting a license to any other intellectual
8 # property including but not limited to intellectual property relating
9 # to a hardware implementation of the functionality of the software
10 # licensed hereunder. You may use the software subject to the license
11 # terms below provided that you ensure that this notice is replicated
12 # unmodified and in its entirety in all distributions of the software,
13 # modified or unmodified, in source code or in binary form.
15 # Redistribution and use in source and binary forms, with or without
16 # modification, are permitted provided that the following conditions are
17 # met: redistributions of source code must retain the above copyright
18 # notice, this list of conditions and the following disclaimer;
19 # redistributions in binary form must reproduce the above copyright
20 # notice, this list of conditions and the following disclaimer in the
21 # documentation and/or other materials provided with the distribution;
22 # neither the name of the copyright holders nor the names of its
23 # contributors may be used to endorse or promote products derived from
24 # this software without specific prior written permission.
26 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 # Authors: Andreas Sandberg
45 from style
import modified_regions
47 class AbstractRepo(object):
48 __metaclass__
= ABCMeta
50 def file_path(self
, fname
):
51 """Get the absolute path to a file relative within the repository. The
52 input file name must be a valid path within the repository.
55 return os
.path
.join(self
.repo_base(), fname
)
57 def in_repo(self
, fname
):
58 """Check if a path points to something within the repository base. Not
59 that this does not check for the presence of the object in the
60 file system as it could exist in the index without being in
64 fname
= os
.path
.abspath(fname
)
65 repo_path
= os
.path
.abspath(self
.repo_base())
67 return os
.path
.commonprefix([repo_path
, fname
]) == repo_path
69 def repo_path(self
, fname
):
70 """Get the path of a file relative to the repository base. The input
71 file name is assumed to be an absolute path or a path relative
72 to the current working directory.
75 return os
.path
.relpath(fname
, self
.repo_base())
77 def get_file(self
, name
):
78 """Get the contents of a file in the file system using a path relative
79 to the repository root.
82 with
open(self
.file_path(name
), "r") as f
:
87 """Get the path to the base of the repository"""
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)
99 def staged_regions(self
, fname
, context
=0):
100 """Get modified regions that will be committed by the next commit
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.
114 class GitRepo(AbstractRepo
):
117 self
._head
_revision
= None
118 self
._repo
_base
= None
121 if self
._repo
_base
is None:
122 self
._repo
_base
= subprocess
.check_output(
123 [ self
.git
, "rev-parse", "--show-toplevel" ]).rstrip("\n")
125 return self
._repo
_base
127 def staged_files(self
):
130 for action
, fname
in self
.status(filter="MA", cached
=True):
132 modified
.append(fname
)
136 return added
, modified
138 def staged_regions(self
, fname
, context
=0):
139 if self
.file_status(fname
, cached
=True) in ("", "A", ):
142 old
= self
.file_from_head(self
.repo_path(fname
)).split("\n")
143 new
= self
.file_from_index(self
.repo_path(fname
)).split("\n")
145 return modified_regions(old
, new
, context
=context
)
147 def modified_regions(self
, fname
, context
=0):
148 if self
.file_status(fname
) in ("", "A", ):
151 old
= self
.file_from_head(self
.repo_path(fname
)).split("\n")
152 new
= self
.get_file(self
.repo_path(fname
)).split("\n")
154 return modified_regions(old
, new
, context
=context
)
157 def head_revision(self
):
158 if self
._head
_revision
is not None:
159 return self
._head
_revision
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"
170 return self
._head
_revision
172 def file_status(self
, fname
, cached
=False):
173 status
= self
.status(files
=[fname
], cached
=cached
)
174 assert len(status
) <= 1
178 # No information available for the file. This usually
179 # means that it hasn't been added to the
183 def status(self
, filter=None, files
=[], cached
=False):
184 cmd
= [ self
.git
, "diff-index", "--name-status" ]
186 cmd
.append("--cached")
188 cmd
+= [ "--diff-filter=%s" % filter ]
189 cmd
+= [ self
.head_revision(), "--" ] + files
190 status
= subprocess
.check_output(cmd
).rstrip("\n")
193 return [ f
.split("\t") for f
in status
.split("\n") ]
197 def file_from_index(self
, name
):
198 return subprocess
.check_output(
199 [ self
.git
, "show", ":%s" % (name
, ) ])
201 def file_from_head(self
, name
):
202 return subprocess
.check_output(
203 [ self
.git
, "show", "%s:%s" % (self
.head_revision(), name
) ])
205 class MercurialRepo(AbstractRepo
):
208 self
._repo
_base
= None
211 if self
._repo
_base
is None:
212 self
._repo
_base
= subprocess
.check_output(
213 [ self
.hg
, "root" ]).rstrip("\n")
215 return self
._repo
_base
217 def staged_files(self
):
220 for action
, fname
in self
.status():
222 modified
.append(fname
)
226 return added
, modified
228 def staged_regions(self
, fname
, context
=0):
229 return self
.modified_regions(fname
, context
=context
)
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")
235 return modified_regions(old
, new
, context
=context
)
237 def status(self
, filter=None):
238 files
= subprocess
.check_output([ self
.hg
, "status" ]).rstrip("\n")
240 return [ f
.split(" ") for f
in files
.split("\n") ]
244 def file_from_tip(self
, name
):
245 return subprocess
.check_output([ self
.hg
, "cat", name
])
247 def 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.
253 Returns: List of repository helper classes that can interface with
254 the detected revision control system(s).
260 (".hg", MercurialRepo
),
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
)
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
)
275 # We reached the root directory without finding a meta