diff mbox series

completion: repair config completion for Zsh

Message ID pull.1860.git.git.1735516831782.gitgitgadget@gmail.com (mailing list archive)
State Superseded
Headers show
Series completion: repair config completion for Zsh | expand

Commit Message

D. Ben Knoble Dec. 30, 2024, midnight UTC
From: "D. Ben Knoble" <ben.knoble+github@gmail.com>

Commit 1e0ee4087e (completion: add and use
__git_compute_first_level_config_vars_for_section, 2024-02-10) uses an
indirect variable syntax that is only valid for Bash, but the Zsh
completion code relies on the Bash completion code to function. Zsh
supports a different indirect variable expansion using ${(P)var}, but in
`emulate ksh` mode does not support Bash's ${!var}.

This manifests as completing strange config options like
"__git_first_level_config_vars_for_section_remote" as a choice for the
command line

    git config set remote.

Using Zsh's C-x ? _complete_debug widget with the cursor at the end of
that command line captures a trace, in which we see

    +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote
     +__git_compute_first_level_config_vars_for_section:7> local section=remote
     +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars
      +__git_compute_config_vars:7> test -n $'add.ignoreErrors\nadvice.addEmbeddedRepo\nadvice.addEmptyPathspec\nadvice.addIgnoredFile\nadvice.amWorkDir\nadvice.ambiguousFetchRefspec\nadvice.checkoutAmbiguousRemoteBranchName\nadvice.commitBeforeMerge\nadvice.detachedHead\nadvice.diverging\nadvice.fetchShowForcedUpdates\nadvice.forceDeleteBranch\nadvice.graftFileDeprecated\nadvice.ignoredHook\nadvice.implicitIdentity\nadvice.mergeConflict\nadvice.nestedTag\nadvice.objectNameWarning\nadvice.pushAlreadyExists\nadvice.pushFetchFirst\nadvice.pushNeedsForce\nadvice.pushNonFFCurrent\nadvice.pushNonFFMatching\nadvice.pushNonFastForward\nadvice.pushRefNeedsUpdate\nadvice.pushUnqualifiedRefName\nadvice.pushUpdateRejected\nadvice.rebaseTodoError\nadvice.refSyntax\nadvice.resetNoRefresh\nadvice.resolveConflict\nadvice.rmHints\nadvice.sequencerInUse\nadvice.setUpstreamFailure\nadvice.skippedCherryPicks\nadvice.sparseIndexExpanded\nadvice.statusAheadBehindWarning\nadvice.statusHints\nadvice.statusUoption\nadvice.submoduleAlternateErrorStrategyDie\nadvice.submoduleMergeConflict\nadvice.submodulesNotUpdated\nadvice.suggestDetachingHead\nadvice.updateSparsePath\nadvice.waitingForEditor\nadvice.worktreeAddOrphan\nalias.\nam.keepcr\nam.threeWay\napply.ignoreWhitespace\napply.whitespace\nattr.tree\nauthor.email\nauthor.name\nbitmapPseudoMerge.\nblame.blankBoundary\nblame.coloring\nblame.date\nblame.ignoreRevsFile\nblame.markIgnoredLines\nblame.markUnblamableLines\nblame.showEmail\nblame.showRoot\nbranch.\nbranch.autoSetupMerge\nbranch.autoSetupRebase\nbranch.sort\nbrowser.\nbundle.\nbundle.heuristic\nbundle.mode\nbundle.version\ncheckout.defaultRemote\ncheckout.guess\ncheckout.thresholdForParallelism\ncheckout.workers\nclean.requireForce\ncolor.advice\ncolor.advice.hint\ncolor.blame.highlightRecent\ncolor.blame.repeatedLines\ncolor.branch\ncolor.branch.current\ncolor.branch.local\ncolor.branch.plain\ncolor.branch.remote\ncolor.branch.reset\ncolor.branch.upstream\ncolor.branch.worktree\ncolor.decorate.HEAD\ncolor.decorate.branch\ncolor.decorate.grafted\ncolor.decorate.remoteBranch\ncolor.decorate.stash\ncolor.decorate.tag\ncolor.diff\ncolor.diff.commit\ncolor.diff.context\ncolor.diff.contextBold\ncolor.diff.contextDimmed\ncolor.diff.frag\ncolor.diff.func\ncolor.diff.meta\ncolor.diff.new\ncolor.diff.newBold\ncolor.diff.newDimmed\ncolor.diff.newMoved\ncolor.diff.newMovedAlternative\ncolor.diff.newMovedAlternativeDimmed\ncolor.diff.newMovedDimmed\ncolor.diff.old\ncolor.diff.oldBold\ncolor.diff.oldDimmed\ncolor.diff.oldMoved\ncolor.diff.oldMovedAlternative\ncolor.diff.oldMovedAlternativeDimmed\ncolor.diff.oldMovedDimmed\ncolor.diff.plain\ncolor.diff.whitespace\ncolor.grep\ncolor.grep.column\ncolor.grep.context\ncolor.grep.filename\ncolor.grep.function\ncolor.grep.lineNumber\ncolor.grep.match\ncolor.grep.matchContext\ncolor.grep.matchSelected\ncolor.grep.selected\ncolor.grep.separator\ncolor.interactive\ncolor.interactive.error\ncolor.interactive.header\ncolor.interactive.help\ncolor.interactive.plain\ncolor.interactive.prompt\ncolor.interactive.reset\ncolor.pager\ncolor.push\ncolor.push.error\ncolor.remote\ncolor.remote.error\ncolor.remote.hint\ncolor.remote.success\ncolor.remote.warning\ncolor.showBranch\ncolor.status\ncolor.status.added\ncolor.status.branch\ncolor.status.changed\ncolor.status.header\ncolor.status.localBranch\ncolor.status.noBranch\ncolor.status.remoteBranch\ncolor.status.unmerged\ncolor.status.untracked\ncolor.status.updated\ncolor.transport\ncolor.transport.rejected\ncolor.ui\ncolumn.branch\ncolumn.clean\ncolumn.status\ncolumn.tag\ncolumn.ui\ncommit.cleanup\ncommit.gpgSign\ncommit.status\ncommit.template\ncommit.verbose\ncommitGraph.changedPathsVersion\ncommitGraph.generationVersion\ncommitGraph.maxNewFilters\ncommitGraph.readChangedPaths\ncommitter.email\ncommitter.name\ncompletion.commands\ncore.abbrev\ncore.alternateRefsCommand\ncore.alternateRefsPrefixes\ncore.askPass\ncore.attributesFile\ncore.autocrlf\ncore.bare\ncore.bigFileThreshold\ncore.checkRoundtripEncoding\ncore.checkStat\ncore.commentChar\ncore.commentString\ncore.commitGraph\ncore.compression\ncore.createObject\ncore.deltaBaseCacheLimit\ncore.editor\ncore.eol\ncore.excludesFile\ncore.fileMode\ncore.filesRefLockTimeout\ncore.fsmonitor\ncore.fsmonitorHookVersion\ncore.fsync\ncore.fsyncMethod\ncore.fsyncObjectFiles\ncore.gitProxy\ncore.hideDotFiles\ncore.hooksPath\ncore.ignoreCase\ncore.ignoreStat\ncore.logAllRefUpdates\ncore.looseCompression\ncore.maxTreeDepth\ncore.multiPackIndex\ncore.notesRef\ncore.packedGitLimit\ncore.packedGitWindowSize\ncore.packedRefsTimeout\ncore.pager\ncore.precomposeUnicode\ncore.preferSymlinkRefs\ncore.preloadIndex\ncore.protectHFS\ncore.protectNTFS\ncore.quotePath\ncore.repositoryFormatVersion\ncore.restrictinheritedhandles\ncore.safecrlf\ncore.sharedRepository\ncore.sparseCheckout\ncore.sparseCheckoutCone\ncore.splitIndex\ncore.sshCommand\ncore.symlinks\ncore.trustctime\ncore.unsetenvvars\ncore.untrackedCache\ncore.useReplaceRefs\ncore.warnAmbiguousRefs\ncore.whitespace\ncore.worktree\ncredential.\ncredential.helper\ncredential.interactive\ncredential.useHttpPath\ncredential.username\ncredentialCache.ignoreSIGHUP\ncredentialStore.lockTimeoutMS\ndiff.\ndiff.algorithm\ndiff.autoRefreshIndex\ndiff.colorMoved\ndiff.colorMovedWS\ndiff.context\ndiff.dirstat\ndiff.dstPrefix\ndiff.external\ndiff.guitool\ndiff.ignoreSubmodules\ndiff.indentHeuristic\ndiff.interHunkContext\ndiff.mnemonicPrefix\ndiff.noPrefix\ndiff.orderFile\ndiff.relative\ndiff.renameLimit\ndiff.renames\ndiff.srcPrefix\ndiff.statGraphWidth\ndiff.statNameWidth\ndiff.submodule\ndiff.suppressBlankEmpty\ndiff.tool\ndiff.trustExitCode\ndiff.wordRegex\ndiff.wsErrorHighlight\ndifftool.\ndifftool.guiDefault\ndifftool.prompt\ndifftool.trustExitCode\nextensions.compatObjectFormat\nextensions.objectFormat\nextensions.refStorage\nextensions.worktreeConfig\nfastimport.unpackLimit\nfeature.\nfeature.experimental\nfeature.manyFiles\nfetch.all\nfetch.bundleCreationToken\nfetch.bundleURI\nfetch.fsck.\nfetch.fsck.skipList\nfetch.fsckObjects\nfetch.negotiationAlgorithm\nfetch.output\nfetch.parallel\nfetch.prune\nfetch.pruneTags\nfetch.recurseSubmodules\nfetch.showForcedUpdates\nfetch.unpackLimit\nfetch.writeCommitGraph\nfilter.\nformat.attach\nformat.cc\nformat.coverFromDescription\nformat.coverLetter\nformat.encodeEmailHeaders\nformat.filenameMaxLength\nformat.forceInBodyFrom\nformat.from\nformat.headers\nformat.mboxrd\nformat.noprefix\nformat.notes\nformat.numbered\nformat.outputDirectory\nformat.pretty\nformat.signOff\nformat.signature\nformat.signatureFile\nformat.subjectPrefix\nformat.suffix\nformat.thread\nformat.to\nformat.useAutoBase\nfsck.badDate\nfsck.badDateOverflow\nfsck.badEmail\nfsck.badFilemode\nfsck.badName\nfsck.badObjectSha1\nfsck.badParentSha1\nfsck.badRefFiletype\nfsck.badRefName\nfsck.badTagName\nfsck.badTimezone\nfsck.badTree\nfsck.badTreeSha1\nfsck.badType\nfsck.duplicateEntries\nfsck.emptyName\nfsck.extraHeaderEntry\nfsck.fullPathname\nfsck.gitattributesBlob\nfsck.gitattributesLarge\nfsck.gitattributesLineLength\nfsck.gitattributesMissing\nfsck.gitattributesSymlink\nfsck.gitignoreSymlink\nfsck.gitmodulesBlob\nfsck.gitmodulesLarge\nfsck.gitmodulesMissing\nfsck.gitmodulesName\nfsck.gitmodulesParse\nfsck.gitmodulesPath\nfsck.gitmodulesSymlink\nfsck.gitmodulesUpdate\nfsck.gitmodulesUrl\nfsck.hasDot\nfsck.hasDotdot\nfsck.hasDotgit\nfsck.largePathname\nfsck.mailmapSymlink\nfsck.missingAuthor\nfsck.missingCommitter\nfsck.missingEmail\nfsck.missingNameBeforeEmail\nfsck.missingObject\nfsck.missingSpaceBeforeDate\nfsck.missingSpaceBeforeEmail\nfsck.missingTag\nfsck.missingTagEntry\nfsck.missingTaggerEntry\nfsck.missingTree\nfsck.missingType\nfsck.missingTypeEntry\nfsck.multipleAuthors\nfsck.nulInCommit\nfsck.nulInHeader\nfsck.nullSha1\nfsck.skipList\nfsck.treeNotSorted\nfsck.unknownType\nfsck.unterminatedHeader\nfsck.zeroPaddedDate\nfsck.zeroPaddedFilemode\nfsmonitor.allowRemote\nfsmonitor.socketDir\ngc.\ngc.aggressiveDepth\ngc.aggressiveWindow\ngc.auto\ngc.autoDetach\ngc.autoPackLimit\ngc.bigPackThreshold\ngc.cruftPacks\ngc.logExpiry\ngc.maxCruftSize\ngc.packRefs\ngc.pruneExpire\ngc.recentObjectsHook\ngc.reflogExpire\ngc.reflogExpireUnreachable\ngc.repackFilter\ngc.repackFilterTo\ngc.rerereResolved\ngc.rerereUnresolved\ngc.worktreePruneExpire\ngc.writeCommitGraph\ngitcvs.allBinary\ngitcvs.commitMsgAnnotation\ngitcvs.dbDriver\ngitcvs.dbName\ngitcvs.dbPass\ngitcvs.dbTableNamePrefix\ngitcvs.dbUser\ngitcvs.enabled\ngitcvs.logFile\ngitcvs.usecrlfattr\ngitweb.avatar\ngitweb.blame\ngitweb.category\ngitweb.description\ngitweb.grep\ngitweb.highlight\ngitweb.owner\ngitweb.patches\ngitweb.pickaxe\ngitweb.remote_heads\ngitweb.showSizes\ngitweb.snapshot\ngitweb.url\ngpg.\ngpg.format\ngpg.minTrustLevel\ngpg.program\ngpg.ssh.allowedSignersFile\ngpg.ssh.defaultKeyCommand\ngpg.ssh.revocationFile\ngrep.column\ngrep.extendedRegexp\ngrep.fallbackToNoIndex\ngrep.fullName\ngrep.lineNumber\ngrep.patternType\ngrep.threads\ngui.blamehistoryctx\ngui.commitMsgWidth\ngui.copyBlameThreshold\ngui.diffContext\ngui.displayUntracked\ngui.encoding\ngui.fastCopyBlame\ngui.matchTrackingBranch\ngui.newBranchTemplate\ngui.pruneDuringFetch\ngui.spellingDictionary\ngui.trustmtime\nguitool.\nhelp.autoCorrect\nhelp.browser\nhelp.format\nhelp.htmlPath\nhttp.\nhttp.cookieFile\nhttp.curloptResolve\nhttp.delegation\nhttp.emptyAuth\nhttp.extraHeader\nhttp.followRedirects\nhttp.lowSpeedLimit\nhttp.lowSpeedTime\nhttp.maxRequests\nhttp.minSessions\nhttp.noEPSV\nhttp.pinnedPubkey\nhttp.postBuffer\nhttp.proactiveAuth\nhttp.proxy\nhttp.proxyAuthMethod\nhttp.proxySSLCAInfo\nhttp.proxySSLCert\nhttp.proxySSLCertPasswordProtected\nhttp.proxySSLKey\nhttp.saveCookies\nhttp.schannelCheckRevoke\nhttp.schannelUseSSLCAInfo\nhttp.sslBackend\nhttp.sslCAInfo\nhttp.sslCAPath\nhttp.sslCert\nhttp.sslCertPasswordProtected\nhttp.sslCipherList\nhttp.sslKey\nhttp.sslTry\nhttp.sslVerify\nhttp.sslVersion\nhttp.userAgent\nhttp.version\ni18n.commitEncoding\ni18n.logOutputEncoding\nimap.authMethod\nimap.folder\nimap.host\nimap.pass\nimap.port\nimap.preformattedHTML\nimap.sslverify\nimap.tunnel\nimap.user\ninclude.path\nincludeIf.\nindex.recordEndOfIndexEntries\nindex.recordOffsetTable\nindex.skipHash\nindex.sparse\nindex.threads\nindex.version\ninstaweb.browser\ninstaweb.httpd\ninstaweb.local\ninstaweb.modulePath\ninstaweb.port\ninteractive.diffFilter\ninteractive.singleKey\nlog.abbrevCommit\nlog.date\nlog.decorate\nlog.diffMerges\nlog.excludeDecoration\nlog.follow\nlog.graphColors\nlog.initialDecorationSet\nlog.mailmap\nlog.showRoot\nlog.showSignature\nlsrefs.unborn\nmailinfo.scissors\nmailmap.blob\nmailmap.file\nmaintenance.\nmaintenance.auto\nmaintenance.autoDetach\nmaintenance.commit-graph.auto\nmaintenance.incremental-repack.auto\nmaintenance.loose-objects.auto\nmaintenance.strategy\nman.\nman.viewer\nmerge.\nmerge.autoStash\nmerge.branchdesc\nmerge.conflictStyle\nmerge.defaultToUpstream\nmerge.directoryRenames\nmerge.ff\nmerge.guitool\nmerge.log\nmerge.renameLimit\nmerge.renames\nmerge.renormalize\nmerge.stat\nmerge.suppressDest\nmerge.tool\nmerge.verbosity\nmerge.verifySignatures\nmergetool.\nmergetool.guiDefault\nmergetool.hideResolved\nmergetool.keepBackup\nmergetool.keepTemporaries\nmergetool.meld.hasOutput\nmergetool.meld.useAutoMerge\nmergetool.prompt\nmergetool.writeToTemp\nnotes.\nnotes.displayRef\nnotes.mergeStrategy\nnotes.rewrite.\nnotes.rewriteMode\nnotes.rewriteRef\npack.allowPackReuse\npack.compression\npack.deltaCacheLimit\npack.deltaCacheSize\npack.depth\npack.indexVersion\npack.island\npack.islandCore\npack.packSizeLimit\npack.preferBitmapTips\npack.readReverseIndex\npack.threads\npack.useBitmapBoundaryTraversal\npack.useBitmaps\npack.useSparse\npack.window\npack.windowMemory\npack.writeBitmapHashCache\npack.writeBitmapLookupTable\npack.writeReverseIndex\npager.\npretty.\npromisor.quiet\nprotocol.\nprotocol.allow\nprotocol.version\npull.ff\npull.octopus\npull.rebase\npull.twohead\npush.autoSetupRemote\npush.default\npush.followTags\npush.gpgSign\npush.negotiate\npush.pushOption\npush.recurseSubmodules\npush.useBitmaps\npush.useForceIfIncludes\nrebase.abbreviateCommands\nrebase.autoSquash\nrebase.autoStash\nrebase.backend\nrebase.forkPoint\nrebase.instructionFormat\nrebase.maxLabelLength\nrebase.missingCommitsCheck\nrebase.rebaseMerges\nrebase.rescheduleFailedExec\nrebase.stat\nrebase.updateRefs\nreceive.advertiseAtomic\nreceive.advertisePushOptions\nreceive.autogc\nreceive.certNonceSeed\nreceive.certNonceSlop\nreceive.denyCurrentBranch\nreceive.denyDeleteCurrent\nreceive.denyDeletes\nreceive.denyNonFastForwards\nreceive.fsck.badDate\nreceive.fsck.badDateOverflow\nreceive.fsck.badEmail\nreceive.fsck.badFilemode\nreceive.fsck.badName\nreceive.fsck.badObjectSha1\nreceive.fsck.badParentSha1\nreceive.fsck.badRefFiletype\nreceive.fsck.badRefName\nreceive.fsck.badTagName\nreceive.fsck.badTimezone\nreceive.fsck.badTree\nreceive.fsck.badTreeSha1\nreceive.fsck.badType\nreceive.fsck.duplicateEntries\nreceive.fsck.emptyName\nreceive.fsck.extraHeaderEntry\nreceive.fsck.fullPathname\nreceive.fsck.gitattributesBlob\nreceive.fsck.gitattributesLarge\nreceive.fsck.gitattributesLineLength\nreceive.fsck.gitattributesMissing\nreceive.fsck.gitattributesSymlink\nreceive.fsck.gitignoreSymlink\nreceive.fsck.gitmodulesBlob\nreceive.fsck.gitmodulesLarge\nreceive.fsck.gitmodulesMissing\nreceive.fsck.gitmodulesName\nreceive.fsck.gitmodulesParse\nreceive.fsck.gitmodulesPath\nreceive.fsck.gitmodulesSymlink\nreceive.fsck.gitmodulesUpdate\nreceive.fsck.gitmodulesUrl\nreceive.fsck.hasDot\nreceive.fsck.hasDotdot\nreceive.fsck.hasDotgit\nreceive.fsck.largePathname\nreceive.fsck.mailmapSymlink\nreceive.fsck.missingAuthor\nreceive.fsck.missingCommitter\nreceive.fsck.missingEmail\nreceive.fsck.missingNameBeforeEmail\nreceive.fsck.missingObject\nreceive.fsck.missingSpaceBeforeDate\nreceive.fsck.missingSpaceBeforeEmail\nreceive.fsck.missingTag\nreceive.fsck.missingTagEntry\nreceive.fsck.missingTaggerEntry\nreceive.fsck.missingTree\nreceive.fsck.missingType\nreceive.fsck.missingTypeEntry\nreceive.fsck.multipleAuthors\nreceive.fsck.nulInCommit\nreceive.fsck.nulInHeader\nreceive.fsck.nullSha1\nreceive.fsck.skipList\nreceive.fsck.treeNotSorted\nreceive.fsck.unknownType\nreceive.fsck.unterminatedHeader\nreceive.fsck.zeroPaddedDate\nreceive.fsck.zeroPaddedFilemode\nreceive.fsckObjects\nreceive.hideRefs\nreceive.keepAlive\nreceive.maxInputSize\nreceive.procReceiveRefs\nreceive.shallowUpdate\nreceive.unpackLimit\nreceive.updateServerInfo\nreftable.blockSize\nreftable.geometricFactor\nreftable.indexObjects\nreftable.lockTimeout\nreftable.restartInterval\nremote.\nremote.pushDefault\nremotes.\nrepack.cruftDepth\nrepack.cruftThreads\nrepack.cruftWindow\nrepack.cruftWindowMemory\nrepack.packKeptObjects\nrepack.updateServerInfo\nrepack.useDeltaBaseOffset\nrepack.useDeltaIslands\nrepack.writeBitmaps\nrerere.autoUpdate\nrerere.enabled\nrevert.reference\nsafe.bareRepository\nsafe.directory\nsendemail.\nsendemail.aliasFileType\nsendemail.aliasesFile\nsendemail.annotate\nsendemail.bcc\nsendemail.cc\nsendemail.ccCmd\nsendemail.chainReplyTo\nsendemail.confirm\nsendemail.envelopeSender\nsendemail.forbidSendmailVariables\nsendemail.from\nsendemail.headerCmd\nsendemail.identity\nsendemail.mailmap\nsendemail.mailmap.blob\nsendemail.mailmap.file\nsendemail.multiEdit\nsendemail.signedOffByCc\nsendemail.smtpBatchSize\nsendemail.smtpDomain\nsendemail.smtpEncryption\nsendemail.smtpPass\nsendemail.smtpReloginDelay\nsendemail.smtpSSLCertPath\nsendemail.smtpServer\nsendemail.smtpServerOption\nsendemail.smtpServerPort\nsendemail.smtpUser\nsendemail.suppressCc\nsendemail.suppressFrom\nsendemail.thread\nsendemail.to\nsendemail.toCmd\nsendemail.transferEncoding\nsendemail.validate\nsendemail.xmailer\nsequence.editor\nshowBranch.default\nsparse.expectFilesOutsideOfPatterns\nsplitIndex.maxPercentChange\nsplitIndex.sharedIndexExpire\nssh.variant\nstash.showIncludeUntracked\nstash.showPatch\nstash.showStat\nstatus.aheadBehind\nstatus.branch\nstatus.displayCommentPrefix\nstatus.relativePaths\nstatus.renameLimit\nstatus.renames\nstatus.short\nstatus.showStash\nstatus.showUntrackedFiles\nstatus.submoduleSummary\nsubmodule.\nsubmodule.active\nsubmodule.alternateErrorStrategy\nsubmodule.alternateLocation\nsubmodule.fetchJobs\nsubmodule.propagateBranches\nsubmodule.recurse\ntag.forceSignAnnotated\ntag.gpgSign\ntag.sort\ntar.umask\ntrace2.configParams\ntrace2.destinationDebug\ntrace2.envVars\ntrace2.eventBrief\ntrace2.eventNesting\ntrace2.eventTarget\ntrace2.maxFiles\ntrace2.normalBrief\ntrace2.normalTarget\ntrace2.perfBrief\ntrace2.perfTarget\ntransfer.advertiseObjectInfo\ntransfer.advertiseSID\ntransfer.bundleURI\ntransfer.credentialsInUrl\ntransfer.fsckObjects\ntransfer.hideRefs\ntransfer.unpackLimit\nuploadarchive.allowUnreachable\nuploadpack.allowAnySHA1InWant\nuploadpack.allowFilter\nuploadpack.allowReachableSHA1InWant\nuploadpack.allowRefInWant\nuploadpack.allowTipSHA1InWant\nuploadpack.hideRefs\nuploadpack.keepAlive\nuploadpack.packObjectsHook\nuploadpackfilter.\nuploadpackfilter.allow\nuploadpackfilter.tree.maxDepth\nurl.\nuser.email\nuser.name\nuser.signingKey\nuser.useConfigOnly\nversionsort.suffix\nweb.browser\nworktree.guessRemote'
     +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote
     +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote
    +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote
    +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '
     +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '
      +__gitcomp_nl:7> emulate -L zsh
      +__gitcomp_nl:7> compset -P '*[=:]'
      +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote

We perform the test for __git_compute_config_vars correctly, but the
${!this_section} references are not expanded as expected.

Instead, portably expand indirect references through the new
__git_indirect. Contrary to some versions you might find online [1],
this version avoids echo non-portabilities [2] [3] and correctly quotes
the indirect expansion after eval (so that the result is not split or
globbed before being handed to printf).

[1]: https://unix.stackexchange.com/a/41409/301073
[2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769
[3]: https://mywiki.wooledge.org/CatEchoLs

The following demo program demonstrates how this works:

    b=1
    indirect() {
      eval printf '%s' "\"\$$1\""
    }
    f() {
      # Comment this out to see that it works for globals, too. Or, use
      # a value with spaces like '2 3 4' to see how it handles those.
      local b=2
      local a=b
      test -n "$(indirect $a)" && echo nice
    }
    f

When placed in a file "demo", then both
    bash -x demo
and
    zsh -xc 'emulate ksh -c ". ./demo"' |& tail
provide traces showing that "$(indirect $a)" produces 2 (or 1, with the
global, or "2 3 4" as a single string, etc.).

Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
---
    completion: repair config completion for Zsh
    
    See commit for details.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v1
Pull-Request: https://github.com/git/git/pull/1860

 contrib/completion/git-completion.bash | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)


base-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635

Comments

Philippe Blain Jan. 3, 2025, 5:20 p.m. UTC | #1
Hi Ben,

Le 2024-12-29 à 19:00, D. Ben Knoble via GitGitGadget a écrit :
> From: "D. Ben Knoble" <ben.knoble+github@gmail.com>
> 
> Commit 1e0ee4087e (completion: add and use
> __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an
> indirect variable syntax that is only valid for Bash, but the Zsh
> completion code relies on the Bash completion code to function. Zsh
> supports a different indirect variable expansion using ${(P)var}, but in
> `emulate ksh` mode does not support Bash's ${!var}.
> 
> This manifests as completing strange config options like
> "__git_first_level_config_vars_for_section_remote" as a choice for the
> command line
> 
>     git config set remote.

Sorry for breaking the zsh completion with this change. Tip: it is customary
in this project to CC commit authors when you identify a commit that
caused a regression :) 

> 
> Using Zsh's C-x ? _complete_debug widget with the cursor at the end of
> that command line captures a trace, in which we see
> 
>     +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote
>      +__git_compute_first_level_config_vars_for_section:7> local section=remote
>      +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars
>       +__git_compute_config_vars:7> test -n $'add.ignoreErrors\nadvice.addEmbeddedRepo\nadvice.addEmptyPathspec\nadvice.addIgnoredFile\nadvice.amWorkDir\nadvice.ambiguousFetchRefspec\nadvice.checkoutAmbiguousRemoteBranchName\nadvice.commitBeforeMerge\nadvice.detachedHead\nadvice.diverging\nadvice.fetchShowForcedUpdates\nadvice.forceDeleteBranch\nadvice.graftFileDeprecated\nadvice.ignoredHook\nadvice.implicitIdentity\nadvice.mergeConflict\nadvice.nestedTag\nadvice.objectNameWarning\nadvice.pushAlreadyExists\nadvice.pushFetchFirst\nadvice.pushNeedsForce\nadvice.pushNonFFCurrent\nadvice.pushNonFFMatching\nadvice.pushNonFastForward\nadvice.pushRefNeedsUpdate\nadvice.pushUnqualifiedRefName\nadvice.pushUpdateRejected\nadvice.rebaseTodoError\nadvice.refSyntax\nadvice.resetNoRefresh\nadvice.resolveConflict\nadvice.rmHints\nadvice.sequencerInUse\nadvice.setUpstreamFailure\nadvice.skippedCherryPicks\nadvice.sparseIndexExpanded\nadvice.statusAheadBehindWarning\nadvice.statusHints\nadvice.statusUoption\nadvice.submoduleAlternateErrorStrategyDie\nadvice.submoduleMergeConflict\nadvice.submodulesNotUpdated\nadvice.suggestDetachingHead\nadvice.updateSparsePath\nadvice.waitingForEditor\nadvice.worktreeAddOrphan\nalias.\nam.keepcr\nam.threeWay\napply.ignoreWhitespace\napply.whitespace\nattr.tree\nauthor.email\nauthor.name\nbitmapPseudoMerge.\nblame.blankBoundary\nblame.coloring\nblame.date\nblame.ignoreRevsFile\nblame.markIgnoredLines\nblame.markUnblamableLines\nblame.showEmail\nblame.showRoot\nbranch.\nbranch.autoSetupMerge\nbranch.autoSetupRebase\nbranch.sort\nbrowser.\nbundle.\nbundle.heuristic\nbundle.mode\nbundle.version\ncheckout.defaultRemote\ncheckout.guess\ncheckout.thresholdForParallelism\ncheckout.workers\nclean.requireForce\ncolor.advice\ncolor.advice.hint\ncolor.blame.highlightRecent\ncolor.blame.repeatedLines\ncolor.branch\ncolor.branch.current\ncolor.branch.local\ncolor.branch.plain\ncolor.branch.remote\ncolor.branch.reset\ncolor.branch.upstream\ncolor.branch.worktree\ncolor.decorate.HEAD\ncolor.decorate.branch\ncolor.decorate.grafted\ncolor.decorate.remoteBranch\ncolor.decorate.stash\ncolor.decorate.tag\ncolor.diff\ncolor.diff.commit\ncolor.diff.context\ncolor.diff.contextBold\ncolor.diff.contextDimmed\ncolor.diff.frag\ncolor.diff.func\ncolor.diff.meta\ncolor.diff.new\ncolor.diff.newBold\ncolor.diff.newDimmed\ncolor.diff.newMoved\ncolor.diff.newMovedAlternative\ncolor.diff.newMovedAlternativeDimmed\ncolor.diff.newMovedDimmed\ncolor.diff.old\ncolor.diff.oldBold\ncolor.diff.oldDimmed\ncolor.diff.oldMoved\ncolor.diff.oldMovedAlternative\ncolor.diff.oldMovedAlternativeDimmed\ncolor.diff.oldMovedDimmed\ncolor.diff.plain\ncolor.diff.whitespace\ncolor.grep\ncolor.grep.column\ncolor.grep.context\ncolor.grep.filename\ncolor.grep.function\ncolor.grep.lineNumber\ncolor.grep.match\ncolor.grep.matchContext\ncolor.grep.matchSelected\ncolor.grep.selected\ncolor.grep.separator\ncolor.interactive\ncolor.interactive.error\ncolor.interactive.header\ncolor.interactive.help\ncolor.interactive.plain\ncolor.interactive.prompt\ncolor.interactive.reset\ncolor.pager\ncolor.push\ncolor.push.error\ncolor.remote\ncolor.remote.error\ncolor.remote.hint\ncolor.remote.success\ncolor.remote.warning\ncolor.showBranch\ncolor.status\ncolor.status.added\ncolor.status.branch\ncolor.status.changed\ncolor.status.header\ncolor.status.localBranch\ncolor.status.noBranch\ncolor.status.remoteBranch\ncolor.status.unmerged\ncolor.status.untracked\ncolor.status.updated\ncolor.transport\ncolor.transport.rejected\ncolor.ui\ncolumn.branch\ncolumn.clean\ncolumn.status\ncolumn.tag\ncolumn.ui\ncommit.cleanup\ncommit.gpgSign\ncommit.status\ncommit.template\ncommit.verbose\ncommitGraph.changedPathsVersion\ncommitGraph.generationVersion\ncommitGraph.maxNewFilters\ncommitGraph.readChangedPaths\ncommitter.email\ncommitter.name\ncompletion.commands\ncore.abbrev\ncore.alternateRefsCommand\ncore.alternateRefsPrefixes\ncore.askPass\ncore.attributesFile\ncore.autocrlf\ncore.bare\ncore.bigFileThreshold\ncore.checkRoundtripEncoding\ncore.checkStat\ncore.commentChar\ncore.commentString\ncore.commitGraph\ncore.compression\ncore.createObject\ncore.deltaBaseCacheLimit\ncore.editor\ncore.eol\ncore.excludesFile\ncore.fileMode\ncore.filesRefLockTimeout\ncore.fsmonitor\ncore.fsmonitorHookVersion\ncore.fsync\ncore.fsyncMethod\ncore.fsyncObjectFiles\ncore.gitProxy\ncore.hideDotFiles\ncore.hooksPath\ncore.ignoreCase\ncore.ignoreStat\ncore.logAllRefUpdates\ncore.looseCompression\ncore.maxTreeDepth\ncore.multiPackIndex\ncore.notesRef\ncore.packedGitLimit\ncore.packedGitWindowSize\ncore.packedRefsTimeout\ncore.pager\ncore.precomposeUnicode\ncore.preferSymlinkRefs\ncore.preloadIndex\ncore.protectHFS\ncore.protectNTFS\ncore.quotePath\ncore.repositoryFormatVersion\ncore.restrictinheritedhandles\ncore.safecrlf\ncore.sharedRepository\ncore.sparseCheckout\ncore.sparseCheckoutCone\ncore.splitIndex\ncore.sshCommand\ncore.symlinks\ncore.trustctime\ncore.unsetenvvars\ncore.untrackedCache\ncore.useReplaceRefs\ncore.warnAmbiguousRefs\ncore.whitespace\ncore.worktree\ncredential.\ncredential.helper\ncredential.interactive\ncredential.useHttpPath\ncredential.username\ncredentialCache.ignoreSIGHUP\ncredentialStore.lockTimeoutMS\ndiff.\ndiff.algorithm\ndiff.autoRefreshIndex\ndiff.colorMoved\ndiff.colorMovedWS\ndiff.context\ndiff.dirstat\ndiff.dstPrefix\ndiff.external\ndiff.guitool\ndiff.ignoreSubmodules\ndiff.indentHeuristic\ndiff.interHunkContext\ndiff.mnemonicPrefix\ndiff.noPrefix\ndiff.orderFile\ndiff.relative\ndiff.renameLimit\ndiff.renames\ndiff.srcPrefix\ndiff.statGraphWidth\ndiff.statNameWidth\ndiff.submodule\ndiff.suppressBlankEmpty\ndiff.tool\ndiff.trustExitCode\ndiff.wordRegex\ndiff.wsErrorHighlight\ndifftool.\ndifftool.guiDefault\ndifftool.prompt\ndifftool.trustExitCode\nextensions.compatObjectFormat\nextensions.objectFormat\nextensions.refStorage\nextensions.worktreeConfig\nfastimport.unpackLimit\nfeature.\nfeature.experimental\nfeature.manyFiles\nfetch.all\nfetch.bundleCreationToken\nfetch.bundleURI\nfetch.fsck.\nfetch.fsck.skipList\nfetch.fsckObjects\nfetch.negotiationAlgorithm\nfetch.output\nfetch.parallel\nfetch.prune\nfetch.pruneTags\nfetch.recurseSubmodules\nfetch.showForcedUpdates\nfetch.unpackLimit\nfetch.writeCommitGraph\nfilter.\nformat.attach\nformat.cc\nformat.coverFromDescription\nformat.coverLetter\nformat.encodeEmailHeaders\nformat.filenameMaxLength\nformat.forceInBodyFrom\nformat.from\nformat.headers\nformat.mboxrd\nformat.noprefix\nformat.notes\nformat.numbered\nformat.outputDirectory\nformat.pretty\nformat.signOff\nformat.signature\nformat.signatureFile\nformat.subjectPrefix\nformat.suffix\nformat.thread\nformat.to\nformat.useAutoBase\nfsck.badDate\nfsck.badDateOverflow\nfsck.badEmail\nfsck.badFilemode\nfsck.badName\nfsck.badObjectSha1\nfsck.badParentSha1\nfsck.badRefFiletype\nfsck.badRefName\nfsck.badTagName\nfsck.badTimezone\nfsck.badTree\nfsck.badTreeSha1\nfsck.badType\nfsck.duplicateEntries\nfsck.emptyName\nfsck.extraHeaderEntry\nfsck.fullPathname\nfsck.gitattributesBlob\nfsck.gitattributesLarge\nfsck.gitattributesLineLength\nfsck.gitattributesMissing\nfsck.gitattributesSymlink\nfsck.gitignoreSymlink\nfsck.gitmodulesBlob\nfsck.gitmodulesLarge\nfsck.gitmodulesMissing\nfsck.gitmodulesName\nfsck.gitmodulesParse\nfsck.gitmodulesPath\nfsck.gitmodulesSymlink\nfsck.gitmodulesUpdate\nfsck.gitmodulesUrl\nfsck.hasDot\nfsck.hasDotdot\nfsck.hasDotgit\nfsck.largePathname\nfsck.mailmapSymlink\nfsck.missingAuthor\nfsck.missingCommitter\nfsck.missingEmail\nfsck.missingNameBeforeEmail\nfsck.missingObject\nfsck.missingSpaceBeforeDate\nfsck.missingSpaceBeforeEmail\nfsck.missingTag\nfsck.missingTagEntry\nfsck.missingTaggerEntry\nfsck.missingTree\nfsck.missingType\nfsck.missingTypeEntry\nfsck.multipleAuthors\nfsck.nulInCommit\nfsck.nulInHeader\nfsck.nullSha1\nfsck.skipList\nfsck.treeNotSorted\nfsck.unknownType\nfsck.unterminatedHeader\nfsck.zeroPaddedDate\nfsck.zeroPaddedFilemode\nfsmonitor.allowRemote\nfsmonitor.socketDir\ngc.\ngc.aggressiveDepth\ngc.aggressiveWindow\ngc.auto\ngc.autoDetach\ngc.autoPackLimit\ngc.bigPackThreshold\ngc.cruftPacks\ngc.logExpiry\ngc.maxCruftSize\ngc.packRefs\ngc.pruneExpire\ngc.recentObjectsHook\ngc.reflogExpire\ngc.reflogExpireUnreachable\ngc.repackFilter\ngc.repackFilterTo\ngc.rerereResolved\ngc.rerereUnresolved\ngc.worktreePruneExpire\ngc.writeCommitGraph\ngitcvs.allBinary\ngitcvs.commitMsgAnnotation\ngitcvs.dbDriver\ngitcvs.dbName\ngitcvs.dbPass\ngitcvs.dbTableNamePrefix\ngitcvs.dbUser\ngitcvs.enabled\ngitcvs.logFile\ngitcvs.usecrlfattr\ngitweb.avatar\ngitweb.blame\ngitweb.category\ngitweb.description\ngitweb.grep\ngitweb.highlight\ngitweb.owner\ngitweb.patches\ngitweb.pickaxe\ngitweb.remote_heads\ngitweb.showSizes\ngitweb.snapshot\ngitweb.url\ngpg.\ngpg.format\ngpg.minTrustLevel\ngpg.program\ngpg.ssh.allowedSignersFile\ngpg.ssh.defaultKeyCommand\ngpg.ssh.revocationFile\ngrep.column\ngrep.extendedRegexp\ngrep.fallbackToNoIndex\ngrep.fullName\ngrep.lineNumber\ngrep.patternType\ngrep.threads\ngui.blamehistoryctx\ngui.commitMsgWidth\ngui.copyBlameThreshold\ngui.diffContext\ngui.displayUntracked\ngui.encoding\ngui.fastCopyBlame\ngui.matchTrackingBranch\ngui.newBranchTemplate\ngui.pruneDuringFetch\ngui.spellingDictionary\ngui.trustmtime\nguitool.\nhelp.autoCorrect\nhelp.browser\nhelp.format\nhelp.htmlPath\nhttp.\nhttp.cookieFile\nhttp.curloptResolve\nhttp.delegation\nhttp.emptyAuth\nhttp.extraHeader\nhttp.followRedirects\nhttp.lowSpeedLimit\nhttp.lowSpeedTime\nhttp.maxRequests\nhttp.minSessions\nhttp.noEPSV\nhttp.pinnedPubkey\nhttp.postBuffer\nhttp.proactiveAuth\nhttp.proxy\nhttp.proxyAuthMethod\nhttp.proxySSLCAInfo\nhttp.proxySSLCert\nhttp.proxySSLCertPasswordProtected\nhttp.proxySSLKey\nhttp.saveCookies\nhttp.schannelCheckRevoke\nhttp.schannelUseSSLCAInfo\nhttp.sslBackend\nhttp.sslCAInfo\nhttp.sslCAPath\nhttp.sslCert\nhttp.sslCertPasswordProtected\nhttp.sslCipherList\nhttp.sslKey\nhttp.sslTry\nhttp.sslVerify\nhttp.sslVersion\nhttp.userAgent\nhttp.version\ni18n.commitEncoding\ni18n.logOutputEncoding\nimap.authMethod\nimap.folder\nimap.host\nimap.pass\nimap.port\nimap.preformattedHTML\nimap.sslverify\nimap.tunnel\nimap.user\ninclude.path\nincludeIf.\nindex.recordEndOfIndexEntries\nindex.recordOffsetTable\nindex.skipHash\nindex.sparse\nindex.threads\nindex.version\ninstaweb.browser\ninstaweb.httpd\ninstaweb.local\ninstaweb.modulePath\ninstaweb.port\ninteractive.diffFilter\ninteractive.singleKey\nlog.abbrevCommit\nlog.date\nlog.decorate\nlog.diffMerges\nlog.excludeDecoration\nlog.follow\nlog.graphColors\nlog.initialDecorationSet\nlog.mailmap\nlog.showRoot\nlog.showSignature\nlsrefs.unborn\nmailinfo.scissors\nmailmap.blob\nmailmap.file\nmaintenance.\nmaintenance.auto\nmaintenance.autoDetach\nmaintenance.commit-graph.auto\nmaintenance.incremental-repack.auto\nmaintenance.loose-objects.auto\nmaintenance.strategy\nman.\nman.viewer\nmerge.\nmerge.autoStash\nmerge.branchdesc\nmerge.conflictStyle\nmerge.defaultToUpstream\nmerge.directoryRenames\nmerge.ff\nmerge.guitool\nmerge.log\nmerge.renameLimit\nmerge.renames\nmerge.renormalize\nmerge.stat\nmerge.suppressDest\nmerge.tool\nmerge.verbosity\nmerge.verifySignatures\nmergetool.\nmergetool.guiDefault\nmergetool.hideResolved\nmergetool.keepBackup\nmergetool.keepTemporaries\nmergetool.meld.hasOutput\nmergetool.meld.useAutoMerge\nmergetool.prompt\nmergetool.writeToTemp\nnotes.\nnotes.displayRef\nnotes.mergeStrategy\nnotes.rewrite.\nnotes.rewriteMode\nnotes.rewriteRef\npack.allowPackReuse\npack.compression\npack.deltaCacheLimit\npack.deltaCacheSize\npack.depth\npack.indexVersion\npack.island\npack.islandCore\npack.packSizeLimit\npack.preferBitmapTips\npack.readReverseIndex\npack.threads\npack.useBitmapBoundaryTraversal\npack.useBitmaps\npack.useSparse\npack.window\npack.windowMemory\npack.writeBitmapHashCache\npack.writeBitmapLookupTable\npack.writeReverseIndex\npager.\npretty.\npromisor.quiet\nprotocol.\nprotocol.allow\nprotocol.version\npull.ff\npull.octopus\npull.rebase\npull.twohead\npush.autoSetupRemote\npush.default\npush.followTags\npush.gpgSign\npush.negotiate\npush.pushOption\npush.recurseSubmodules\npush.useBitmaps\npush.useForceIfIncludes\nrebase.abbreviateCommands\nrebase.autoSquash\nrebase.autoStash\nrebase.backend\nrebase.forkPoint\nrebase.instructionFormat\nrebase.maxLabelLength\nrebase.missingCommitsCheck\nrebase.rebaseMerges\nrebase.rescheduleFailedExec\nrebase.stat\nrebase.updateRefs\nreceive.advertiseAtomic\nreceive.advertisePushOptions\nreceive.autogc\nreceive.certNonceSeed\nreceive.certNonceSlop\nreceive.denyCurrentBranch\nreceive.denyDeleteCurrent\nreceive.denyDeletes\nreceive.denyNonFastForwards\nreceive.fsck.badDate\nreceive.fsck.badDateOverflow\nreceive.fsck.badEmail\nreceive.fsck.badFilemode\nreceive.fsck.badName\nreceive.fsck.badObjectSha1\nreceive.fsck.badParentSha1\nreceive.fsck.badRefFiletype\nreceive.fsck.badRefName\nreceive.fsck.badTagName\nreceive.fsck.badTimezone\nreceive.fsck.badTree\nreceive.fsck.badTreeSha1\nreceive.fsck.badType\nreceive.fsck.duplicateEntries\nreceive.fsck.emptyName\nreceive.fsck.extraHeaderEntry\nreceive.fsck.fullPathname\nreceive.fsck.gitattributesBlob\nreceive.fsck.gitattributesLarge\nreceive.fsck.gitattributesLineLength\nreceive.fsck.gitattributesMissing\nreceive.fsck.gitattributesSymlink\nreceive.fsck.gitignoreSymlink\nreceive.fsck.gitmodulesBlob\nreceive.fsck.gitmodulesLarge\nreceive.fsck.gitmodulesMissing\nreceive.fsck.gitmodulesName\nreceive.fsck.gitmodulesParse\nreceive.fsck.gitmodulesPath\nreceive.fsck.gitmodulesSymlink\nreceive.fsck.gitmodulesUpdate\nreceive.fsck.gitmodulesUrl\nreceive.fsck.hasDot\nreceive.fsck.hasDotdot\nreceive.fsck.hasDotgit\nreceive.fsck.largePathname\nreceive.fsck.mailmapSymlink\nreceive.fsck.missingAuthor\nreceive.fsck.missingCommitter\nreceive.fsck.missingEmail\nreceive.fsck.missingNameBeforeEmail\nreceive.fsck.missingObject\nreceive.fsck.missingSpaceBeforeDate\nreceive.fsck.missingSpaceBeforeEmail\nreceive.fsck.missingTag\nreceive.fsck.missingTagEntry\nreceive.fsck.missingTaggerEntry\nreceive.fsck.missingTree\nreceive.fsck.missingType\nreceive.fsck.missingTypeEntry\nreceive.fsck.multipleAuthors\nreceive.fsck.nulInCommit\nreceive.fsck.nulInHeader\nreceive.fsck.nullSha1\nreceive.fsck.skipList\nreceive.fsck.treeNotSorted\nreceive.fsck.unknownType\nreceive.fsck.unterminatedHeader\nreceive.fsck.zeroPaddedDate\nreceive.fsck.zeroPaddedFilemode\nreceive.fsckObjects\nreceive.hideRefs\nreceive.keepAlive\nreceive.maxInputSize\nreceive.procReceiveRefs\nreceive.shallowUpdate\nreceive.unpackLimit\nreceive.updateServerInfo\nreftable.blockSize\nreftable.geometricFactor\nreftable.indexObjects\nreftable.lockTimeout\nreftable.restartInterval\nremote.\nremote.pushDefault\nremotes.\nrepack.cruftDepth\nrepack.cruftThreads\nrepack.cruftWindow\nrepack.cruftWindowMemory\nrepack.packKeptObjects\nrepack.updateServerInfo\nrepack.useDeltaBaseOffset\nrepack.useDeltaIslands\nrepack.writeBitmaps\nrerere.autoUpdate\nrerere.enabled\nrevert.reference\nsafe.bareRepository\nsafe.directory\nsendemail.\nsendemail.aliasFileType\nsendemail.aliasesFile\nsendemail.annotate\nsendemail.bcc\nsendemail.cc\nsendemail.ccCmd\nsendemail.chainReplyTo\nsendemail.confirm\nsendemail.envelopeSender\nsendemail.forbidSendmailVariables\nsendemail.from\nsendemail.headerCmd\nsendemail.identity\nsendemail.mailmap\nsendemail.mailmap.blob\nsendemail.mailmap.file\nsendemail.multiEdit\nsendemail.signedOffByCc\nsendemail.smtpBatchSize\nsendemail.smtpDomain\nsendemail.smtpEncryption\nsendemail.smtpPass\nsendemail.smtpReloginDelay\nsendemail.smtpSSLCertPath\nsendemail.smtpServer\nsendemail.smtpServerOption\nsendemail.smtpServerPort\nsendemail.smtpUser\nsendemail.suppressCc\nsendemail.suppressFrom\nsendemail.thread\nsendemail.to\nsendemail.toCmd\nsendemail.transferEncoding\nsendemail.validate\nsendemail.xmailer\nsequence.editor\nshowBranch.default\nsparse.expectFilesOutsideOfPatterns\nsplitIndex.maxPercentChange\nsplitIndex.sharedIndexExpire\nssh.variant\nstash.showIncludeUntracked\nstash.showPatch\nstash.showStat\nstatus.aheadBehind\nstatus.branch\nstatus.displayCommentPrefix\nstatus.relativePaths\nstatus.renameLimit\nstatus.renames\nstatus.short\nstatus.showStash\nstatus.showUntrackedFiles\nstatus.submoduleSummary\nsubmodule.\nsubmodule.active\nsubmodule.alternateErrorStrategy\nsubmodule.alternateLocation\nsubmodule.fetchJobs\nsubmodule.propagateBranches\nsubmodule.recurse\ntag.forceSignAnnotated\ntag.gpgSign\ntag.sort\ntar.umask\ntrace2.configParams\ntrace2.destinationDebug\ntrace2.envVars\ntrace2.eventBrief\ntrace2.eventNesting\ntrace2.eventTarget\ntrace2.maxFiles\ntrace2.normalBrief\ntrace2.normalTarget\ntrace2.perfBrief\ntrace2.perfTarget\ntransfer.advertiseObjectInfo\ntransfer.advertiseSID\ntransfer.bundleURI\ntransfer.credentialsInUrl\ntransfer.fsckObjects\ntransfer.hideRefs\ntransfer.unpackLimit\nuploadarchive.allowUnreachable\nuploadpack.allowAnySHA1InWant\nuploadpack.allowFilter\nuploadpack.allowReachableSHA1InWant\nuploadpack.allowRefInWant\nuploadpack.allowTipSHA1InWant\nuploadpack.hideRefs\nuploadpack.keepAlive\nuploadpack.packObjectsHook\nuploadpackfilter.\nuploadpackfilter.allow\nuploadpackfilter.tree.maxDepth\nurl.\nuser.email\nuser.name\nuser.signingKey\nuser.useConfigOnly\nversionsort.suffix\nweb.browser\nworktree.guessRemote'
>      +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote
>      +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote
>     +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote
>     +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '
>      +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '
>       +__gitcomp_nl:7> emulate -L zsh
>       +__gitcomp_nl:7> compset -P '*[=:]'
>       +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote

I'm not sure this wall of text brings valuable information to the
commit message.
 
> We perform the test for __git_compute_config_vars correctly, but the
> ${!this_section} references are not expanded as expected.

Just this part would be enough, I think.


> Instead, portably expand indirect references through the new
> __git_indirect. Contrary to some versions you might find online [1],
> this version avoids echo non-portabilities [2] [3] and correctly quotes
> the indirect expansion after eval (so that the result is not split or
> globbed before being handed to printf).
> 
> [1]: https://unix.stackexchange.com/a/41409/301073
> [2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769
> [3]: https://mywiki.wooledge.org/CatEchoLs
> 
> The following demo program demonstrates how this works:
> 
>     b=1
>     indirect() {
>       eval printf '%s' "\"\$$1\""
>     }
>     f() {
>       # Comment this out to see that it works for globals, too. Or, use
>       # a value with spaces like '2 3 4' to see how it handles those.
>       local b=2
>       local a=b
>       test -n "$(indirect $a)" && echo nice
>     }
>     f
> 
> When placed in a file "demo", then both
>     bash -x demo
> and
>     zsh -xc 'emulate ksh -c ". ./demo"' |& tail
> provide traces showing that "$(indirect $a)" produces 2 (or 1, with the
> global, or "2 3 4" as a single string, etc.).

Thanks, I verified that this indeed works, at least with on my (old) system
with Bash 3.2.57 and Zsh 5.0.8.

I'm wondering what could be done to prevent regressions like this in 
the future. In [1], brian mentions a way to test the whole test suite with Zsh
in "sh" mode, which could be added to one of our CI jobs. 

But the completion test script (t9902-completion.sh) is really Bash-specific 
and does 'exec bash' if it detects it is not running in Bash, so this would not 
help us anyway... 

[1] https://lore.kernel.org/git/20240426221154.2194139-1-sandals@crustytoothpaste.net/

> 
> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>
> ---
>     completion: repair config completion for Zsh
>     
>     See commit for details.
> 
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v1
> Pull-Request: https://github.com/git/git/pull/1860
> 
>  contrib/completion/git-completion.bash | 17 +++++++++++------
>  1 file changed, 11 insertions(+), 6 deletions(-)
> 
> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
> index b3b6aa3bae2..413911be3be 100644
> --- a/contrib/completion/git-completion.bash
> +++ b/contrib/completion/git-completion.bash
> @@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()
>  	__git_config_vars_all="$(git --no-pager help --config)"
>  }
>  
> +__git_indirect()
> +{
> +	eval printf '%s' "\"\$$1\""
> +}
> +
>  __git_compute_first_level_config_vars_for_section ()
>  {
>  	local section="$1"
>  	__git_compute_config_vars
>  	local this_section="__git_first_level_config_vars_for_section_${section}"
> -	test -n "${!this_section}" ||
> +	test -n "$(__git_indirect "${this_section}")" ||
>  	printf -v "__git_first_level_config_vars_for_section_${section}" %s \
>  		"$(echo "$__git_config_vars" | awk -F. "/^${section}\.[a-z]/ { print \$2 }")"
>  }
> @@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()
>  	local section="$1"
>  	__git_compute_config_vars_all
>  	local this_section="__git_second_level_config_vars_for_section_${section}"
> -	test -n "${!this_section}" ||
> +	test -n "$(__git_indirect "${this_section}")" ||
>  	printf -v "__git_second_level_config_vars_for_section_${section}" %s \
>  		"$(echo "$__git_config_vars_all" | awk -F. "/^${section}\.</ { print \$3 }")"
>  }
> @@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()
>  		local section="${pfx%.*.}"
>  		__git_compute_second_level_config_vars_for_section "${section}"
>  		local this_section="__git_second_level_config_vars_for_section_${section}"
> -		__gitcomp "${!this_section}" "$pfx" "$cur_" "$sfx"
> +		__gitcomp "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "$sfx"
>  		return
>  		;;
>  	branch.*)
> @@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()
>  		__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
>  		__git_compute_first_level_config_vars_for_section "${section}"
>  		local this_section="__git_first_level_config_vars_for_section_${section}"
> -		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
> +		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
>  		return
>  		;;
>  	pager.*)
> @@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()
>  		__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
>  		__git_compute_first_level_config_vars_for_section "${section}"
>  		local this_section="__git_first_level_config_vars_for_section_${section}"
> -		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
> +		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
>  		return
>  		;;
>  	submodule.*)
> @@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()
>  		__gitcomp_nl "$(__git config -f "$(__git rev-parse --show-toplevel)/.gitmodules" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')" "$pfx" "$cur_" "."
>  		__git_compute_first_level_config_vars_for_section "${section}"
>  		local this_section="__git_first_level_config_vars_for_section_${section}"
> -		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
> +		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
>  		return
>  		;;
>  	*.*)
> 
> base-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635
> 

the code changes look good to me.

Thanks,
Philippe.
D. Ben Knoble Jan. 3, 2025, 6:07 p.m. UTC | #2
On Fri, Jan 3, 2025 at 12:21 PM Philippe Blain
<levraiphilippeblain@gmail.com> wrote:
>
> Hi Ben,
>
> Le 2024-12-29 à 19:00, D. Ben Knoble via GitGitGadget a écrit :
> > From: "D. Ben Knoble" <ben.knoble+github@gmail.com>
> >
> > Commit 1e0ee4087e (completion: add and use
> > __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an
> > indirect variable syntax that is only valid for Bash, but the Zsh
> > completion code relies on the Bash completion code to function. Zsh
> > supports a different indirect variable expansion using ${(P)var}, but in
> > `emulate ksh` mode does not support Bash's ${!var}.
> >
> > This manifests as completing strange config options like
> > "__git_first_level_config_vars_for_section_remote" as a choice for the
> > command line
> >
> >     git config set remote.
>
> Sorry for breaking the zsh completion with this change. Tip: it is customary
> in this project to CC commit authors when you identify a commit that
> caused a regression :)

Once upon a time I knew that ;) thanks for the reminder.

>
> > [wall of text]
>
> I'm not sure this wall of text brings valuable information to the
> commit message.

How about something like the range-diff (pushed to the remote) pasted to the PR?

> Thanks, I verified that this indeed works, at least with on my (old) system
> with Bash 3.2.57 and Zsh 5.0.8.

Excellent!

>
> I'm wondering what could be done to prevent regressions like this in
> the future. In [1], brian mentions a way to test the whole test suite with Zsh
> in "sh" mode, which could be added to one of our CI jobs.
>
> But the completion test script (t9902-completion.sh) is really Bash-specific
> and does 'exec bash' if it detects it is not running in Bash, so this would not
> help us anyway...
>
> [1] https://lore.kernel.org/git/20240426221154.2194139-1-sandals@crustytoothpaste.net/

I have no real thoughts on the testing bit, but it would be nice to
avoid. Long-term, it may be necessary to either split the completion
scripts (as Bash/Zsh diverge?) or document non-portable constructs
specific to the completion setup? Neither is automatic of the kind you
had in mind :(
diff mbox series

Patch

diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index b3b6aa3bae2..413911be3be 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -2737,12 +2737,17 @@  __git_compute_config_vars_all ()
 	__git_config_vars_all="$(git --no-pager help --config)"
 }
 
+__git_indirect()
+{
+	eval printf '%s' "\"\$$1\""
+}
+
 __git_compute_first_level_config_vars_for_section ()
 {
 	local section="$1"
 	__git_compute_config_vars
 	local this_section="__git_first_level_config_vars_for_section_${section}"
-	test -n "${!this_section}" ||
+	test -n "$(__git_indirect "${this_section}")" ||
 	printf -v "__git_first_level_config_vars_for_section_${section}" %s \
 		"$(echo "$__git_config_vars" | awk -F. "/^${section}\.[a-z]/ { print \$2 }")"
 }
@@ -2752,7 +2757,7 @@  __git_compute_second_level_config_vars_for_section ()
 	local section="$1"
 	__git_compute_config_vars_all
 	local this_section="__git_second_level_config_vars_for_section_${section}"
-	test -n "${!this_section}" ||
+	test -n "$(__git_indirect "${this_section}")" ||
 	printf -v "__git_second_level_config_vars_for_section_${section}" %s \
 		"$(echo "$__git_config_vars_all" | awk -F. "/^${section}\.</ { print \$3 }")"
 }
@@ -2907,7 +2912,7 @@  __git_complete_config_variable_name ()
 		local section="${pfx%.*.}"
 		__git_compute_second_level_config_vars_for_section "${section}"
 		local this_section="__git_second_level_config_vars_for_section_${section}"
-		__gitcomp "${!this_section}" "$pfx" "$cur_" "$sfx"
+		__gitcomp "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "$sfx"
 		return
 		;;
 	branch.*)
@@ -2917,7 +2922,7 @@  __git_complete_config_variable_name ()
 		__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	pager.*)
@@ -2934,7 +2939,7 @@  __git_complete_config_variable_name ()
 		__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	submodule.*)
@@ -2944,7 +2949,7 @@  __git_complete_config_variable_name ()
 		__gitcomp_nl "$(__git config -f "$(__git rev-parse --show-toplevel)/.gitmodules" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')" "$pfx" "$cur_" "."
 		__git_compute_first_level_config_vars_for_section "${section}"
 		local this_section="__git_first_level_config_vars_for_section_${section}"
-		__gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
+		__gitcomp_nl_append "$(__git_indirect "${this_section}")" "$pfx" "$cur_" "${sfx:- }"
 		return
 		;;
 	*.*)