diff --git a/.gitignore b/.gitignore
index d1c7edd4..9603c0b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,6 +14,7 @@ rhodecode/public/js/src/components/**/*.css
/.project
/.pydevproject
/.coverage
+.coverage.*
/.cache*
/.ruff_cache*
/.rhodecode
diff --git a/Makefile b/Makefile
index 5f2d1a5d..cb1e602d 100644
--- a/Makefile
+++ b/Makefile
@@ -84,13 +84,22 @@ test-only:
rhodecode/tests/database
.PHONY: test-simple
-# test-simple: Run tests only for main test suite witout coverage
+# test-simple: Run tests only for main test suite without coverage
test-simple:
PYTHONHASHSEED=random \
py.test -x -vv -r xw -p no:sugar \
--ignore=rhodecode/tests/vcs_operations \
--ignore=rhodecode/tests/database
+
+.PHONY: test-custom
+# test-custom: Run tests without coverage, only run tests matching PATTERN
+test-custom:
+ PYTHONHASHSEED=random \
+ py.test $(if $(PATTERN),-k "$(PATTERN)",) -vv -r xw -p no:sugar \
+ --ignore=rhodecode/tests/vcs_operations \
+ --ignore=rhodecode/tests/database
+
# >>> Docs commands
.PHONY: docs
diff --git a/README.rst b/README.rst
index cccf2b17..ca2e419a 100644
--- a/README.rst
+++ b/README.rst
@@ -37,7 +37,7 @@ RhodeCode uses `PEP386 versioning `_
Installation
------------
-Please visit https://docs.rhodecode.com/RhodeCode-Control/tasks/install-cli.html
+Please visit https://docs.rhodecode.com/5.x/rcstack/install/installation.html
for more details
@@ -56,7 +56,7 @@ RhodeCode is open-source; contributions are welcome!
Please see the contribution documentation inside of the docs folder, which is
also available at
-https://docs.rhodecode.com/RhodeCode-Enterprise/contributing/contributing.html
+https://docs.rhodecode.com/5.x/rce/contributing/overview.html
For additional information about collaboration tools, our issue tracker,
licensing, and contribution credit, visit https://rhodecode.com/open-source
diff --git a/docs/release-notes/release-notes-5.5.1.rst b/docs/release-notes/release-notes-5.5.1.rst
new file mode 100644
index 00000000..d6a20536
--- /dev/null
+++ b/docs/release-notes/release-notes-5.5.1.rst
@@ -0,0 +1,40 @@
+|RCE| 5.5.1 |RNS|
+-----------------
+
+Release Date
+^^^^^^^^^^^^
+
+- 2025-04-07
+
+New Features
+^^^^^^^^^^^^
+
+
+
+General
+^^^^^^^
+
+
+Security
+^^^^^^^^
+
+- API: changed impersonate features to only be allowed by super-admins instead of repo admins.
+ This applies better practices to this feature of API.
+
+Performance
+^^^^^^^^^^^
+
+
+
+Fixes
+^^^^^
+
+- Fixed issues with caches key on windows WSL that were over 255 characters
+- Fix for SVN dumps with empty revisions
+- Fix for slack integration now showing proper actor for PR-related events
+
+Upgrade notes
+^^^^^^^^^^^^^
+
+- RhodeCode 5.5.1 is a planned bugfix release.
+
diff --git a/docs/release-notes/release-notes.rst b/docs/release-notes/release-notes.rst
index 58763251..fd3584b1 100644
--- a/docs/release-notes/release-notes.rst
+++ b/docs/release-notes/release-notes.rst
@@ -9,6 +9,7 @@ Release Notes
.. toctree::
:maxdepth: 1
+ release-notes-5.5.1.rst
release-notes-5.5.0.rst
release-notes-5.4.2.rst
release-notes-5.4.1.rst
diff --git a/docs/requirements_docs.txt b/docs/requirements_docs.txt
index 19dfa5a9..aa3e8a92 100644
--- a/docs/requirements_docs.txt
+++ b/docs/requirements_docs.txt
@@ -8,4 +8,4 @@ pygments==2.18.0
docutils<0.19
markupsafe==2.1.3
-jinja2==3.1.5
+jinja2==3.1.6
diff --git a/pyproject.toml b/pyproject.toml
index 5a522e97..8e240a1d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -117,7 +117,7 @@ line-ending = "auto"
[tool.bumpversion]
-current_version = "5.5.0"
+current_version = "5.6.0"
parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
diff --git a/requirements.txt b/requirements.txt
index e18d0aaa..f2c82d1c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -48,7 +48,7 @@ channelstream==0.7.1
zope.deprecation==5.1.0
zope.interface==7.2.0
pyramid-jinja2==2.10
- jinja2==3.1.5
+ jinja2==3.1.6
markupsafe==3.0.2
markupsafe==3.0.2
pyramid==2.0.2
@@ -122,7 +122,7 @@ nbconvert==7.7.3
six==1.16.0
webencodings==0.5.1
defusedxml==0.7.1
- jinja2==3.1.5
+ jinja2==3.1.6
markupsafe==3.0.2
jupyter_core==5.3.1
platformdirs==3.10.0
diff --git a/rhodecode/VERSION b/rhodecode/VERSION
index c7ba1e87..4cc0e35c 100644
--- a/rhodecode/VERSION
+++ b/rhodecode/VERSION
@@ -1 +1 @@
-5.5.0
\ No newline at end of file
+5.6.0
\ No newline at end of file
diff --git a/rhodecode/api/tests/test_close_pull_request.py b/rhodecode/api/tests/test_close_pull_request.py
index b9064039..d035824d 100644
--- a/rhodecode/api/tests/test_close_pull_request.py
+++ b/rhodecode/api/tests/test_close_pull_request.py
@@ -91,7 +91,7 @@ class TestClosePullRequest(object):
)
response = api_call(self.app, params)
- expected = "userid is not the same as your user"
+ expected = "Provided userid is not the same the user calling this function"
assert_error(id_, expected, given=response.body)
@pytest.mark.backends("git", "hg")
diff --git a/rhodecode/api/tests/test_comment_pull_request.py b/rhodecode/api/tests/test_comment_pull_request.py
index ba68a0d3..838aeb32 100644
--- a/rhodecode/api/tests/test_comment_pull_request.py
+++ b/rhodecode/api/tests/test_comment_pull_request.py
@@ -234,7 +234,7 @@ class TestCommentPullRequest(object):
)
response = api_call(self.app, params)
- expected = "userid is not the same as your user"
+ expected = "Provided userid is not the same the user calling this function"
assert_error(id_, expected, given=response.body)
@pytest.mark.backends("git", "hg")
@@ -360,7 +360,7 @@ class TestCommentPullRequest(object):
userid=TEST_USER_ADMIN_LOGIN,
)
response = api_call(self.app, params)
- expected = "userid is not the same as your user"
+ expected = "Provided userid is not the same the user calling this function"
assert_error(id_, expected, given=response.body)
@pytest.mark.backends("git", "hg")
diff --git a/rhodecode/api/tests/test_get_repo_changeset.py b/rhodecode/api/tests/test_get_repo_changeset.py
index 36580ad1..464c538d 100644
--- a/rhodecode/api/tests/test_get_repo_changeset.py
+++ b/rhodecode/api/tests/test_get_repo_changeset.py
@@ -155,3 +155,27 @@ class TestGetRepoChangeset(object):
response = api_call(self.app, params)
expected = "commit_id must be a string value got instead"
assert_error(id_, expected, given=response.body)
+
+ @pytest.mark.skip(reason="This test is currently broken")
+ @pytest.mark.parametrize("details", ["basic", "extended", "full"])
+ def test_get_repo_changeset_empty_revs(self, details, backend_svn):
+ """
+ Test case for repositories with empty revisions.
+ Ensures that no crashes occur and an empty list is returned.
+ """
+ empty_rev_id = "1000"
+
+ __, params = build_data(
+ self.apikey,
+ "get_repo_changesets",
+ repoid=backend_svn.repo_name,
+ start_rev=empty_rev_id,
+ limit=10,
+ details=details,
+ )
+
+ response = api_call(self.app, params)
+ result = response.json["result"]
+
+ assert isinstance(result, list)
+ assert len(result) == 0
diff --git a/rhodecode/api/tests/test_merge_pull_request.py b/rhodecode/api/tests/test_merge_pull_request.py
index 9f9e22e8..cc5c951b 100644
--- a/rhodecode/api/tests/test_merge_pull_request.py
+++ b/rhodecode/api/tests/test_merge_pull_request.py
@@ -263,5 +263,5 @@ class TestMergePullRequest(object):
)
response = api_call(self.app, params)
- expected = "userid is not the same as your user"
+ expected = "Provided userid is not the same the user calling this function"
assert_error(id_, expected, given=response.body)
diff --git a/rhodecode/api/views/pull_request_api.py b/rhodecode/api/views/pull_request_api.py
index 59b5fa29..0e499349 100644
--- a/rhodecode/api/views/pull_request_api.py
+++ b/rhodecode/api/views/pull_request_api.py
@@ -147,7 +147,7 @@ def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None), mer
repo = pull_request.target_repo
if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
- raise JSONRPCError("repository `%s` or pull request `%s` does not exist" % (repoid, pullrequestid))
+ raise JSONRPCError(f"repository `{repoid}` or pull request `{pullrequestid}` does not exist")
# NOTE(marcink): only calculate and return merge state if the pr state is 'created'
# otherwise we can lock the repo on calculation of merge state while update/merge
@@ -302,21 +302,19 @@ def merge_pull_request(request, apiuser, pullrequestid, repoid=Optional(None), u
repo = get_repo_or_error(repoid)
else:
repo = pull_request.target_repo
- auth_user = apiuser
+ auth_user = apiuser
if not isinstance(userid, Optional):
- is_repo_admin = HasRepoPermissionAnyApi("repository.admin")(user=apiuser, repo_name=repo.repo_name)
- if has_superadmin_permission(apiuser) or is_repo_admin:
+ # super-admin can impersonate other users
+ if has_superadmin_permission(apiuser):
apiuser = get_user_or_error(userid)
auth_user = apiuser.AuthUser()
else:
- raise JSONRPCError("userid is not the same as your user")
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
if pull_request.pull_request_state != PullRequest.STATE_CREATED:
raise JSONRPCError(
- "Operation forbidden because pull request is in state {}, only state {} is allowed.".format(
- pull_request.pull_request_state, PullRequest.STATE_CREATED
- )
+ f"Operation forbidden because pull request is in state {pull_request.pull_request_state}, only state {PullRequest.STATE_CREATED} is allowed."
)
with pull_request.set_state(PullRequest.STATE_UPDATING):
@@ -533,20 +531,26 @@ def comment_pull_request(
repo = pull_request.target_repo
db_repo_name = repo.repo_name
+
auth_user = apiuser
- if not isinstance(userid, Optional):
- is_repo_admin = HasRepoPermissionAnyApi("repository.admin")(user=apiuser, repo_name=db_repo_name)
- if has_superadmin_permission(apiuser) or is_repo_admin:
+ if isinstance(userid, Optional):
+ userid = apiuser.user_id
+ else:
+ # super-admin can impersonate other users
+ if has_superadmin_permission(apiuser):
apiuser = get_user_or_error(userid)
+ userid = apiuser.user_id
auth_user = apiuser.AuthUser()
else:
- raise JSONRPCError("userid is not the same as your user")
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
if pull_request.is_closed():
raise JSONRPCError(f"pull request `{pullrequestid}` comment failed, pull request is closed")
if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
raise JSONRPCError(f"repository `{repoid}` does not exist")
+
+ user = get_user_or_error(userid)
message = Optional.extract(message)
status = Optional.extract(status)
commit_id = Optional.extract(commit_id)
@@ -594,7 +598,7 @@ def comment_pull_request(
comment = CommentsModel().create(
text=text,
repo=pull_request.target_repo.repo_id,
- user=apiuser.user_id,
+ user=user,
pull_request=pull_request.pull_request_id,
f_path=None,
line_no=None,
@@ -928,9 +932,9 @@ def update_pull_request(
repo = pull_request.target_repo
if not PullRequestModel().check_user_update(pull_request, apiuser, api=True):
- raise JSONRPCError("pull request `{}` update failed, no permission to update.".format(pullrequestid))
+ raise JSONRPCError(f"pull request `{pullrequestid}` update failed, no permission to update.")
if pull_request.is_closed():
- raise JSONRPCError("pull request `{}` update failed, pull request is closed".format(pullrequestid))
+ raise JSONRPCError(f"pull request `{pullrequestid}` update failed, pull request is closed")
reviewer_objects = Optional.extract(reviewers) or []
observer_objects = Optional.extract(observers) or []
@@ -959,9 +963,7 @@ def update_pull_request(
if str2bool(Optional.extract(update_commits)):
if pull_request.pull_request_state != PullRequest.STATE_CREATED:
raise JSONRPCError(
- "Operation forbidden because pull request is in state {}, only state {} is allowed.".format(
- pull_request.pull_request_state, PullRequest.STATE_CREATED
- )
+ f"Operation forbidden because pull request is in state {pull_request.pull_request_state}, only state {PullRequest.STATE_CREATED} is allowed."
)
with pull_request.set_state(PullRequest.STATE_UPDATING):
@@ -1092,12 +1094,13 @@ def close_pull_request(
else:
repo = pull_request.target_repo
- is_repo_admin = HasRepoPermissionAnyApi("repository.admin")(user=apiuser, repo_name=repo.repo_name)
+ auth_user = apiuser
if not isinstance(userid, Optional):
- if has_superadmin_permission(apiuser) or is_repo_admin:
- apiuser = get_user_or_error(userid)
+ if has_superadmin_permission(apiuser):
+ # super-admin can impersonate other users
+ auth_user = get_user_or_error(userid)
else:
- raise JSONRPCError("userid is not the same as your user")
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
if pull_request.is_closed():
raise JSONRPCError(f"pull request `{pullrequestid}` is already closed")
@@ -1106,14 +1109,14 @@ def close_pull_request(
allowed_to_close = PullRequestModel().check_user_update(pull_request, apiuser, api=True)
if not allowed_to_close:
- raise JSONRPCError("pull request `{}` close failed, no permission to close.".format(pullrequestid))
+ raise JSONRPCError(f"pull request `{pullrequestid}` close failed, no permission to close.")
# message we're using to close the PR, else it's automatically generated
message = Optional.extract(message)
# finally close the PR, with proper message comment
comment, status = PullRequestModel().close_pull_request_with_comment(
- pull_request, apiuser, repo, message=message, auth_user=apiuser
+ pull_request, apiuser, repo, message=message, auth_user=auth_user
)
status_lbl = ChangesetStatus.get_status_lbl(status)
diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py
index 4dd11cee..bdd90be6 100644
--- a/rhodecode/api/views/repo_api.py
+++ b/rhodecode/api/views/repo_api.py
@@ -43,7 +43,7 @@ from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str, safe_int
from rhodecode.lib.ext_json import json
from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
from rhodecode.lib.vcs import RepositoryError
-from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
+from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError, EmptyRepositoryError
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.comment import CommentsModel
from rhodecode.model.db import Session, ChangesetStatus, RepositoryField, Repository, RepoGroup, ChangesetComment
@@ -393,6 +393,8 @@ def get_repo_changesets(request, apiuser, repoid, start_rev, limit, details=Opti
commits = vcs_repo.get_commits(start_id=start_rev, pre_load=pre_load, translate_tags=False)
except TypeError as e:
raise JSONRPCError(safe_str(e))
+ except EmptyRepositoryError:
+ return []
except Exception:
log.exception("Fetching of commits failed")
raise JSONRPCError("Error occurred during commit fetching")
@@ -600,7 +602,7 @@ def get_repo_file(
_extended_types = ["minimal", "minimal+search", "basic", "full"]
if details not in _extended_types:
ret_types = ",".join(_extended_types)
- raise JSONRPCError(f"ret_type must be one of %s, got {ret_types}", details)
+ raise JSONRPCError(f"ret_type must be one of {_extended_types}, got {ret_types}", details)
extended_info = False
content = False
@@ -695,7 +697,7 @@ def get_repo_fts_tree(request, apiuser, repoid, commit_id, root_path):
except Exception:
log.exception("Exception occurred while trying to get repo nodes")
- raise JSONRPCError("failed to get repo: `%s` nodes" % repo.repo_name)
+ raise JSONRPCError(f"failed to get repo: `{repo.repo_name}` nodes")
@jsonrpc_method()
@@ -755,7 +757,7 @@ def get_repo_refs(request, apiuser, repoid):
return refs
except Exception:
log.exception("Exception occurred while trying to get repo refs")
- raise JSONRPCError("failed to get repo: `%s` references" % repo.repo_name)
+ raise JSONRPCError(f"failed to get repo: `{repo.repo_name}` references")
@jsonrpc_method()
@@ -918,14 +920,14 @@ def create_repo(
task_id = get_task_id(task)
# no commit, it's done in RepoModel, or async via celery
return {
- "msg": "Created new repository `{}`".format(schema_data["repo_name"]),
+ "msg": f"Created new repository `{schema_data['repo_name']}`",
"success": True, # cannot return the repo data here since fork
# can be done async
"task": task_id,
}
except Exception:
log.exception("Exception while trying to create the repository %s", schema_data["repo_name"])
- raise JSONRPCError("failed to create repository `{}`".format(schema_data["repo_name"]))
+ raise JSONRPCError(f"failed to create repository `{schema_data['repo_name']}`")
@jsonrpc_method()
@@ -994,7 +996,7 @@ def remove_field_from_repo(request, apiuser, repoid, key):
field = RepositoryField.get_by_key_name(key, repo)
if not field:
- raise JSONRPCError("Field with key `%s` does not exists for repo `%s`" % (key, repoid))
+ raise JSONRPCError(f"Field with key `{key}` does not exists for repo `{repoid}`")
try:
RepoModel().delete_repo_field(repo, field_key=key)
@@ -1165,7 +1167,7 @@ def update_repo(
}
except Exception:
log.exception("Exception while trying to update the repository %s", repoid)
- raise JSONRPCError("failed to update repo `%s`" % repoid)
+ raise JSONRPCError(f"failed to update repo `{repoid}`")
@jsonrpc_method()
@@ -1310,14 +1312,14 @@ def fork_repo(
task_id = get_task_id(task)
return {
- "msg": "Created fork of `{}` as `{}`".format(repo.repo_name, schema_data["repo_name"]),
+ "msg": f"Created fork of `{repo.repo_name}` as `{schema_data['repo_name']}`",
"success": True, # cannot return the repo data here since fork
# can be done async
"task": task_id,
}
except Exception:
log.exception("Exception while trying to create fork %s", schema_data["repo_name"])
- raise JSONRPCError("failed to fork repository `{}` as `{}`".format(repo_name, schema_data["repo_name"]))
+ raise JSONRPCError(f"failed to fork repository `{repo_name}` as `{schema_data['repo_name']}`")
@jsonrpc_method()
@@ -1361,11 +1363,11 @@ def delete_repo(request, apiuser, repoid, forks=Optional("")):
_forks_msg = ""
_forks = [f for f in repo.forks]
if handle_forks == "detach":
- _forks_msg = " " + "Detached %s forks" % len(_forks)
+ _forks_msg = " " + f"Detached {len(_forks)} forks"
elif handle_forks == "delete":
- _forks_msg = " " + "Deleted %s forks" % len(_forks)
+ _forks_msg = " " + f"Deleted {len(_forks)} forks"
elif _forks:
- raise JSONRPCError("Cannot delete `%s` it still contains attached forks" % (repo.repo_name,))
+ raise JSONRPCError(f"Cannot delete `{repo.repo_name}` it still contains attached forks")
old_data = repo.get_api_data()
RepoModel().delete(repo, forks=forks)
@@ -1509,7 +1511,7 @@ def lock(request, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr(
# make sure normal user does not pass someone else userid,
# he is not allowed to do that
if not isinstance(userid, Optional) and userid != apiuser.user_id:
- raise JSONRPCError("userid is not the same as your user")
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
if isinstance(userid, Optional):
userid = apiuser.user_id
@@ -1527,7 +1529,7 @@ def lock(request, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr(
"locked_by": None,
"lock_reason": None,
"lock_state_changed": False,
- "msg": "Repo `%s` not locked." % repo.repo_name,
+ "msg": f"Repo `{repo.repo_name}` not locked.",
}
return _d
else:
@@ -1541,8 +1543,7 @@ def lock(request, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr(
"lock_reason": _reason,
"lock_state_changed": False,
"msg": (
- "Repo `%s` locked by `%s` on `%s`."
- % (repo.repo_name, lock_user.username, json.dumps(time_to_datetime(_time)))
+ f"Repo `{repo.repo_name}` locked by `{lock_user.username}` on `{json.dumps(time_to_datetime(_time))}`."
),
}
return _d
@@ -1565,12 +1566,12 @@ def lock(request, apiuser, repoid, locked=Optional(None), userid=Optional(OAttr(
"locked_by": user.username,
"lock_reason": lock_reason,
"lock_state_changed": True,
- "msg": ("User `%s` set lock state for repo `%s` to `%s`" % (user.username, repo.repo_name, locked)),
+ "msg": f"User `{user.username}` set lock state for repo `{repo.repo_name}` to `{locked}`",
}
return _d
except Exception:
log.exception("Exception occurred while trying to lock repository")
- raise JSONRPCError("Error occurred locking repository `%s`" % repo.repo_name)
+ raise JSONRPCError(f"Error occurred locking repository `{repo.repo_name}`")
@jsonrpc_method()
@@ -1643,8 +1644,17 @@ def comment_commit(
log.exception("Failed to fetch commit")
raise JSONRPCError(safe_str(e))
+ auth_user = apiuser
if isinstance(userid, Optional):
userid = apiuser.user_id
+ else:
+ # super-admin can impersonate other users
+ if has_superadmin_permission(apiuser):
+ apiuser = get_user_or_error(userid)
+ userid = apiuser.user_id
+ auth_user = apiuser.AuthUser()
+ else:
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
user = get_user_or_error(userid)
status = Optional.extract(status)
@@ -1655,21 +1665,14 @@ def comment_commit(
allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
if status and status not in allowed_statuses:
- raise JSONRPCError(
- "Bad status, must be on "
- "of %s got %s"
- % (
- allowed_statuses,
- status,
- )
- )
+ raise JSONRPCError(f"Bad status, must be on of {allowed_statuses} got {status}")
if resolves_comment_id:
comment = ChangesetComment.get(resolves_comment_id)
if not comment:
- raise JSONRPCError("Invalid resolves_comment_id `%s` for this commit." % resolves_comment_id)
+ raise JSONRPCError(f"Invalid resolves_comment_id `{resolves_comment_id}` for this commit.")
if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
- raise JSONRPCError("Comment `%s` is wrong type for setting status to resolved." % resolves_comment_id)
+ raise JSONRPCError(f"Comment `{resolves_comment_id}` is wrong type for setting status to resolved.")
try:
rc_config = SettingsModel().get_all_settings()
@@ -1685,7 +1688,7 @@ def comment_commit(
renderer=renderer,
comment_type=comment_type,
resolves_comment_id=resolves_comment_id,
- auth_user=apiuser,
+ auth_user=auth_user,
extra_recipients=extra_recipients,
send_email=send_email,
)
@@ -1721,7 +1724,7 @@ def comment_commit(
)
return {
- "msg": ("Commented on commit `{}` for repository `{}`".format(comment.revision, repo.repo_name)),
+ "msg": f"Commented on commit `{comment.revision}` for repository `{repo.repo_name}`",
"status_change": status,
"success": True,
}
@@ -1792,9 +1795,7 @@ def get_repo_comments(
comment_type = Optional.extract(comment_type)
if comment_type and comment_type not in ChangesetComment.COMMENT_TYPES:
- raise JSONRPCError(
- "comment_type must be one of `{}` got {}".format(ChangesetComment.COMMENT_TYPES, comment_type)
- )
+ raise JSONRPCError(f"comment_type must be one of `{ChangesetComment.COMMENT_TYPES}` got {comment_type}")
comments = CommentsModel().get_repository_comments(
repo=repo, comment_type=comment_type, user=user, commit_id=commit_id
@@ -1886,11 +1887,12 @@ def edit_comment(request, apiuser, message, comment_id, version, userid=Optional
is_repo_admin = HasRepoPermissionAnyApi("repository.admin")(user=apiuser, repo_name=comment.repo.repo_name)
if not isinstance(userid, Optional):
- if is_super_admin or is_repo_admin:
+ if is_super_admin:
+ # super-admin can impersonate other users
apiuser = get_user_or_error(userid)
auth_user = apiuser.AuthUser()
else:
- raise JSONRPCError("userid is not the same as your user")
+ raise JSONRPCError("Provided userid is not the same the user calling this function")
comment_author = comment.author.user_id == auth_user.user_id
@@ -2001,14 +2003,12 @@ def grant_user_permission(request, apiuser, repoid, userid, perm):
PermissionModel().flush_user_permission_caches(changes)
return {
- "msg": "Granted perm: `{}` for user: `{}` in repo: `{}`".format(
- perm.permission_name, user.username, repo.repo_name
- ),
+ "msg": f"Granted perm: `{perm.permission_name}` for user: `{user.username}` in repo: `{repo.repo_name}`",
"success": True,
}
except Exception:
log.exception("Exception occurred while trying edit permissions for repo")
- raise JSONRPCError("failed to edit permission for user: `{}` in repo: `{}`".format(userid, repoid))
+ raise JSONRPCError(f"failed to edit permission for user: `{userid}` in repo: `{repoid}`")
@jsonrpc_method()
@@ -2058,12 +2058,12 @@ def revoke_user_permission(request, apiuser, repoid, userid):
PermissionModel().flush_user_permission_caches(changes)
return {
- "msg": "Revoked perm for user: `{}` in repo: `{}`".format(user.username, repo.repo_name),
+ "msg": f"Revoked perm for user: `{user.username}` in repo: `{repo.repo_name}`",
"success": True,
}
except Exception:
log.exception("Exception occurred while trying revoke permissions to repo")
- raise JSONRPCError("failed to edit permission for user: `{}` in repo: `{}`".format(userid, repoid))
+ raise JSONRPCError(f"failed to edit permission for user: `{userid}` in repo: `{repoid}`")
@jsonrpc_method()
@@ -2145,9 +2145,7 @@ def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
}
except Exception:
log.exception("Exception occurred while trying change permission on repo")
- raise JSONRPCError(
- "failed to edit permission for user group: `%s` in repo: `%s`" % (usergroupid, repo.repo_name)
- )
+ raise JSONRPCError(f"failed to edit permission for user group: `{usergroupid}` in repo: `{repo.repo_name}`")
@jsonrpc_method()
@@ -2206,16 +2204,13 @@ def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
PermissionModel().flush_user_permission_caches(changes)
return {
- "msg": "Revoked perm for user group: `{}` in repo: `{}`".format(
- user_group.users_group_name, repo.repo_name
- ),
+ "msg": f"Revoked perm for user group: `{user_group.users_group_name}` in repo: `{repo.repo_name}`",
"success": True,
}
except Exception:
log.exception("Exception occurred while trying revoke user group permission on repo")
raise JSONRPCError(
- "failed to edit permission for user group: `%s` in "
- "repo: `%s`" % (user_group.users_group_name, repo.repo_name)
+ f"failed to edit permission for user group: `{user_group.users_group_name}` in repo: `{repo.repo_name}`"
)
@@ -2276,12 +2271,12 @@ def pull(request, apiuser, repoid, remote_uri=Optional(None), sync_large_objects
repo.repo_name, apiuser.username, remote_uri=remote_uri, sync_large_objects=sync_large_objects
)
return {
- "msg": "Pulled from url `{}` on repo `{}`".format(remote_uri_display, repo.repo_name),
+ "msg": f"Pulled from url `{remote_uri_display}` on repo `{repo.repo_name}`",
"repository": repo.repo_name,
}
except Exception:
log.exception("Exception occurred while trying to pull changes from remote location")
- raise JSONRPCError("Unable to pull changes from `%s`" % remote_uri_display)
+ raise JSONRPCError(f"Unable to pull changes from `{remote_uri_display}`")
@jsonrpc_method()
@@ -2340,12 +2335,12 @@ def strip(request, apiuser, repoid, revision, branch):
)
return {
- "msg": "Stripped commit {} from repo `{}`".format(revision, repo.repo_name),
+ "msg": f"Stripped commit {revision} from repo `{repo.repo_name}`",
"repository": repo.repo_name,
}
except Exception:
log.exception("Exception while trying to strip")
- raise JSONRPCError("Unable to strip commit {} from repo `{}`".format(revision, repo.repo_name))
+ raise JSONRPCError(f"Unable to strip commit {revision} from repo `{repo.repo_name}`")
@jsonrpc_method()
@@ -2515,4 +2510,4 @@ def maintenance(request, apiuser, repoid):
}
except Exception:
log.exception("Exception occurred while trying to run maintenance")
- raise JSONRPCError("Unable to execute maintenance on `%s`" % repo.repo_name)
+ raise JSONRPCError(f"Unable to execute maintenance on `{repo.repo_name}`")
diff --git a/rhodecode/apps/ssh_support/tests/test_server_svn.py b/rhodecode/apps/ssh_support/tests/test_server_svn.py
index 64017573..d926299f 100644
--- a/rhodecode/apps/ssh_support/tests/test_server_svn.py
+++ b/rhodecode/apps/ssh_support/tests/test_server_svn.py
@@ -44,6 +44,8 @@ class SubversionServerCreator(object):
def __init__(self, service_api_url, ini_file):
self.service_api_url = service_api_url
self.ini_file = ini_file
+ if not os.path.isdir(self.root):
+ os.makedirs(self.root)
def create(self, **kwargs):
self.config_data["app:main"]["app.service_api.host"] = self.service_api_url
diff --git a/rhodecode/events/base.py b/rhodecode/events/base.py
index 019c2ca6..9ea6f9e4 100644
--- a/rhodecode/events/base.py
+++ b/rhodecode/events/base.py
@@ -82,14 +82,22 @@ class RhodecodeEvent(object):
log.debug("Got integration actor: %s", auth_user)
if isinstance(auth_user, AuthUser):
instance = auth_user.get_instance()
- # we can't find this DB user...
- if not instance:
+ if instance:
+ return AttributeDict(
+ dict(
+ username=instance.username,
+ user_id=instance.user_id,
+ )
+ )
+ else:
+ # we can't find this DB user...
return AttributeDict(
dict(
username=auth_user.username,
user_id=auth_user.user_id,
)
)
+
elif auth_user:
return auth_user
return SYSTEM_USER
diff --git a/rhodecode/integrations/types/handlers/slack.py b/rhodecode/integrations/types/handlers/slack.py
index 2ca104b8..db64d2aa 100644
--- a/rhodecode/integrations/types/handlers/slack.py
+++ b/rhodecode/integrations/types/handlers/slack.py
@@ -17,7 +17,7 @@
# and proprietary license terms, please see https://rhodecode.com/licenses/
import re
import textwrap
-import dataclasses
+from dataclasses import dataclass, asdict
import logging
from mako.template import Template
@@ -28,13 +28,16 @@ from rhodecode.integrations.types.base import CommitParsingDataHandler, render_w
log = logging.getLogger(__name__)
-@dataclasses.dataclass
+@dataclass
class SlackData:
title: str
text: str
fields: list[dict] | None = None
overrides: dict | None = None
+ def asdict(self):
+ return asdict(self)
+
def html_to_slack_links(message):
return re.compile(r'(.+?)').sub(r"<\1|\2>", message)
diff --git a/rhodecode/integrations/types/slack.py b/rhodecode/integrations/types/slack.py
index 75e769d8..07898b1e 100644
--- a/rhodecode/integrations/types/slack.py
+++ b/rhodecode/integrations/types/slack.py
@@ -134,6 +134,8 @@ class SlackIntegrationType(IntegrationTypeBase):
handler = SlackDataHandler()
slack_data = handler(event, data)
+ # Transform to dict so its serializable
+ slack_data = slack_data.asdict()
# title, text, fields, overrides
run_task(post_text_to_slack, self.settings, slack_data)
diff --git a/rhodecode/lib/vcs/backends/svn/repository.py b/rhodecode/lib/vcs/backends/svn/repository.py
index 2241ba43..d0ae04de 100644
--- a/rhodecode/lib/vcs/backends/svn/repository.py
+++ b/rhodecode/lib/vcs/backends/svn/repository.py
@@ -306,8 +306,13 @@ class SubversionRepository(base.BaseRepository):
self._validate_commit_id(end_id)
start_raw_id = self._sanitize_commit_id(start_id)
- start_pos = self.commit_ids.index(start_raw_id) if start_id else None
end_raw_id = self._sanitize_commit_id(end_id)
+
+ # for empty revisions in SVN
+ if start_raw_id not in self.commit_ids or end_raw_id not in self.commit_ids:
+ return base.CollectionGenerator(self, [])
+
+ start_pos = self.commit_ids.index(start_raw_id) if start_id else None
end_pos = max(0, self.commit_ids.index(end_raw_id)) if end_id else None
if None not in [start_id, end_id] and start_pos > end_pos:
diff --git a/rhodecode/lib/vcs/client_http.py b/rhodecode/lib/vcs/client_http.py
index 1b42d4c0..4dec389d 100644
--- a/rhodecode/lib/vcs/client_http.py
+++ b/rhodecode/lib/vcs/client_http.py
@@ -203,8 +203,10 @@ class RemoteRepo(object):
self.stream_url = remote_maker.stream_url
self._session = remote_maker._session_factory()
- cache_repo_id = self._repo_id_sanitizer(repo_id)
_repo_name = self._get_repo_name(config, path)
+ # at this point this is actually a PATH, so we turn it into a hash to prevent 255 char limits
+ cache_repo_id = self._repo_id_sanitizer(repo_id)
+ cache_repo_id = rc_cache.utils.compute_key_from_params("repo_name", cache_repo_id)
self._cache_region, self._cache_namespace = remote_maker.init_cache_region(cache_repo_id)
with_wire = with_wire or {"cache": False}
diff --git a/rhodecode/subscribers.py b/rhodecode/subscribers.py
index 1c3b46eb..924ca33a 100644
--- a/rhodecode/subscribers.py
+++ b/rhodecode/subscribers.py
@@ -60,30 +60,30 @@ def add_renderer_globals(event):
def auto_merge_pr_if_needed(event):
from rhodecode.model.db import PullRequest
- from rhodecode.model.pull_request import (
- PullRequestModel, ChangesetStatus, MergeCheck
- )
+ from rhodecode.model.pull_request import PullRequestModel, ChangesetStatus, MergeCheck
pr_event_data = event.as_dict()["pullrequest"]
pull_request = PullRequest.get(pr_event_data["pull_request_id"])
calculated_status = pr_event_data["status"]
- if (calculated_status == ChangesetStatus.STATUS_APPROVED
- and PullRequestModel().is_automatic_merge_enabled(pull_request)):
+ if calculated_status == ChangesetStatus.STATUS_APPROVED and PullRequestModel().is_automatic_merge_enabled(
+ pull_request
+ ):
user = pull_request.author.AuthUser()
- merge_check = MergeCheck.validate(
- pull_request, user, translator=lambda x: x, fail_early=True
- )
+ merge_check = MergeCheck.validate(pull_request, user, translator=lambda x: x, fail_early=True)
if merge_check.merge_possible:
from rhodecode.lib.base import vcs_operation_context
+
extras = vcs_operation_context(
- event.request.environ, repo_name=pull_request.target_repo.repo_name,
- username=user.username, action="push",
- scm=pull_request.target_repo.repo_type)
- from rc_ee.lib.celerylib.tasks import auto_merge_repo
- auto_merge_repo.apply_async(
- args=(pull_request.pull_request_id, extras), countdown=3
+ event.request.environ,
+ repo_name=pull_request.target_repo.repo_name,
+ username=user.username,
+ action="push",
+ scm=pull_request.target_repo.repo_type,
)
+ from rc_ee.lib.celerylib.tasks import auto_merge_repo
+
+ auto_merge_repo.apply_async(args=(pull_request.pull_request_id, extras), countdown=3)
def set_user_lang(event):
diff --git a/rhodecode/templates/base/base.mako b/rhodecode/templates/base/base.mako
index 7d0191cc..63ff8cf6 100644
--- a/rhodecode/templates/base/base.mako
+++ b/rhodecode/templates/base/base.mako
@@ -1111,39 +1111,6 @@
}
});
- var dismissNotice = function(noticeId) {
-
- var url = pyroutes.url('user_notice_dismiss',
- {"user_id": templateContext.rhodecode_user.user_id});
-
- var postData = {
- 'csrf_token': CSRF_TOKEN,
- 'notice_id': noticeId,
- };
-
- var success = function(response) {
- $('#notice-message-' + noticeId).remove();
- return false;
- };
- var failure = function(data, textStatus, xhr) {
- alert("error processing request: " + textStatus);
- return false;
- };
- ajaxPOST(url, postData, success, failure);
- }
-
- var hideLicenseWarning = function () {
- var fingerprint = templateContext.session_attrs.license_fingerprint;
- storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
- $('#notifications').hide();
- }
-
- var hideLicenseError = function () {
- var fingerprint = templateContext.session_attrs.license_fingerprint;
- storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
- $('#notifications').hide();
- }
-
%def>
diff --git a/rhodecode/templates/base/root.mako b/rhodecode/templates/base/root.mako
index d431a2d3..3654783d 100644
--- a/rhodecode/templates/base/root.mako
+++ b/rhodecode/templates/base/root.mako
@@ -104,6 +104,38 @@ c.template_context['attachment_store'] = {
rhodecode_edition: '${c.rhodecode_edition}'
}
};
+ var dismissNotice = function(noticeId) {
+
+ var url = pyroutes.url('user_notice_dismiss',
+ {"user_id": templateContext.rhodecode_user.user_id});
+
+ var postData = {
+ 'csrf_token': CSRF_TOKEN,
+ 'notice_id': noticeId,
+ };
+
+ var success = function(response) {
+ $('#notice-message-' + noticeId).remove();
+ return false;
+ };
+ var failure = function(data, textStatus, xhr) {
+ alert("error processing request: " + textStatus);
+ return false;
+ };
+ ajaxPOST(url, postData, success, failure);
+ }
+
+ var hideLicenseWarning = function () {
+ var fingerprint = templateContext.session_attrs.license_fingerprint;
+ storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
+ $('#notifications').hide();
+ }
+
+ var hideLicenseError = function () {
+ var fingerprint = templateContext.session_attrs.license_fingerprint;
+ storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
+ $('#notifications').hide();
+ }
<%include file="/base/plugins_base.mako"/>
diff --git a/rhodecode/tests/integrations/test_slack.py b/rhodecode/tests/integrations/test_slack.py
index 42daedc6..7e2babee 100644
--- a/rhodecode/tests/integrations/test_slack.py
+++ b/rhodecode/tests/integrations/test_slack.py
@@ -21,7 +21,7 @@ import mock
from mock import patch
from rhodecode import events
-from rhodecode.integrations.types.handlers.slack import SlackDataHandler
+from rhodecode.integrations.types.handlers.slack import SlackDataHandler, SlackData
from rhodecode.model.db import Session, Integration
from rhodecode.integrations.types.slack import SlackIntegrationType
from rhodecode.tests import GIT_REPO
@@ -100,10 +100,14 @@ def slack_integration_empty(request, app, slack_settings):
def test_slack_push(slack_integration, repo_push_event):
with patch("rhodecode.integrations.types.slack.post_text_to_slack") as call:
events.trigger(repo_push_event)
+ # make sure we can handle both dicts and dataclass objects
+ slack_data = call.call_args[0][1]
+ if isinstance(slack_data, dict):
+ slack_data = SlackData(**slack_data)
- assert "pushed to" in call.call_args[0][1].title
+ assert "pushed to" in slack_data.title
# specific commit was parsed and serialized
- assert "change that fixes #41" in call.call_args[0][1].text
+ assert "change that fixes #41" in slack_data.text
def test_slack_push_no_events(slack_integration_empty, repo_push_event):