##// END OF EJS Templates
feat(configs): deprecared old hooks protocol and ssh wrapper....
feat(configs): deprecared old hooks protocol and ssh wrapper. New defaults are now set on v2 keys, so previous installation are automatically set to new keys. Fallback mode is still available.

File last commit:

r5126:de59bf70 default
r5496:cab50adf default
Show More
commit.py
484 lines | 15.3 KiB | text/x-python | PythonLexer
copyrights: updated for 2023
r5088 # Copyright (C) 2014-2023 RhodeCode GmbH
project: added all source files and assets
r1 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
"""
GIT commit module
"""
git: fix submodule handling for git repositories
r5126 import io
project: added all source files and assets
r1 import stat
python3: fix import configparser
r4927 import configparser
project: added all source files and assets
r1 from itertools import chain
from zope.cachedescriptors.property import Lazy as LazyProperty
dan
vcs: make commit datetimes utc
r154 from rhodecode.lib.datelib import utcdate_fromtimestamp
vcs-lib: bulk of changes for python3 support
r5074 from rhodecode.lib.str_utils import safe_bytes, safe_str
project: added all source files and assets
r1 from rhodecode.lib.vcs.backends import base
from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
from rhodecode.lib.vcs.nodes import (
FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
ChangedFileNodesGenerator, AddedFileNodesGenerator,
largefiles: enabled download of largefiles for git and mercurial from web interface....
r1577 RemovedFileNodesGenerator, LargeFileNode)
project: added all source files and assets
r1
class GitCommit(base.BaseCommit):
"""
Represents state of the repository at single commit id.
"""
_filter_pre_load = [
# done through a more complex tree walk on parents
"affected_files",
# done through subprocess not remote call
"children",
# done through a more complex tree walk on parents
"status",
# mercurial specific property not supported here
"_file_paths",
changelog: fix and optimize loading of chunks for file history....
r2130 # mercurial specific property not supported here
'obsolete',
# mercurial specific property not supported here
'phase',
# mercurial specific property not supported here
'hidden'
project: added all source files and assets
r1 ]
def __init__(self, repository, raw_id, idx, pre_load=None):
self.repository = repository
self._remote = repository._remote
# TODO: johbo: Tweak of raw_id should not be necessary
self.raw_id = safe_str(raw_id)
self.idx = idx
self._set_bulk_properties(pre_load)
# caches
self._stat_modes = {} # stat info for paths
self._paths = {} # path processed with parse_tree
self.nodes = {}
self._submodules = None
def _set_bulk_properties(self, pre_load):
vcs: optimized pre-load attributes for better caching.
r3850
project: added all source files and assets
r1 if not pre_load:
return
pre_load = [entry for entry in pre_load
if entry not in self._filter_pre_load]
if not pre_load:
return
result = self._remote.bulk_request(self.raw_id, pre_load)
for attr, value in result.items():
if attr in ["author", "message"]:
if value:
vcs-lib: bulk of changes for python3 support
r5074 value = safe_str(value)
project: added all source files and assets
r1 elif attr == "date":
dan
vcs: make commit datetimes utc
r154 value = utcdate_fromtimestamp(*value)
project: added all source files and assets
r1 elif attr == "parents":
value = self._make_commits(value)
vcs: optimized pre-load attributes for better caching.
r3850 elif attr == "branch":
git: fix for unicode branches
r4659 value = self._set_branch(value)
project: added all source files and assets
r1 self.__dict__[attr] = value
@LazyProperty
def _commit(self):
return self._remote[self.raw_id]
@LazyProperty
def _tree_id(self):
return self._remote[self._commit['tree']]['id']
@LazyProperty
def id(self):
return self.raw_id
@LazyProperty
def short_id(self):
return self.raw_id[:12]
@LazyProperty
def message(self):
vcs-lib: bulk of changes for python3 support
r5074 return safe_str(self._remote.message(self.id))
project: added all source files and assets
r1
@LazyProperty
def committer(self):
vcs-lib: bulk of changes for python3 support
r5074 return safe_str(self._remote.author(self.id))
project: added all source files and assets
r1
@LazyProperty
def author(self):
vcs-lib: bulk of changes for python3 support
r5074 return safe_str(self._remote.author(self.id))
project: added all source files and assets
r1
@LazyProperty
def date(self):
git: adjusted code for new libgit2 backend...
r3842 unix_ts, tz = self._remote.date(self.raw_id)
dan
vcs: make commit datetimes utc
r154 return utcdate_fromtimestamp(unix_ts, tz)
project: added all source files and assets
r1
@LazyProperty
def status(self):
"""
Returns modified, added, removed, deleted files for current commit
"""
return self.changed, self.added, self.removed
@LazyProperty
def tags(self):
vcs-lib: bulk of changes for python3 support
r5074 tags = [safe_str(name) for name,
python3: removed usage of .iteritems()
r4932 commit_id in self.repository.tags.items()
project: added all source files and assets
r1 if commit_id == self.raw_id]
return tags
@LazyProperty
git: adjusted code for new libgit2 backend...
r3842 def commit_branches(self):
branches = []
python3: removed usage of .iteritems()
r4932 for name, commit_id in self.repository.branches.items():
project: added all source files and assets
r1 if commit_id == self.raw_id:
git: adjusted code for new libgit2 backend...
r3842 branches.append(name)
return branches
git: fix for unicode branches
r4659 def _set_branch(self, branches):
if branches:
# actually commit can have multiple branches in git
vcs-lib: bulk of changes for python3 support
r5074 return safe_str(branches[0])
git: fix for unicode branches
r4659
git: adjusted code for new libgit2 backend...
r3842 @LazyProperty
def branch(self):
core: added more accurate time measurement for called functions
r3853 branches = self._remote.branch(self.raw_id)
git: fix for unicode branches
r4659 return self._set_branch(branches)
project: added all source files and assets
r1
git: adjusted code for new libgit2 backend...
r3842 def _get_tree_id_for_path(self, path):
vcs-lib: bulk of changes for python3 support
r5074
project: added all source files and assets
r1 path = safe_str(path)
if path in self._paths:
return self._paths[path]
tree_id = self._tree_id
path = path.strip('/')
if path == '':
data = [tree_id, "tree"]
self._paths[''] = data
return data
git: adjusted code for new libgit2 backend...
r3842 tree_id, tree_type, tree_mode = \
self._remote.tree_and_type_for_path(self.raw_id, path)
if tree_id is None:
raise self.no_node_at_path(path)
project: added all source files and assets
r1
git: adjusted code for new libgit2 backend...
r3842 self._paths[path] = [tree_id, tree_type]
self._stat_modes[path] = tree_mode
project: added all source files and assets
r1
if path not in self._paths:
raise self.no_node_at_path(path)
return self._paths[path]
def _get_kind(self, path):
git: adjusted code for new libgit2 backend...
r3842 tree_id, type_ = self._get_tree_id_for_path(path)
project: added all source files and assets
r1 if type_ == 'blob':
return NodeKind.FILE
elif type_ == 'tree':
return NodeKind.DIR
git: adjusted code for new libgit2 backend...
r3842 elif type_ == 'link':
project: added all source files and assets
r1 return NodeKind.SUBMODULE
return None
vcs-lib: bulk of changes for python3 support
r5074 def _assert_is_path(self, path):
project: added all source files and assets
r1 path = self._fix_path(path)
if self._get_kind(path) != NodeKind.FILE:
vcs-lib: bulk of changes for python3 support
r5074 raise CommitError(f"File does not exist for commit {self.raw_id} at '{path}'")
project: added all source files and assets
r1 return path
def _get_file_nodes(self):
return chain(*(t[2] for t in self.walk()))
@LazyProperty
def parents(self):
"""
Returns list of parent commits.
"""
git: adjusted code for new libgit2 backend...
r3842 parent_ids = self._remote.parents(self.id)
project: added all source files and assets
r1 return self._make_commits(parent_ids)
@LazyProperty
def children(self):
"""
Returns list of child commits.
"""
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 children = self._remote.children(self.raw_id)
return self._make_commits(children)
project: added all source files and assets
r1
git: adjusted code for new libgit2 backend...
r3842 def _make_commits(self, commit_ids):
def commit_maker(_commit_id):
vcs-lib: bulk of changes for python3 support
r5074 return self.repository.get_commit(commit_id=_commit_id)
git: adjusted code for new libgit2 backend...
r3842
return [commit_maker(commit_id) for commit_id in commit_ids]
project: added all source files and assets
r1
vcs-lib: bulk of changes for python3 support
r5074 def get_file_mode(self, path: bytes):
project: added all source files and assets
r1 """
Returns stat mode of the file at the given `path`.
"""
vcs-lib: bulk of changes for python3 support
r5074 path = self._assert_is_path(path)
project: added all source files and assets
r1 # ensure path is traversed
git: adjusted code for new libgit2 backend...
r3842 self._get_tree_id_for_path(path)
vcs-lib: bulk of changes for python3 support
r5074
project: added all source files and assets
r1 return self._stat_modes[path]
def is_link(self, path):
return stat.S_ISLNK(self.get_file_mode(path))
dan
vcsserver: made binary content check be calculated on vcsserver...
r3896 def is_node_binary(self, path):
tree_id, _ = self._get_tree_id_for_path(path)
return self._remote.is_binary(tree_id)
vcs-lib: bulk of changes for python3 support
r5074 def node_md5_hash(self, path):
path = self._assert_is_path(path)
return self._remote.md5_hash(self.raw_id, path)
project: added all source files and assets
r1 def get_file_content(self, path):
"""
Returns content of the file at given `path`.
"""
git: adjusted code for new libgit2 backend...
r3842 tree_id, _ = self._get_tree_id_for_path(path)
return self._remote.blob_as_pretty_string(tree_id)
project: added all source files and assets
r1
dan
file-nodes: added streaming remote attributes for vcsserver....
r3895 def get_file_content_streamed(self, path):
tree_id, _ = self._get_tree_id_for_path(path)
stream_method = getattr(self._remote, 'stream:blob_as_pretty_string')
return stream_method(tree_id)
project: added all source files and assets
r1 def get_file_size(self, path):
"""
Returns size of the file at given `path`.
"""
git: adjusted code for new libgit2 backend...
r3842 tree_id, _ = self._get_tree_id_for_path(path)
return self._remote.blob_raw_length(tree_id)
project: added all source files and assets
r1
vcs: rename get_file_history to get_path_history as it better reflects what it does.
r3275 def get_path_history(self, path, limit=None, pre_load=None):
project: added all source files and assets
r1 """
Returns history of file as reversed list of `GitCommit` objects for
which file at given `path` has been modified.
"""
vcs-lib: bulk of changes for python3 support
r5074 path = self._assert_is_path(path)
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 hist = self._remote.node_history(self.raw_id, path, limit)
project: added all source files and assets
r1 return [
self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 for commit_id in hist]
project: added all source files and assets
r1
def get_file_annotate(self, path, pre_load=None):
"""
Returns a generator of four element tuples with
lineno, commit_id, commit lazy loader and line
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 """
project: added all source files and assets
r1
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 result = self._remote.node_annotate(self.raw_id, path)
project: added all source files and assets
r1
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 for ln_no, commit_id, content in result:
project: added all source files and assets
r1 yield (
git: replaced some raw subprocess commands with dedicated GIT vcsserver commands.
r3862 ln_no, commit_id,
lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
content)
project: added all source files and assets
r1
vcs-lib: bulk of changes for python3 support
r5074 def get_nodes(self, path, pre_load=None):
git: adjusted code for new libgit2 backend...
r3842
project: added all source files and assets
r1 if self._get_kind(path) != NodeKind.DIR:
raise CommitError(
vcs-lib: bulk of changes for python3 support
r5074 f"Directory does not exist for commit {self.raw_id} at '{path}'")
project: added all source files and assets
r1 path = self._fix_path(path)
git: adjusted code for new libgit2 backend...
r3842
tree_id, _ = self._get_tree_id_for_path(path)
project: added all source files and assets
r1 dirnodes = []
filenodes = []
git: adjusted code for new libgit2 backend...
r3842
# extracted tree ID gives us our files...
git: fix submodule handling for git repositories
r5126 str_path = safe_str(path) # libgit operates on bytes
project: added all source files and assets
r1 for name, stat_, id_, type_ in self._remote.tree_items(tree_id):
if type_ == 'link':
git: fix submodule handling for git repositories
r5126 url = self._get_submodule_url('/'.join((str_path, name)))
project: added all source files and assets
r1 dirnodes.append(SubModuleNode(
git: adjusted code for new libgit2 backend...
r3842 name, url=url, commit=id_, alias=self.repository.alias))
project: added all source files and assets
r1 continue
git: fix submodule handling for git repositories
r5126 if str_path != '':
obj_path = '/'.join((str_path, name))
project: added all source files and assets
r1 else:
obj_path = name
if obj_path not in self._stat_modes:
self._stat_modes[obj_path] = stat_
if type_ == 'tree':
vcs-lib: bulk of changes for python3 support
r5074 dirnodes.append(DirNode(safe_bytes(obj_path), commit=self))
project: added all source files and assets
r1 elif type_ == 'blob':
vcs-lib: bulk of changes for python3 support
r5074 filenodes.append(FileNode(safe_bytes(obj_path), commit=self, mode=stat_, pre_load=pre_load))
project: added all source files and assets
r1 else:
vcs-lib: bulk of changes for python3 support
r5074 raise CommitError(f"Requested object should be Tree or Blob, is {type_}")
project: added all source files and assets
r1
nodes = dirnodes + filenodes
for node in nodes:
if node.path not in self.nodes:
self.nodes[node.path] = node
nodes.sort()
return nodes
vcs: added possibility to pre-load attributes for FileNodes.
r1355 def get_node(self, path, pre_load=None):
project: added all source files and assets
r1 path = self._fix_path(path)
if path not in self.nodes:
try:
git: adjusted code for new libgit2 backend...
r3842 tree_id, type_ = self._get_tree_id_for_path(path)
project: added all source files and assets
r1 except CommitError:
raise NodeDoesNotExistError(
vcs-lib: bulk of changes for python3 support
r5074 f"Cannot find one of parents' directories for a given "
f"path: {path}")
project: added all source files and assets
r1
git: fixed issue with git submodules detection.
r4115 if type_ in ['link', 'commit']:
project: added all source files and assets
r1 url = self._get_submodule_url(path)
git: adjusted code for new libgit2 backend...
r3842 node = SubModuleNode(path, url=url, commit=tree_id,
project: added all source files and assets
r1 alias=self.repository.alias)
elif type_ == 'tree':
if path == '':
node = RootNode(commit=self)
else:
vcs-lib: bulk of changes for python3 support
r5074 node = DirNode(safe_bytes(path), commit=self)
project: added all source files and assets
r1 elif type_ == 'blob':
vcs-lib: bulk of changes for python3 support
r5074 node = FileNode(safe_bytes(path), commit=self, pre_load=pre_load)
git: adjusted code for new libgit2 backend...
r3842 self._stat_modes[path] = node.mode
project: added all source files and assets
r1 else:
raise self.no_node_at_path(path)
# cache node
self.nodes[path] = node
git: adjusted code for new libgit2 backend...
r3842
project: added all source files and assets
r1 return self.nodes[path]
largefiles: enabled download of largefiles for git and mercurial from web interface....
r1577 def get_largefile_node(self, path):
git: adjusted code for new libgit2 backend...
r3842 tree_id, _ = self._get_tree_id_for_path(path)
pointer_spec = self._remote.is_large_file(tree_id)
largefiles: enabled download of largefiles for git and mercurial from web interface....
r1577
if pointer_spec:
# content of that file regular FileNode is the hash of largefile
file_id = pointer_spec.get('oid_hash')
if self._remote.in_largefiles_store(file_id):
lf_path = self._remote.store_path(file_id)
vcs-lib: bulk of changes for python3 support
r5074 return LargeFileNode(safe_bytes(lf_path), commit=self, org_path=path)
largefiles: enabled download of largefiles for git and mercurial from web interface....
r1577
project: added all source files and assets
r1 @LazyProperty
def affected_files(self):
"""
Gets a fast accessible file changes for given commit
"""
added, modified, deleted = self._changes_cache
return list(added.union(modified).union(deleted))
@LazyProperty
def _changes_cache(self):
added = set()
modified = set()
deleted = set()
parents = self.parents
if not self.parents:
parents = [base.EmptyCommit()]
for parent in parents:
if isinstance(parent, base.EmptyCommit):
oid = None
else:
oid = parent.raw_id
vcs-lib: bulk of changes for python3 support
r5074 _added, _modified, _deleted = self._remote.tree_changes(oid, self.raw_id)
added = added | set(_added)
modified = modified | set(_modified)
deleted = deleted | set(_deleted)
project: added all source files and assets
r1 return added, modified, deleted
def _get_paths_for_status(self, status):
"""
Returns sorted list of paths for given ``status``.
:param status: one of: *added*, *modified* or *deleted*
"""
added, modified, deleted = self._changes_cache
return sorted({
'added': list(added),
'modified': list(modified),
'deleted': list(deleted)}[status]
)
@LazyProperty
def added(self):
"""
Returns list of added ``FileNode`` objects.
"""
if not self.parents:
return list(self._get_file_nodes())
api: exposed modified added/modified/deleted functions of commit to return only paths....
r4242 return AddedFileNodesGenerator(self.added_paths, self)
@LazyProperty
def added_paths(self):
return [n for n in self._get_paths_for_status('added')]
project: added all source files and assets
r1
@LazyProperty
def changed(self):
"""
Returns list of modified ``FileNode`` objects.
"""
if not self.parents:
return []
api: exposed modified added/modified/deleted functions of commit to return only paths....
r4242 return ChangedFileNodesGenerator(self.changed_paths, self)
@LazyProperty
def changed_paths(self):
return [n for n in self._get_paths_for_status('modified')]
project: added all source files and assets
r1
@LazyProperty
def removed(self):
"""
Returns list of removed ``FileNode`` objects.
"""
if not self.parents:
return []
api: exposed modified added/modified/deleted functions of commit to return only paths....
r4242 return RemovedFileNodesGenerator(self.removed_paths, self)
@LazyProperty
def removed_paths(self):
return [n for n in self._get_paths_for_status('deleted')]
project: added all source files and assets
r1
def _get_submodule_url(self, submodule_path):
git_modules_path = '.gitmodules'
if self._submodules is None:
self._submodules = {}
try:
submodules_node = self.get_node(git_modules_path)
except NodeDoesNotExistError:
return None
git: use safe configparser for git submodules to prevent from errors on submodules with % sign....
r4239 parser = configparser.RawConfigParser()
git: fix submodule handling for git repositories
r5126 parser.read_file(io.StringIO(submodules_node.str_content))
project: added all source files and assets
r1
for section in parser.sections():
path = parser.get(section, 'path')
url = parser.get(section, 'url')
if path and url:
self._submodules[path.strip('/')] = url
return self._submodules.get(submodule_path.strip('/'))