Skip to content

remote: add url=/token= to fetch/push/pull, plus dest_ref for fetch#2182

Open
cumulus13 wants to merge 1 commit into
gitpython-developers:mainfrom
cumulus13:feature/remote-fetch-url-token
Open

remote: add url=/token= to fetch/push/pull, plus dest_ref for fetch#2182
cumulus13 wants to merge 1 commit into
gitpython-developers:mainfrom
cumulus13:feature/remote-fetch-url-token

Conversation

@cumulus13

Copy link
Copy Markdown

Remote.fetch(), Remote.push(), and Remote.pull() could previously only operate against the remote's configured name/URL, forcing anyone who wanted to use an alternate URL (e.g. with an embedded credential) to first rewrite the remote's permanent config via set_url() -- persisting a token to .git/config as a side effect just to do a single operation. All three shell out to the underlying git transport command with the remote's name hardcoded as the target; git remote update/rename/set-url/prune etc. are not affected since those are config-only commands that don't accept an arbitrary URL to begin with.

Add to Remote.fetch(), Remote.push(), and Remote.pull():

  • url: use this URL for this call only. The remote's stored config is never read or written for the transport target when given; it takes precedence over the remote's name/configured url.

  • token: optional credential embedded into url's authority component for this call only (http/https only). Never written to config or logged, and scrubbed from GitCommandError text if the call fails, so a failure can't leak the token via an exception/log message.

Additionally, Remote.fetch() gets dest_ref (fetch-only -- push writes directly to the named destination ref regardless of url=, and pull's merge target is the checked-out branch, so neither has fetch's FETCH_HEAD-only footgun): a bare refspec entry (no ':dst') only updates FETCH_HEAD when fetching from an anonymous url, because unlike a named/configured remote, git has no remote..fetch pattern to complete a destination from. dest_ref lets a caller ask for a normal tracking ref instead:

  • True -> refs/remotes//
  • '...{branch}...' -> templated destination, applied per-entry
    when refspec is a list
  • plain string -> used verbatim (single bare entry only)
    Entries that already contain ':' are left untouched. Raises
    ValueError if given without url=, or if a plain-string dest_ref is
    combined with more than one bare refspec entry; raises TypeError
    for any other dest_ref type.

Existing calls (no url=/token=/dest_ref=) are unaffected on all three methods; behavior and the 'no refspec configured' assertion are unchanged for that path on fetch/pull.

Adds test coverage for: one-off URL fetch/push/pull (each confirming the configured remote url/config is untouched afterwards and, for push/pull, that the operation actually took effect against the alternate target), skipping the refspec-assertion when url= is given, token= rejecting non-http(s) urls on all three methods, token redaction in the raised GitCommandError on failure for all three, dest_ref=True creating a real tracking ref, a '{branch}' template applied across a list of refspecs, a plain-string dest_ref, explicit 'src:dst' refspecs being left untouched by dest_ref, and the ValueError/TypeError validation paths.

Remote.fetch(), Remote.push(), and Remote.pull() could previously
only operate against the remote's configured name/URL, forcing
anyone who wanted to use an alternate URL (e.g. with an embedded
credential) to first rewrite the remote's permanent config via
set_url() -- persisting a token to .git/config as a side effect
just to do a single operation. All three shell out to the
underlying git transport command with the remote's name hardcoded
as the target; git remote update/rename/set-url/prune etc. are not
affected since those are config-only commands that don't accept an
arbitrary URL to begin with.

Add to Remote.fetch(), Remote.push(), and Remote.pull():

- url: use this URL for this call only. The remote's stored config
  is never read or written for the transport target when given; it
  takes precedence over the remote's name/configured url.

- token: optional credential to embed into the transport target for
  this call only (http/https only). If url= is also given, the
  token is embedded into that url. If url= is omitted, the token is
  embedded into this remote's own *currently configured* url
  instead (read only, never written) -- so a caller can pass just
  token= on an ordinary named remote without repeating a url they
  already have configured, and without that token ever silently
  doing nothing. Never written to config or logged, and scrubbed
  from GitCommandError text if the call fails, so a failure can't
  leak the token via an exception/log message.

  All three methods resolve url=/token= identically via a shared
  Remote._resolve_transport_target() helper.

Additionally, Remote.fetch() gets dest_ref (fetch-only -- push
writes directly to the named destination ref regardless of url=,
and pull's merge target is the checked-out branch, so neither has
fetch's FETCH_HEAD-only footgun): a *bare* refspec entry (no
':dst') only updates FETCH_HEAD when fetching from an explicit
url/token target, because unlike a named/configured remote, git
has no remote.<name>.fetch pattern to complete a destination from.
dest_ref lets a caller ask for a normal tracking ref instead:
  * True             -> refs/remotes/<this-remote-name>/<branch>
  * '...{branch}...' -> templated destination, applied per-entry
                        when refspec is a list
  * plain string      -> used verbatim (single bare entry only)
Entries that already contain ':' are left untouched. Raises
ValueError if given without url=/token=, or if a plain-string
dest_ref is combined with more than one bare refspec entry; raises
TypeError for any other dest_ref type.

Existing calls (no url=/token=/dest_ref=) are unaffected on all
three methods; behavior and the 'no refspec configured' assertion
are unchanged for that path on fetch/pull.

Adds test coverage for: one-off URL fetch/push/pull (each
confirming the configured remote url/config is untouched afterwards
and, for push/pull, that the operation actually took effect against
the alternate target), skipping the refspec-assertion when
url=/token= is given, token= rejecting non-http(s) urls (both
explicit and derived-from-remote) on all three methods, token
redaction in the raised GitCommandError on failure for all three,
token= alone deriving and embedding into the remote's own
configured url on all three methods, dest_ref=True creating a real
tracking ref, a '{branch}' template applied across a list of
refspecs, a plain-string dest_ref, explicit 'src:dst' refspecs being
left untouched by dest_ref, and the ValueError/TypeError validation
paths.
@cumulus13
cumulus13 force-pushed the feature/remote-fetch-url-token branch from 908a6c9 to 0ab00e7 Compare July 23, 2026 20:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant