Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ Weblate 5.15
* Avoid false positive checks upon committing pending changes.
* Performance improvements for file upload.
* Show glossary matches for the source language.
* Pull/merge requests are now only created when necessary.

.. rubric:: Compatibility

Expand Down
57 changes: 51 additions & 6 deletions weblate/vcs/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,56 @@
REQUIRED_CONFIG: ClassVar[set[str]] = {"username", "token"}
OPTIONAL_CONFIG: ClassVar[set[str]] = {"scheme"}

def get_fork_branch_name(self) -> str:
"""Get the fork branch name used for pushing."""
if self.component is not None:
return f"weblate-{self.component.project.slug}-{self.component.slug}"
return f"weblate-{self.branch}"

def count_outgoing(self, branch: str | None = None) -> int:
"""
Count outgoing commits.

For merge request workflows, we need to check against both the origin
remote (to see if commits are already merged) and the fork remote (to see
if commits are already pushed but not merged). This prevents creating
duplicate merge requests when commits have already been merged, while also
avoiding unnecessary pushes when commits are already in the fork.
"""
# Check if fork is used for this branch (default and matching branches use fork)
if not self.should_use_fork(branch):
# Fork not used: check specified branch directly on origin
return super().count_outgoing(branch)

# Fork workflow: check if commits have been merged to origin's pull branch
# Omit branch parameter to check the repository's default pull branch
origin_outgoing = super().count_outgoing()
if origin_outgoing == 0:
# All commits are in origin, nothing to push
return 0

# Check if commits are in the fork (already pushed)
# The fork branch name is determined by get_fork_branch_name()
credentials = self.get_credentials()
fork_remote = credentials["username"]
fork_branch_name = self.get_fork_branch_name()
fork_branch = f"{fork_remote}/{fork_branch_name}"

try:
fork_outgoing = len(
self.log_revisions(self.ref_from_remote.format(fork_branch))
)
# If fork has all commits, don't need to push
if fork_outgoing == 0:
return 0
except RepositoryError:
# Fork branch doesn't exist yet or is not accessible,
# indicating commits haven't been pushed to fork
pass

# Commits are not in origin or fork, need to push
return origin_outgoing

def merge(
self, abort: bool = False, message: str | None = None, no_ff: bool = False
) -> None:
Expand All @@ -899,7 +949,7 @@
self, repo: str | None = None
) -> tuple[str | None, str | None, str | None, str, str, str]:
if repo is None:
repo = self.component.push or self.component.repo

Check failure on line 952 in weblate/vcs/git.py

View workflow job for this annotation

GitHub Actions / mypy

Item "None" of "Component | None" has no attribute "repo"

Check failure on line 952 in weblate/vcs/git.py

View workflow job for this annotation

GitHub Actions / mypy

Item "None" of "Component | None" has no attribute "push"
parsed = urlparse(repo)
host = parsed.hostname
scheme: str | None = parsed.scheme
Expand Down Expand Up @@ -1087,12 +1137,7 @@
else:
fork_remote = credentials["username"]
self.fork(credentials)
if self.component is not None:
fork_branch = (
f"weblate-{self.component.project.slug}-{self.component.slug}"
)
else:
fork_branch = f"weblate-{self.branch}"
fork_branch = self.get_fork_branch_name()
self.push_to_fork(credentials, self.branch, fork_branch)
self.create_pull_request(credentials, self.branch, fork_remote, fork_branch)

Expand Down Expand Up @@ -1141,7 +1186,7 @@

def get_merge_message(self):
lines = render_template(
self.component.pull_message.strip(), component=self.component

Check failure on line 1189 in weblate/vcs/git.py

View workflow job for this annotation

GitHub Actions / mypy

Item "None" of "Component | None" has no attribute "pull_message"
).splitlines()
return lines[0], "\n".join(lines[1:]).strip()

Expand Down Expand Up @@ -1367,7 +1412,7 @@
self, repo: str | None = None
) -> tuple[str | None, str | None, str | None, str, str, str]:
if repo is None:
repo = self.component.repo

Check failure on line 1415 in weblate/vcs/git.py

View workflow job for this annotation

GitHub Actions / mypy

Item "None" of "Component | None" has no attribute "repo"

scheme_regex = r"^[a-z]+:\/\/.*" # matches for example ssh://* and https://*

Expand Down
Loading
Loading