Compare commits

..
56 Commits
Author SHA1 Message Date
bitbucket-pipelines 8b8b9337ac chore: bump version and update CHANGELOG.md [skip ci] 2026-02-10 23:39:27 +00:00
JonathanGitFlowandjt da8b27ce88 Merged in bugfix/add-build-step-to-pipeline (pull request #58)
bugfix: agrego paso de build al pipeline

Approved-by: Jonathan Teran
2026-02-10 23:38:32 +00:00
jt ade86f1350 bugfix: agrego paso de build al pipeline 2026-02-10 20:38:02 -03:00
bitbucket-pipelines 1eee08f60d chore: bump version and update CHANGELOG.md [skip ci] 2026-02-10 23:36:17 +00:00
JonathanGitFlowandjt f20f8c811c Merged in feature/use-uv-package-manager (pull request #57)
feature: cambio pipelines para usar uv package manager

Approved-by: Jonathan Teran
2026-02-10 23:35:35 +00:00
jt 0f700b026a feature: cambio pipelines para usar uv package manager 2026-02-10 20:34:04 -03:00
JonathanGitFlowandjt 9564339218 Merged in docs/create-access-token (pull request #56)
docs: arreglo pasos para crear Access Token

Approved-by: Jonathan Teran
2026-01-14 12:28:46 +00:00
Jonathan Teran Carballo dfd96e62bb docs: arreglo pasos para crear Access Token 2026-01-14 09:27:03 -03:00
bitbucket-pipelines 65c901e5b8 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-22 02:33:28 +00:00
JonathanGitFlowandjt 5759f67aa2 Merged in feature/2025-12-21 (pull request #55)
feature: 2025 12 21

Approved-by: Jonathan Teran
2025-12-22 02:32:42 +00:00
jt 726c73af39 feature: al agrupar release, mergear cambios en rama group 2025-12-21 23:30:46 -03:00
jt 057b32a2f9 feature: usar commit message cuando el pr solo tiene un commit 2025-12-21 23:10:21 -03:00
jt c0e2e596aa feature: eliminar prompt de ticket en creacion de rama 2025-12-21 22:32:54 -03:00
jt 77e338dcc8 feature: deshacer rebase cuando este falla en el pasaje de cambios 2025-12-21 22:31:16 -03:00
jt 1aa2263a38 bugfix: uso titulo personalizado de PR 2025-12-21 22:27:14 -03:00
bitbucket-pipelines 8c4037f702 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-15 02:03:36 +00:00
JonathanGitFlowandjt f3980d6c78 Merged in feature/merge-command-improvements (pull request #54)
feature: merge command improvements

Approved-by: Jonathan Teran
2025-12-15 02:03:11 +00:00
Jonathan Teran Carballo df8998a092 feature: agrego opcion para cambiar a rama objetivo y obtener cambios 2025-12-14 23:01:49 -03:00
Jonathan Teran Carballo c87b14121c feature: agrego opcion para personalizar nombre de PR 2025-12-14 22:53:26 -03:00
bitbucket-pipelines 7fb0ba0c39 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-15 01:40:00 +00:00
JonathanGitFlowandjt 5b6beb7c08 Merged in bugfix/list-branchs-on-environment (pull request #53)
bugfix: list branchs on environment

Approved-by: Jonathan Teran
2025-12-15 01:39:35 +00:00
Jonathan Teran Carballo c54cb95ed7 bugfix: uso rama actual cuando esta es un entorno 2025-12-14 22:38:25 -03:00
bitbucket-pipelines a2731e3400 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-15 01:11:57 +00:00
JonathanGitFlowandjt b20787d69d Merged in feature/branch-command-improvements (pull request #52)
feature: branch command improvements

Approved-by: Jonathan Teran
2025-12-15 01:11:32 +00:00
Jonathan Teran Carballo 5d33b29abd bugfix: uso env actual en vez de rama actual para listar ramas 2025-12-14 22:10:00 -03:00
Jonathan Teran Carballo 104b3214d4 feature: listar ramas de todos los entornos con flag --all, y del entorno actual por defecto 2025-12-14 22:00:14 -03:00
Jonathan Teran Carballo 774cf9ade6 feature: agrego opcion para listar ramas wip 2025-12-14 21:56:18 -03:00
Jonathan Teran Carballo 74c333aa1a refactor: cambio opcion para listar ramas trash 2025-12-14 21:48:44 -03:00
Jonathan Teran Carballo 499babcaec bugfix: elimino : extra de comando new 2025-12-14 21:40:54 -03:00
bitbucket-pipelines 379e0f3257 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-15 00:32:39 +00:00
JonathanGitFlowandjt 4a57dfd121 Merged in feature/new-command-improvements (pull request #51)
feature: new command improvements

Approved-by: Jonathan Teran
2025-12-15 00:32:13 +00:00
Jonathan Teran Carballo 01e938ebd9 feature: agrego opcion para incluir ticket en nombre de rama 2025-12-14 21:29:48 -03:00
Jonathan Teran Carballo 97b4a56ff7 feature: mostrar warning si la rama actual no es un entorno configurado 2025-12-14 13:46:09 -03:00
Jonathan Teran Carballo 24be50111d feature: hacer pull en rama actual antes de crear nueva rama 2025-12-14 13:43:23 -03:00
bitbucket-pipelines 897e9aa53b chore: bump version and update CHANGELOG.md [skip ci] 2025-12-14 16:39:20 +00:00
JonathanGitFlowandjt e4800d1054 Merged in bugfix/arreglos-pendientes (pull request #50)
bugfix: arreglos pendientes

Approved-by: Jonathan Teran
2025-12-14 16:38:55 +00:00
Jonathan Teran Carballo 0d95a978bf bugfix: verificar si la rama grupo trackea una rama remota antes de hacer pull 2025-12-14 13:32:23 -03:00
Jonathan Teran Carballo 0af5102fae bugfix: capturo interrupion por teclado para no mostrar stack trace 2025-12-14 13:25:31 -03:00
bitbucket-pipelines 2a21922a63 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-14 15:22:19 +00:00
JonathanGitFlowandjt 12b2bc9a39 Merged in bugfix/merge-pr-title-generation (pull request #49)
bugfix: merge pr title generation

Approved-by: Jonathan Teran
2025-12-14 15:21:54 +00:00
Jonathan Teran Carballo b24b22a178 bugfix: generación de PR de ramas release 2025-12-14 12:20:32 -03:00
bitbucket-pipelines c5f1be346c chore: bump version and update CHANGELOG.md [skip ci] 2025-12-14 15:05:04 +00:00
JonathanGitFlowandjt 6c32b3e751 Merged in bugfix/release-not-listing-branches (pull request #48)
bugfix: release not listing branches

Approved-by: Jonathan Teran
2025-12-14 15:04:39 +00:00
Jonathan Teran Carballo 5600e5ec83 bugfix: listado de ramas para elegir grupo release no devolvia resultados 2025-12-14 11:59:42 -03:00
bitbucket-pipelines 57142afa40 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-11 20:08:25 +00:00
JonathanGitFlowandjt 4375af35d9 Merged in bugfix/release-switch-to-group (pull request #47)
bugfix: release switch to group

Approved-by: Jonathan Teran
2025-12-11 20:07:56 +00:00
Jonathan Teran Carballo 44b8efd953 bugfix: al hacer un release, cambio a la rama grupo si esta disponible 2025-12-11 17:07:13 -03:00
bitbucket-pipelines f4b47000a6 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-11 18:18:23 +00:00
JonathanGitFlowandjt b12d2a1930 Merged in bugfix/first-fork-point-getter (pull request #46)
bugfix: first fork point getter

Approved-by: Jonathan Teran
2025-12-11 18:17:53 +00:00
Jonathan Teran Carballo 7b4fb34ef3 bugfix: cambio la forma en la que se obtiene el first fork point entre 2 ramas 2025-12-11 15:15:45 -03:00
bitbucket-pipelines 6a758cdb94 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-11 12:55:31 +00:00
JonathanGitFlowandjt b148a9f71d Merged in feature/show-commit-list-on-release (pull request #45)
feature: show commit list on release

Approved-by: Jonathan Teran
2025-12-11 12:55:01 +00:00
Jonathan Teran 2e65cb7a3e feature: muestro el listado de commits a pasar al siguiente entorno en release 2025-12-11 09:53:41 -03:00
bitbucket-pipelines 9c8df34a09 chore: bump version and update CHANGELOG.md [skip ci] 2025-12-10 18:29:40 +00:00
JonathanGitFlowandjt 89f8bc58ba Merged in bugfix/commit-list-for-merge (pull request #44)
bugfix: commit list for merge

Approved-by: Jonathan Teran
2025-12-10 18:29:07 +00:00
Jonathan Teran 4e089dc4d6 bugfix: cambio la lista de commits a mostrar en el merge 2025-12-10 15:06:21 -03:00
14 changed files with 399 additions and 64 deletions
+1
View File
@@ -0,0 +1 @@
3.10
+110
View File
@@ -1,3 +1,113 @@
## v1.12.1 - 2026-02-10
- **bugfix**: agrego paso de build al pipeline [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (ade86f1)
---
## v1.12.0 - 2026-02-10
- **feature**: cambio pipelines para usar uv package manager [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (0f700b0)
---
## v1.11.0 - 2025-12-22
- **feature**: al agrupar release, mergear cambios en rama group [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (726c73a)
- **feature**: usar commit message cuando el pr solo tiene un commit [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (057b32a)
- **feature**: eliminar prompt de ticket en creacion de rama [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (c0e2e59)
- **feature**: deshacer rebase cuando este falla en el pasaje de cambios [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (77e338d)
- **bugfix**: uso titulo personalizado de PR [[jonathan.nerat](mailto:jonathan.nerat@gmail.com)] (1aa2263)
---
## v1.10.0 - 2025-12-15
- **feature**: agrego opcion para cambiar a rama objetivo y obtener cambios [[jteran](mailto:jteran@renatre.org.ar)] (df8998a)
- **feature**: agrego opcion para personalizar nombre de PR [[jteran](mailto:jteran@renatre.org.ar)] (c87b141)
---
## v1.9.1 - 2025-12-15
- **bugfix**: uso rama actual cuando esta es un entorno [[jteran](mailto:jteran@renatre.org.ar)] (c54cb95)
---
## v1.9.0 - 2025-12-15
- **bugfix**: uso env actual en vez de rama actual para listar ramas [[jteran](mailto:jteran@renatre.org.ar)] (5d33b29)
- **feature**: listar ramas de todos los entornos con flag --all, y del entorno actual por defecto [[jteran](mailto:jteran@renatre.org.ar)] (104b321)
- **feature**: agrego opcion para listar ramas wip [[jteran](mailto:jteran@renatre.org.ar)] (774cf9a)
- **refactor**: cambio opcion para listar ramas trash [[jteran](mailto:jteran@renatre.org.ar)] (74c333a)
- **bugfix**: elimino : extra de comando new [[jteran](mailto:jteran@renatre.org.ar)] (499babc)
---
## v1.8.0 - 2025-12-15
- **feature**: agrego opcion para incluir ticket en nombre de rama [[jteran](mailto:jteran@renatre.org.ar)] (01e938e)
- **feature**: mostrar warning si la rama actual no es un entorno configurado [[jteran](mailto:jteran@renatre.org.ar)] (97b4a56)
- **feature**: hacer pull en rama actual antes de crear nueva rama [[jteran](mailto:jteran@renatre.org.ar)] (24be501)
---
## v1.7.5 - 2025-12-14
- **bugfix**: verificar si la rama grupo trackea una rama remota antes de hacer pull [[jteran](mailto:jteran@renatre.org.ar)] (0d95a97)
- **bugfix**: capturo interrupion por teclado para no mostrar stack trace [[jteran](mailto:jteran@renatre.org.ar)] (0af5102)
---
## v1.7.4 - 2025-12-14
- **bugfix**: generación de PR de ramas release [[jteran](mailto:jteran@renatre.org.ar)] (b24b22a)
---
## v1.7.3 - 2025-12-14
- **bugfix**: listado de ramas para elegir grupo release no devolvia resultados [[jteran](mailto:jteran@renatre.org.ar)] (5600e5e)
---
## v1.7.2 - 2025-12-11
- **bugfix**: al hacer un release, cambio a la rama grupo si esta disponible [[jteran](mailto:jteran@renatre.org.ar)] (44b8efd)
---
## v1.7.1 - 2025-12-11
- **bugfix**: cambio la forma en la que se obtiene el first fork point entre 2 ramas [[jteran](mailto:jteran@renatre.org.ar)] (7b4fb34)
---
## v1.7.0 - 2025-12-11
- **feature**: muestro el listado de commits a pasar al siguiente entorno en release [[jteran](mailto:jteran@renatre.org.ar)] (2e65cb7)
---
## v1.6.4 - 2025-12-10
- **bugfix**: cambio la lista de commits a mostrar en el merge [[jteran](mailto:jteran@renatre.org.ar)] (4e089dc)
---
## v1.6.3 - 2025-12-04 ## v1.6.3 - 2025-12-04
- **bugfix**: revertir guion entre version y entorno, agregar guion a sufijo [[jteran](mailto:jteran@renatre.org.ar)] (7ce6add) - **bugfix**: revertir guion entre version y entorno, agregar guion a sufijo [[jteran](mailto:jteran@renatre.org.ar)] (7ce6add)
+3 -3
View File
@@ -49,9 +49,9 @@ El comando solicitará 2 opciones:
- [x] Bitbucket - [x] Bitbucket
- [ ] Github: planeado - [ ] Github: planeado
La configuración del remoto requiere tener un API Token para poder crear los PRs automáticamente. La configuración del remoto requiere tener un Access Token para poder crear los PRs automáticamente.
El mismo se puede crear desde: *"Atlassian Account Settings" > "Security" > "Create and Manage API El mismo se puede crear desde el repositorio al que se quiere dar acceso: *"Repository settings" >
Tokens" > "Create API Tokens with scopes"*. El token necesita el scope `write:pullrequest:bitbucket` "Access Tokens" > "Create access token"*. El token necesita permiso para *Write* en *Pull Requests*.
Luego se debe guardar el token dentro del repositorio a configurar, en el archivo Luego se debe guardar el token dentro del repositorio a configurar, en el archivo
`.repository-token`. De no especificarse, los merges y tags se harán de forma local. `.repository-token`. De no especificarse, los merges y tags se harán de forma local.
+6 -5
View File
@@ -1,13 +1,14 @@
image: python:3.12 image: python:3.10
pipelines: pipelines:
branches: branches:
'main': 'main':
- step: - step:
name: "Tag main" name: "Tag and publish package"
caches: caches:
- pip - pip
script: script:
- cd $BITBUCKET_CLONE_DIR - pip install uv
- pip install . - uv run git-flow tag --token=$BEARER
- git-flow tag --token=$BEARER - uv build
- uv publish --token=$PYPI_TOKEN
+1 -1
View File
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
name = "git-flow" name = "git-flow"
description = "Git workflow automation to follow best practices" description = "Git workflow automation to follow best practices"
dynamic = ["version"] dynamic = ["version"]
requires-python = ">= 3.12" requires-python = ">= 3.10"
dependencies = [ dependencies = [
"requests" "requests"
] ]
+3
View File
@@ -21,6 +21,9 @@ COMMIT_TYPES = [
"wip", "wip",
] ]
WIP_BRANCH_PREFIX = "wip/"
TRASH_BRANCH_PREFIX = "trash/"
# Commits con estos valores incrementan el componente correspondiente # Commits con estos valores incrementan el componente correspondiente
SEMVER_MAJOR = 3 SEMVER_MAJOR = 3
SEMVER_MINOR = 2 SEMVER_MINOR = 2
+39 -11
View File
@@ -1,5 +1,5 @@
from argparse import ArgumentParser, Namespace from argparse import ArgumentParser, Namespace
from git_flow import COLOR_WHITE_BOLD, COLOR_GREEN, COLOR_RED, COLOR_RESET, GitFlowError from git_flow import COLOR_WHITE_BOLD, COLOR_GREEN, COLOR_RED, COLOR_RESET, TRASH_BRANCH_PREFIX, WIP_BRANCH_PREFIX, GitFlowError
from git_flow.command.base import Command from git_flow.command.base import Command
from git_flow.git import Git from git_flow.git import Git
@@ -18,7 +18,25 @@ class BranchCommand(Command):
parser.add_argument( parser.add_argument(
"environment", "environment",
nargs="?", nargs="?",
help="Entorno de las ramas a listar. Puede ser vacio para listar ramas de todos los entornos, un entorno válido, o 'trash' para listar las ramas eliminadas.", help="Entorno de las ramas a listar. Por defecto, es el entorno actual.",
)
parser.add_argument(
"--trash",
action="store_true",
help=f"Listar ramas en la 'papelera de reciclaje' (ramas que empiezan con '{TRASH_BRANCH_PREFIX}')"
)
parser.add_argument(
"--wip",
action="store_true",
help=f"Listar ramas 'en progreso' (ramas que empiezan con '{WIP_BRANCH_PREFIX}')"
)
parser.add_argument(
"--all",
action="store_true",
help=f"Listar ramas de todos los entornos"
) )
return parser return parser
@@ -29,19 +47,29 @@ class BranchCommand(Command):
self.current_branch = Git.get_current_branch() self.current_branch = Git.get_current_branch()
self.environments = self.flowconfig["flow.branches"].split(",") self.environments = self.flowconfig["flow.branches"].split(",")
if args.environment is None: if self.current_branch in self.environments:
current_environment = self.current_branch
else:
(current_environment, _) = self.get_branch_env_and_type(self.current_branch)
if args.trash:
self.show_branches(TRASH_BRANCH_PREFIX)
elif args.wip:
self.show_branches(WIP_BRANCH_PREFIX)
elif args.all:
self.show_envs() self.show_envs()
self.show_all_branches() self.show_all_branches()
elif args.environment is None:
self.show_envs()
self.show_env_branches(current_environment)
elif args.environment in self.environments: elif args.environment in self.environments:
self.show_envs() self.show_envs()
self.show_env_branches(args.environment) self.show_env_branches(args.environment)
elif args.environment == "trash":
self.show_trash_branches()
else: else:
raise GitFlowError(f"'{args.environment}' no es un entorno válido.") raise GitFlowError(f"'{args.environment}' no es un entorno válido.")
def show_trash_branches(self): def show_branches(self, prefix: str):
branches = self.get_trash_branches() branches = self.get_branches(prefix)
if not branches: if not branches:
print("No hay ramas a mostrar.") print("No hay ramas a mostrar.")
@@ -51,14 +79,14 @@ class BranchCommand(Command):
prefix = (COLOR_RED + "*") if branch == self.current_branch else " " prefix = (COLOR_RED + "*") if branch == self.current_branch else " "
print(prefix + " " + branch + COLOR_RESET) print(prefix + " " + branch + COLOR_RESET)
def get_trash_branches(self): def get_branches(self, prefix: str):
return Git("for-each-ref", "refs/heads/trash/", format=BRANCH_FORMAT).lines() return Git("for-each-ref", "refs/heads/" + prefix, format=BRANCH_FORMAT).lines()
def show_all_branches(self): def show_all_branches(self):
for environment in self.environments: for environment in self.environments:
self.show_env_branches(environment, len(self.environments) > 1) self.show_env_branches(environment, len(self.environments) > 1)
trash = len(self.get_trash_branches()) trash = len(self.get_branches(TRASH_BRANCH_PREFIX))
if trash: if trash:
print() print()
@@ -75,7 +103,7 @@ class BranchCommand(Command):
is_first_env = environment == self.environments[0] is_first_env = environment == self.environments[0]
pattern = "refs/heads/" if is_first_env else "refs/heads/release/" + environment + "/" pattern = "refs/heads/" if is_first_env else "refs/heads/release/" + environment + "/"
pattern += "*/*" pattern += "*/*"
exclude = ["refs/heads/trash/", "refs/heads/release/"] if is_first_env else [] exclude = ["refs/heads/" + TRASH_BRANCH_PREFIX, "refs/heads/release/"] if is_first_env else []
branches = Git("for-each-ref", pattern, exclude=exclude, format=BRANCH_FORMAT).lines() branches = Git("for-each-ref", pattern, exclude=exclude, format=BRANCH_FORMAT).lines()
print(COLOR_WHITE_BOLD + (environment if show_env else "Ramas") + COLOR_RESET) print(COLOR_WHITE_BOLD + (environment if show_env else "Ramas") + COLOR_RESET)
+5 -5
View File
@@ -1,6 +1,6 @@
from argparse import Namespace from argparse import Namespace
from datetime import datetime from datetime import datetime
from git_flow import COMMIT_TYPES, GitFlowError from git_flow import COMMIT_TYPES, TRASH_BRANCH_PREFIX, WIP_BRANCH_PREFIX, GitFlowError
from git_flow.command.base import Command from git_flow.command.base import Command
from git_flow.git import Git from git_flow.git import Git
@@ -31,8 +31,8 @@ class CommitCommand(Command):
) )
message = commit_type + ": " + commit_message message = commit_type + ": " + commit_message
if branch.startswith("wip/"): if branch.startswith(WIP_BRANCH_PREFIX):
original_branch = branch.removeprefix("wip/") original_branch = branch.removeprefix(WIP_BRANCH_PREFIX)
Git("commit", m=message).exec(print="Creando commit en rama WIP") Git("commit", m=message).exec(print="Creando commit en rama WIP")
if commit_type != "wip": if commit_type != "wip":
@@ -40,10 +40,10 @@ class CommitCommand(Command):
Git("switch", original_branch).exec(print="Volviendo a rama original") Git("switch", original_branch).exec(print="Volviendo a rama original")
Git("merge", branch, squash=True).exec(print="Squasheando commits WIP en uno solo") Git("merge", branch, squash=True).exec(print="Squasheando commits WIP en uno solo")
Git("commit", m=message).exec(print="Creando commit final de rama WIP") Git("commit", m=message).exec(print="Creando commit final de rama WIP")
Git("branch", branch, "trash/" + branch + "/" + suffix, move=True).exec(print="Backup de rama WIP") Git("branch", branch, TRASH_BRANCH_PREFIX + branch + "/" + suffix, move=True).exec(print="Backup de rama WIP")
else: else:
if commit_type == "wip": if commit_type == "wip":
Git("switch", "wip/" + branch, create=True).exec(print="Creando nueva rama WIP") Git("switch", WIP_BRANCH_PREFIX + branch, create=True).exec(print="Creando nueva rama WIP")
Git("commit", m=message).exec(print="Creando commit") Git("commit", m=message).exec(print="Creando commit")
+32 -13
View File
@@ -17,16 +17,15 @@ class MergeCommand(Command):
branch = self.ensure_right_branch() branch = self.ensure_right_branch()
(target, _) = self.get_branch_env_and_type(branch) (target, _) = self.get_branch_env_and_type(branch)
base = Git.get_first_fork_point(branch, target)
if "flow.remote" in self.flowconfig: if "flow.remote" in self.flowconfig:
remote = self.flowconfig["flow.remote"] remote = self.flowconfig["flow.remote"]
token = self.ensure_repository_token() token = self.ensure_repository_token()
self.run_remote(remote, token, base, branch, target) self.run_remote(remote, token, branch, target)
else: else:
self.run_local(base, branch, target) self.run_local(branch, target)
def run_remote(self, remote: str, token: str, base: str, branch: str, target: str): def run_remote(self, remote: str, token: str, branch: str, target: str):
self.ensure_clean_worktree(True) self.ensure_clean_worktree(True)
Git("switch", target).exec(print="Cambiando a rama destino") Git("switch", target).exec(print="Cambiando a rama destino")
@@ -34,26 +33,26 @@ class MergeCommand(Command):
Git("switch", "-").exec(print="Volviendo a rama a mergear") Git("switch", "-").exec(print="Volviendo a rama a mergear")
self.check_merge_conflicts(target) self.check_merge_conflicts(target)
self.show_commits_to_merge(base) self.show_commits_to_merge(target)
if self.confirm(f"¿Crear PR de '{branch}' a '{target}'?"): if self.confirm(f"¿Crear PR de '{branch}' a '{target}'?"):
Git("push", remote, branch, set_upstream=True).exec( Git("push", remote, branch, set_upstream=True).exec(
print="Subiendo rama al remoto para crear PR" print="Subiendo rama al remoto para crear PR"
) )
self.create_pull_request(token, base, branch, target) self.create_pull_request(token, branch, target)
def run_local(self, base: str, branch: str, target: str): def run_local(self, branch: str, target: str):
self.ensure_clean_worktree(False) self.ensure_clean_worktree(False)
self.check_merge_conflicts(target) self.check_merge_conflicts(target)
self.show_commits_to_merge(base) self.show_commits_to_merge(target)
if self.confirm(f"¿Mergear rama '{branch}' a '{target}'?"): if self.confirm(f"¿Mergear rama '{branch}' a '{target}'?"):
Git("switch", target).exec(print="Cambiando a rama destino") Git("switch", target).exec(print="Cambiando a rama destino")
Git("merge", branch, ff=False).exec(print="Mergeando") Git("merge", branch, ff=False).exec(print="Mergeando")
def show_commits_to_merge(self, base): def show_commits_to_merge(self, target):
commits = Git("log", base + "..", format="%s").lines() commits = Git("log", target + "..", format="%s").lines()
if not commits: if not commits:
raise GitFlowError("No hay cambios a mergear.") raise GitFlowError("No hay cambios a mergear.")
@@ -79,14 +78,34 @@ class MergeCommand(Command):
else: else:
self.success("No se detectaron merge conflicts.") self.success("No se detectaron merge conflicts.")
def create_pull_request(self, token: str, base: str, branch: str, target: str): def create_pull_request(self, token: str, branch: str, target: str):
changelog = Changelog() changelog = Changelog()
commits = Git("log", target + "..", format="%s").lines()
if len(commits) == 1:
title = commits[0]
else:
title = branch.replace("/", ": ").replace("-", " ")
if title.startswith("release: "):
title = title[title.index(' ', title.index(' ') + 1) + 1:]
self.info("Título del PR por defecto: " + title)
opt_title = self.prompt("[Opcional] Ingrese otro titulo para el PR")
if opt_title:
title = opt_title
message = self.get_remote_api(token).create_pull_request( message = self.get_remote_api(token).create_pull_request(
branch, branch,
target, target,
branch.replace("/", ": ").replace("-", " "), title,
changelog.generate_content(base, branch), changelog.generate_content(target, branch),
) )
self.success(message) self.success(message)
if self.confirm("¿Desea cambiar a la rama objetivo y bajar los cambios?"):
Git("switch", target).exec(print="Cambiando a rama objetivo")
Git("pull").exec(print="Obteniendo cambios")
+8 -1
View File
@@ -13,7 +13,11 @@ class NewCommand(Command):
def run(self, args: Namespace = Namespace()): def run(self, args: Namespace = Namespace()):
self.ensure_initialized() self.ensure_initialized()
self.ensure_right_branch() branch = self.ensure_right_branch()
envs = self.flowconfig["flow.branches"].split(",")
if branch not in envs:
self.warning("La rama actual no corresponde a un entorno configurado.")
self.info("Se creará una nueva rama de trabajo sobre la rama actual.") self.info("Se creará una nueva rama de trabajo sobre la rama actual.")
self.info("Debe seleccionar el tipo de cambio a realizar.") self.info("Debe seleccionar el tipo de cambio a realizar.")
@@ -21,6 +25,9 @@ class NewCommand(Command):
branch_type = self.choice("Tipos de cambio", BRANCH_TYPES) branch_type = self.choice("Tipos de cambio", BRANCH_TYPES)
new_branch = self._get_unique_branch_name(branch_type) new_branch = self._get_unique_branch_name(branch_type)
if "flow.remote" in self.flowconfig and Git.get_tracking_branch(branch):
Git("pull").exec(print="Actualizando rama actual")
if self.confirm(f"¿Crear rama '{new_branch}' sobre la rama actual?"): if self.confirm(f"¿Crear rama '{new_branch}' sobre la rama actual?"):
Git("switch", new_branch, create=True).exec(print="Creando nueva rama") Git("switch", new_branch, create=True).exec(print="Creando nueva rama")
+29 -9
View File
@@ -43,23 +43,34 @@ class ReleaseCommand(Command):
else f"release/{next_env}/{branch}" else f"release/{next_env}/{branch}"
) )
group = args.group base = Git.get_first_fork_point(branch, env)
commits = Git("log", base + ".." , format="%s").lines()
if not group and self.confirm("¿Desea agrupar este release con otra rama?", False): if not commits:
raise GitFlowError("No hay cambios a mergear.")
print("Cambios a pasar al proximo entorno:")
for commit in commits:
print("- " + commit)
group = args.group
grouping = group is not None
if not grouping:
if self.confirm("¿Desea agrupar este release con otra rama?", False):
grouping = True
group = self.choice("Grupo release: ", Git.get_branches("release/" + next_env + "/")) group = self.choice("Grupo release: ", Git.get_branches("release/" + next_env + "/"))
else: else:
group = next_env group = next_env
if has_remote: if has_remote and Git.get_tracking_branch(group):
Git("switch", group).exec(print="Cambiando a la rama objetivo") Git("switch", group).exec(print="Cambiando a la rama objetivo")
Git("pull").exec(print="Sincronizando cambios la rama objetivo") Git("pull").exec(print="Sincronizando cambios la rama objetivo")
Git("switch", "-").exec(print="Volviendo a la rama original") Git("switch", "-").exec(print="Volviendo a la rama original")
base = Git.get_first_fork_point(branch, env) if len(commits) > 1 and self.confirm(
commits = int(Git("rev-list", base + "..", count=True).firstline()) f"¿Desea reemplazar los {len(commits)} commits de la rama por uno solo?",
if commits > 1 and self.confirm(
f"¿Desea reemplazar los {commits} commits de la rama por uno solo?",
False False
): ):
self.info("Debe ingresar el mensaje del commit a crear.") self.info("Debe ingresar el mensaje del commit a crear.")
@@ -80,6 +91,15 @@ class ReleaseCommand(Command):
else: else:
Git("switch", next_branch, create=True).exec(print="Creando rama release") Git("switch", next_branch, create=True).exec(print="Creando rama release")
Git("rebase", base, next_branch, onto=group).exec( status = Git("rebase", base, next_branch, onto=group).code(
print="Moviendo cambios hacia el siguiente ambiente" print="Moviendo cambios hacia el siguiente ambiente"
) )
if status:
Git("rebase", abort=True).exec(
print="Deshaciendo cambios por conflictos"
)
raise GitFlowError("Error al realizar pasaje de cambios a rama objetivo")
elif grouping:
Git("switch", group).exec(print="Cambiando a rama objetivo")
Git("merge", next_branch, ff_only=True).exec(print="Mergeando cambios de la nueva rama")
+13 -14
View File
@@ -1,5 +1,6 @@
import subprocess import subprocess
from git_flow import GitFlowError
from git_flow.io import * from git_flow.io import *
@@ -63,32 +64,30 @@ class Git:
@staticmethod @staticmethod
def get_first_fork_point(branch: str, env: str): def get_first_fork_point(branch: str, env: str):
boundary_commits = list( # Lista de commits alcanzables por el entorno (por ejemplo, dev),
filter( env_commits = Git("rev-list", env + "..." + branch, first_parent=True).lines()
lambda c: c.startswith("-"), # boundary commits
Git(
"rev-list", env + "..." + branch, topo_order=True, boundary=True
).lines(),
)
)
if not boundary_commits: if not env_commits:
raise RuntimeError("No se encontro un commit base para realizar el rebase.") raise RuntimeError("No se encontro un commit base para realizar el rebase.")
return boundary_commits[-1].removeprefix("-") return Git("show", env_commits[-1] + "^", patch=False, format="%h").firstline()
@staticmethod @staticmethod
def _get_references(kind: str): def _get_references(kind: str):
prefix = "refs/" + kind prefix = "refs/" + kind
return list(map( return Git("for-each-ref", prefix + "**", format="%(refname:short)").lines()
lambda r: r.removeprefix(prefix),
Git("for-each-ref", prefix + "*", format="%(refname)").lines(),
))
@staticmethod @staticmethod
def is_repository() -> bool: def is_repository() -> bool:
return Git("rev-parse", is_inside_work_tree=True).code() == 0 return Git("rev-parse", is_inside_work_tree=True).code() == 0
@staticmethod
def get_tracking_branch(branch: str) -> str | None:
try:
return Git("rev-parse", branch + "@{upstream}", abbrev_ref=True).firstline()
except subprocess.CalledProcessError:
return None
def __init__(self, subcommand: str, *args: str, **kwargs: str | int | bool | list[str] | None) -> None: def __init__(self, subcommand: str, *args: str, **kwargs: str | int | bool | list[str] | None) -> None:
self.command = ["git", subcommand] self.command = ["git", subcommand]
+3
View File
@@ -79,6 +79,9 @@ def main():
command.error(str(e)) command.error(str(e))
except Exception as e: except Exception as e:
command.error("Ocurrió un error inesperado: " + str(e)) command.error("Ocurrió un error inesperado: " + str(e))
except KeyboardInterrupt as e:
print()
command.error("Ejecución abortada")
return return
Generated
+144
View File
@@ -0,0 +1,144 @@
version = 1
revision = 3
requires-python = ">=3.10"
[[package]]
name = "certifi"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" },
{ url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" },
{ url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" },
{ url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" },
{ url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" },
{ url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" },
{ url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" },
{ url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" },
{ url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" },
{ url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" },
{ url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" },
{ url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" },
{ url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" },
{ url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" },
{ url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" },
{ url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" },
{ url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" },
{ url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" },
{ url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" },
{ url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" },
{ url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" },
{ url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" },
{ url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" },
{ url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" },
{ url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" },
{ url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" },
{ url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" },
{ url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" },
{ url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" },
{ url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" },
{ url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" },
{ url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" },
{ url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" },
{ url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" },
{ url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" },
{ url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" },
{ url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" },
{ url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" },
{ url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" },
{ url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" },
{ url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" },
{ url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" },
{ url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" },
{ url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" },
{ url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" },
{ url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" },
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "git-flow"
source = { editable = "." }
dependencies = [
{ name = "requests" },
]
[package.metadata]
requires-dist = [{ name = "requests" }]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "urllib3"
version = "2.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
]