{"thread":{"id":"62710","subject":"[PATCH] completion: repair config completion for Zsh","startedAt":"2024-12-30T00:00:37Z","lastAt":"2025-01-07T01:23:58Z","messageCount":6,"participants":["D. Ben Knoble via GitGitGadget","Philippe Blain","D. Ben Knoble","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"509712","messageId":"pull.1860.git.git.1735516831782.gitgitgadget@gmail.com","threadId":"62710","inReplyTo":null,"subject":"[PATCH] completion: repair config completion for Zsh","fromName":"D. Ben Knoble via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-12-30T00:00:31Z","receivedAt":"2024-12-30T00:00:37Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n\nCommit 1e0ee4087e (completion: add and use\n__git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\nindirect variable syntax that is only valid for Bash, but the Zsh\ncompletion code relies on the Bash completion code to function. Zsh\nsupports a different indirect variable expansion using ${(P)var}, but in\n`emulate ksh` mode does not support Bash's ${!var}.\n\nThis manifests as completing strange config options like\n\"__git_first_level_config_vars_for_section_remote\" as a choice for the\ncommand line\n\n    git config set remote.\n\nUsing Zsh's C-x ? _complete_debug widget with the cursor at the end of\nthat command line captures a trace, in which we see\n\n    +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote\n     +__git_compute_first_level_config_vars_for_section:7> local section=remote\n     +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars\n      +__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'\n     +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote\n     +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '\n     +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '\n      +__gitcomp_nl:7> emulate -L zsh\n      +__gitcomp_nl:7> compset -P '*[=:]'\n      +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote\n\nWe perform the test for __git_compute_config_vars correctly, but the\n${!this_section} references are not expanded as expected.\n\nInstead, portably expand indirect references through the new\n__git_indirect. Contrary to some versions you might find online [1],\nthis version avoids echo non-portabilities [2] [3] and correctly quotes\nthe indirect expansion after eval (so that the result is not split or\nglobbed before being handed to printf).\n\n[1]: https://unix.stackexchange.com/a/41409/301073\n[2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769\n[3]: https://mywiki.wooledge.org/CatEchoLs\n\nThe following demo program demonstrates how this works:\n\n    b=1\n    indirect() {\n      eval printf '%s' \"\\\"\\$$1\\\"\"\n    }\n    f() {\n      # Comment this out to see that it works for globals, too. Or, use\n      # a value with spaces like '2 3 4' to see how it handles those.\n      local b=2\n      local a=b\n      test -n \"$(indirect $a)\" && echo nice\n    }\n    f\n\nWhen placed in a file \"demo\", then both\n    bash -x demo\nand\n    zsh -xc 'emulate ksh -c \". ./demo\"' |& tail\nprovide traces showing that \"$(indirect $a)\" produces 2 (or 1, with the\nglobal, or \"2 3 4\" as a single string, etc.).\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n---\n    completion: repair config completion for Zsh\n    \n    See commit for details.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v1\nPull-Request: https://github.com/git/git/pull/1860\n\n contrib/completion/git-completion.bash | 17 +++++++++++------\n 1 file changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b3b6aa3bae2..413911be3be 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()\n \t__git_config_vars_all=\"$(git --no-pager help --config)\"\n }\n \n+__git_indirect()\n+{\n+\teval printf '%s' \"\\\"\\$$1\\\"\"\n+}\n+\n __git_compute_first_level_config_vars_for_section ()\n {\n \tlocal section=\"$1\"\n \t__git_compute_config_vars\n \tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_first_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars\" | awk -F. \"/^${section}\\.[a-z]/ { print \\$2 }\")\"\n }\n@@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()\n \tlocal section=\"$1\"\n \t__git_compute_config_vars_all\n \tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_second_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars_all\" | awk -F. \"/^${section}\\.</ { print \\$3 }\")\"\n }\n@@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()\n \t\tlocal section=\"${pfx%.*.}\"\n \t\t__git_compute_second_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp \"${!this_section}\" \"$pfx\" \"$cur_\" \"$sfx\"\n+\t\t__gitcomp \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n \t\treturn\n \t\t;;\n \tbranch.*)\n@@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tpager.*)\n@@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git_remotes)\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tsubmodule.*)\n@@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git config -f \"$(__git rev-parse --show-toplevel)/.gitmodules\" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \t*.*)\n\nbase-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635\n-- \ngitgitgadget\n"},{"id":"509868","messageId":"4060690e-39b0-678a-75ef-af30beb439f3@gmail.com","threadId":"62710","inReplyTo":"pull.1860.git.git.1735516831782.gitgitgadget@gmail.com","subject":"Re: [PATCH] completion: repair config completion for Zsh","fromName":"Philippe Blain","fromEmail":"levraiphilippeblain@gmail.com","sentAt":"2025-01-03T17:20:59Z","receivedAt":"2025-01-03T17:21:07Z","isPatch":true,"sender":{"key":"levraiphilippeblain@gmail.com","avatar":"https://avatars.githubusercontent.com/u/44212482?v=4"},"body":"Hi Ben,\n\nLe 2024-12-29 à 19:00, D. Ben Knoble via GitGitGadget a écrit :\n> From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n> \n> Commit 1e0ee4087e (completion: add and use\n> __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\n> indirect variable syntax that is only valid for Bash, but the Zsh\n> completion code relies on the Bash completion code to function. Zsh\n> supports a different indirect variable expansion using ${(P)var}, but in\n> `emulate ksh` mode does not support Bash's ${!var}.\n> \n> This manifests as completing strange config options like\n> \"__git_first_level_config_vars_for_section_remote\" as a choice for the\n> command line\n> \n>     git config set remote.\n\nSorry for breaking the zsh completion with this change. Tip: it is customary\nin this project to CC commit authors when you identify a commit that\ncaused a regression :) \n\n> \n> Using Zsh's C-x ? _complete_debug widget with the cursor at the end of\n> that command line captures a trace, in which we see\n> \n>     +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote\n>      +__git_compute_first_level_config_vars_for_section:7> local section=remote\n>      +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars\n>       +__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'\n>      +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote\n>      +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote\n>     +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote\n>     +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '\n>      +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '\n>       +__gitcomp_nl:7> emulate -L zsh\n>       +__gitcomp_nl:7> compset -P '*[=:]'\n>       +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote\n\nI'm not sure this wall of text brings valuable information to the\ncommit message.\n \n> We perform the test for __git_compute_config_vars correctly, but the\n> ${!this_section} references are not expanded as expected.\n\nJust this part would be enough, I think.\n\n\n> Instead, portably expand indirect references through the new\n> __git_indirect. Contrary to some versions you might find online [1],\n> this version avoids echo non-portabilities [2] [3] and correctly quotes\n> the indirect expansion after eval (so that the result is not split or\n> globbed before being handed to printf).\n> \n> [1]: https://unix.stackexchange.com/a/41409/301073\n> [2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769\n> [3]: https://mywiki.wooledge.org/CatEchoLs\n> \n> The following demo program demonstrates how this works:\n> \n>     b=1\n>     indirect() {\n>       eval printf '%s' \"\\\"\\$$1\\\"\"\n>     }\n>     f() {\n>       # Comment this out to see that it works for globals, too. Or, use\n>       # a value with spaces like '2 3 4' to see how it handles those.\n>       local b=2\n>       local a=b\n>       test -n \"$(indirect $a)\" && echo nice\n>     }\n>     f\n> \n> When placed in a file \"demo\", then both\n>     bash -x demo\n> and\n>     zsh -xc 'emulate ksh -c \". ./demo\"' |& tail\n> provide traces showing that \"$(indirect $a)\" produces 2 (or 1, with the\n> global, or \"2 3 4\" as a single string, etc.).\n\nThanks, I verified that this indeed works, at least with on my (old) system\nwith Bash 3.2.57 and Zsh 5.0.8.\n\nI'm wondering what could be done to prevent regressions like this in \nthe future. In [1], brian mentions a way to test the whole test suite with Zsh\nin \"sh\" mode, which could be added to one of our CI jobs. \n\nBut the completion test script (t9902-completion.sh) is really Bash-specific \nand does 'exec bash' if it detects it is not running in Bash, so this would not \nhelp us anyway... \n\n[1] https://lore.kernel.org/git/20240426221154.2194139-1-sandals@crustytoothpaste.net/\n\n> \n> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n> ---\n>     completion: repair config completion for Zsh\n>     \n>     See commit for details.\n> \n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v1\n> Pull-Request: https://github.com/git/git/pull/1860\n> \n>  contrib/completion/git-completion.bash | 17 +++++++++++------\n>  1 file changed, 11 insertions(+), 6 deletions(-)\n> \n> diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\n> index b3b6aa3bae2..413911be3be 100644\n> --- a/contrib/completion/git-completion.bash\n> +++ b/contrib/completion/git-completion.bash\n> @@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()\n>  \t__git_config_vars_all=\"$(git --no-pager help --config)\"\n>  }\n>  \n> +__git_indirect()\n> +{\n> +\teval printf '%s' \"\\\"\\$$1\\\"\"\n> +}\n> +\n>  __git_compute_first_level_config_vars_for_section ()\n>  {\n>  \tlocal section=\"$1\"\n>  \t__git_compute_config_vars\n>  \tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n> -\ttest -n \"${!this_section}\" ||\n> +\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n>  \tprintf -v \"__git_first_level_config_vars_for_section_${section}\" %s \\\n>  \t\t\"$(echo \"$__git_config_vars\" | awk -F. \"/^${section}\\.[a-z]/ { print \\$2 }\")\"\n>  }\n> @@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()\n>  \tlocal section=\"$1\"\n>  \t__git_compute_config_vars_all\n>  \tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n> -\ttest -n \"${!this_section}\" ||\n> +\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n>  \tprintf -v \"__git_second_level_config_vars_for_section_${section}\" %s \\\n>  \t\t\"$(echo \"$__git_config_vars_all\" | awk -F. \"/^${section}\\.</ { print \\$3 }\")\"\n>  }\n> @@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()\n>  \t\tlocal section=\"${pfx%.*.}\"\n>  \t\t__git_compute_second_level_config_vars_for_section \"${section}\"\n>  \t\tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n> -\t\t__gitcomp \"${!this_section}\" \"$pfx\" \"$cur_\" \"$sfx\"\n> +\t\t__gitcomp \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n>  \t\treturn\n>  \t\t;;\n>  \tbranch.*)\n> @@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()\n>  \t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n>  \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n>  \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n> -\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n> +\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n>  \t\treturn\n>  \t\t;;\n>  \tpager.*)\n> @@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()\n>  \t\t__gitcomp_nl \"$(__git_remotes)\" \"$pfx\" \"$cur_\" \".\"\n>  \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n>  \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n> -\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n> +\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n>  \t\treturn\n>  \t\t;;\n>  \tsubmodule.*)\n> @@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()\n>  \t\t__gitcomp_nl \"$(__git config -f \"$(__git rev-parse --show-toplevel)/.gitmodules\" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')\" \"$pfx\" \"$cur_\" \".\"\n>  \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n>  \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n> -\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n> +\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n>  \t\treturn\n>  \t\t;;\n>  \t*.*)\n> \n> base-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635\n> \n\nthe code changes look good to me.\n\nThanks,\nPhilippe.\n"},{"id":"509872","messageId":"CALnO6CDpH4w8nOx7YDygtnY+G5NTBRbMGs7HTTDUHguYkCELiw@mail.gmail.com","threadId":"62710","inReplyTo":"4060690e-39b0-678a-75ef-af30beb439f3@gmail.com","subject":"Re: [PATCH] completion: repair config completion for Zsh","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2025-01-03T18:07:21Z","receivedAt":"2025-01-03T18:07:34Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"On Fri, Jan 3, 2025 at 12:21 PM Philippe Blain\n<levraiphilippeblain@gmail.com> wrote:\n>\n> Hi Ben,\n>\n> Le 2024-12-29 à 19:00, D. Ben Knoble via GitGitGadget a écrit :\n> > From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n> >\n> > Commit 1e0ee4087e (completion: add and use\n> > __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\n> > indirect variable syntax that is only valid for Bash, but the Zsh\n> > completion code relies on the Bash completion code to function. Zsh\n> > supports a different indirect variable expansion using ${(P)var}, but in\n> > `emulate ksh` mode does not support Bash's ${!var}.\n> >\n> > This manifests as completing strange config options like\n> > \"__git_first_level_config_vars_for_section_remote\" as a choice for the\n> > command line\n> >\n> >     git config set remote.\n>\n> Sorry for breaking the zsh completion with this change. Tip: it is customary\n> in this project to CC commit authors when you identify a commit that\n> caused a regression :)\n\nOnce upon a time I knew that ;) thanks for the reminder.\n\n>\n> > [wall of text]\n>\n> I'm not sure this wall of text brings valuable information to the\n> commit message.\n\nHow about something like the range-diff (pushed to the remote) pasted to the PR?\n\n> Thanks, I verified that this indeed works, at least with on my (old) system\n> with Bash 3.2.57 and Zsh 5.0.8.\n\nExcellent!\n\n>\n> I'm wondering what could be done to prevent regressions like this in\n> the future. In [1], brian mentions a way to test the whole test suite with Zsh\n> in \"sh\" mode, which could be added to one of our CI jobs.\n>\n> But the completion test script (t9902-completion.sh) is really Bash-specific\n> and does 'exec bash' if it detects it is not running in Bash, so this would not\n> help us anyway...\n>\n> [1] https://lore.kernel.org/git/20240426221154.2194139-1-sandals@crustytoothpaste.net/\n\nI have no real thoughts on the testing bit, but it would be nice to\navoid. Long-term, it may be necessary to either split the completion\nscripts (as Bash/Zsh diverge?) or document non-portable constructs\nspecific to the completion setup? Neither is automatic of the kind you\nhad in mind :(\n"},{"id":"509911","messageId":"pull.1860.v2.git.git.1736002073641.gitgitgadget@gmail.com","threadId":"62710","inReplyTo":"pull.1860.git.git.1735516831782.gitgitgadget@gmail.com","subject":"[PATCH v2] completion: repair config completion for Zsh","fromName":"D. Ben Knoble via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-01-04T14:47:53Z","receivedAt":"2025-01-04T14:47:57Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n\nCommit 1e0ee4087e (completion: add and use\n__git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\nindirect variable syntax that is only valid for Bash, but the Zsh\ncompletion code relies on the Bash completion code to function. Zsh\nsupports a different indirect variable expansion using ${(P)var}, but in\n`emulate ksh` mode does not support Bash's ${!var}.\n\nThis manifests as completing strange config options like\n\"__git_first_level_config_vars_for_section_remote\" as a choice for the\ncommand line\n\n    git config set remote.\n\nUsing Zsh's C-x ? _complete_debug widget with the cursor at the end of\nthat command line captures a trace, in which we see (some details\nelided):\n\n    +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote\n     +__git_compute_first_level_config_vars_for_section:7> local section=remote\n     +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars\n      +__git_compute_config_vars:7> test -n $'add.ignoreErrors\\nadvice.addEmbeddedRepo\\nadvice.addEmptyPathspec\\nadvice.addIgnoredFile[…]'\n     +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote\n     +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '\n     +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '\n      +__gitcomp_nl:7> emulate -L zsh\n      +__gitcomp_nl:7> compset -P '*[=:]'\n      +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote\n\nWe perform the test for __git_compute_config_vars correctly, but the\n${!this_section} references are not expanded as expected.\n\nInstead, portably expand indirect references through the new\n__git_indirect. Contrary to some versions you might find online [1],\nthis version avoids echo non-portabilities [2] [3] and correctly quotes\nthe indirect expansion after eval (so that the result is not split or\nglobbed before being handed to printf).\n\n[1]: https://unix.stackexchange.com/a/41409/301073\n[2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769\n[3]: https://mywiki.wooledge.org/CatEchoLs\n\nThe following demo program demonstrates how this works:\n\n    b=1\n    indirect() {\n      eval printf '%s' \"\\\"\\$$1\\\"\"\n    }\n    f() {\n      # Comment this out to see that it works for globals, too. Or, use\n      # a value with spaces like '2 3 4' to see how it handles those.\n      local b=2\n      local a=b\n      test -n \"$(indirect $a)\" && echo nice\n    }\n    f\n\nWhen placed in a file \"demo\", then both\n    bash -x demo\nand\n    zsh -xc 'emulate ksh -c \". ./demo\"' |& tail\nprovide traces showing that \"$(indirect $a)\" produces 2 (or 1, with the\nglobal, or \"2 3 4\" as a single string, etc.).\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n---\n    completion: repair config completion for Zsh\n    \n    See commit for details. V2: removed a wall of text in the commit\n    message.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v2\nPull-Request: https://github.com/git/git/pull/1860\n\nRange-diff vs v1:\n\n 1:  260c3bc296e ! 1:  daee7636106 completion: repair config completion for Zsh\n     @@ Commit message\n              git config set remote.\n      \n          Using Zsh's C-x ? _complete_debug widget with the cursor at the end of\n     -    that command line captures a trace, in which we see\n     +    that command line captures a trace, in which we see (some details\n     +    elided):\n      \n              +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote\n               +__git_compute_first_level_config_vars_for_section:7> local section=remote\n               +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars\n     -          +__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'\n     +          +__git_compute_config_vars:7> test -n $'add.ignoreErrors\\nadvice.addEmbeddedRepo\\nadvice.addEmptyPathspec\\nadvice.addIgnoredFile[…]'\n               +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote\n               +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote\n              +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote\n\n\n contrib/completion/git-completion.bash | 17 +++++++++++------\n 1 file changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b3b6aa3bae2..413911be3be 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()\n \t__git_config_vars_all=\"$(git --no-pager help --config)\"\n }\n \n+__git_indirect()\n+{\n+\teval printf '%s' \"\\\"\\$$1\\\"\"\n+}\n+\n __git_compute_first_level_config_vars_for_section ()\n {\n \tlocal section=\"$1\"\n \t__git_compute_config_vars\n \tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_first_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars\" | awk -F. \"/^${section}\\.[a-z]/ { print \\$2 }\")\"\n }\n@@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()\n \tlocal section=\"$1\"\n \t__git_compute_config_vars_all\n \tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_second_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars_all\" | awk -F. \"/^${section}\\.</ { print \\$3 }\")\"\n }\n@@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()\n \t\tlocal section=\"${pfx%.*.}\"\n \t\t__git_compute_second_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp \"${!this_section}\" \"$pfx\" \"$cur_\" \"$sfx\"\n+\t\t__gitcomp \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n \t\treturn\n \t\t;;\n \tbranch.*)\n@@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tpager.*)\n@@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git_remotes)\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tsubmodule.*)\n@@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git config -f \"$(__git rev-parse --show-toplevel)/.gitmodules\" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \t*.*)\n\nbase-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635\n-- \ngitgitgadget\n"},{"id":"510020","messageId":"pull.1860.v3.git.git.1736200026899.gitgitgadget@gmail.com","threadId":"62710","inReplyTo":"pull.1860.v2.git.git.1736002073641.gitgitgadget@gmail.com","subject":"[PATCH v3] completion: repair config completion for Zsh","fromName":"D. Ben Knoble via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-01-06T21:47:06Z","receivedAt":"2025-01-06T21:47:10Z","isPatch":true,"sender":{"key":"ben.knoble@gmail.com","avatar":"https://avatars.githubusercontent.com/u/22802209?v=4"},"body":"From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n\nCommit 1e0ee4087e (completion: add and use\n__git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\nindirect variable syntax that is only valid for Bash, but the Zsh\ncompletion code relies on the Bash completion code to function. Zsh\nsupports a different indirect variable expansion using ${(P)var}, but in\n`emulate ksh` mode does not support Bash's ${!var}.\n\nThis manifests as completing strange config options like\n\"__git_first_level_config_vars_for_section_remote\" as a choice for the\ncommand line\n\n    git config set remote.\n\nUsing Zsh's C-x ? _complete_debug widget with the cursor at the end of\nthat command line captures a trace, in which we see (some details\nelided):\n\n    +__git_complete_config_variable_name:7> __git_compute_first_level_config_vars_for_section remote\n     +__git_compute_first_level_config_vars_for_section:7> local section=remote\n     +__git_compute_first_level_config_vars_for_section:7> __git_compute_config_vars\n      +__git_compute_config_vars:7> test -n $'add.ignoreErrors\\nadvice.addEmbeddedRepo\\nadvice.addEmptyPathspec\\nadvice.addIgnoredFile[…]'\n     +__git_compute_first_level_config_vars_for_section:7> local this_section=__git_first_level_config_vars_for_section_remote\n     +__git_compute_first_level_config_vars_for_section:7> test -n __git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> local this_section=__git_first_level_config_vars_for_section_remote\n    +__git_complete_config_variable_name:7> __gitcomp_nl_append __git_first_level_config_vars_for_section_remote remote. '' ' '\n     +__gitcomp_nl_append:7> __gitcomp_nl __git_first_level_config_vars_for_section_remote remote. '' ' '\n      +__gitcomp_nl:7> emulate -L zsh\n      +__gitcomp_nl:7> compset -P '*[=:]'\n      +__gitcomp_nl:7> compadd -Q -S ' ' -p remote. -- __git_first_level_config_vars_for_section_remote\n\nWe perform the test for __git_compute_config_vars correctly, but the\n${!this_section} references are not expanded as expected.\n\nInstead, portably expand indirect references through the new\n__git_indirect. Contrary to some versions you might find online [1],\nthis version avoids echo non-portabilities [2] [3] and correctly quotes\nthe indirect expansion after eval (so that the result is not split or\nglobbed before being handed to printf).\n\n[1]: https://unix.stackexchange.com/a/41409/301073\n[2]: https://askubuntu.com/questions/715765/mysterious-behavior-of-echo-command#comment1056038_715769\n[3]: https://mywiki.wooledge.org/CatEchoLs\n\nThe following demo program demonstrates how this works:\n\n    b=1\n    indirect() {\n      eval printf '%s' \"\\\"\\$$1\\\"\"\n    }\n    f() {\n      # Comment this out to see that it works for globals, too. Or, use\n      # a value with spaces like '2 3 4' to see how it handles those.\n      local b=2\n      local a=b\n      test -n \"$(indirect $a)\" && echo nice\n    }\n    f\n\nWhen placed in a file \"demo\", then both\n    bash -x demo\nand\n    zsh -xc 'emulate ksh -c \". ./demo\"' |& tail\nprovide traces showing that \"$(indirect $a)\" produces 2 (or 1, with the\nglobal, or \"2 3 4\" as a single string, etc.).\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\nAcked-by: Philippe Blain <levraiphilippeblain@gmail.com>\n---\n    completion: repair config completion for Zsh\n    \n    With apologies to both CC'd if I've misunderstood the flow in\n    https://git-scm.com/docs/SubmittingPatches#_choosing_your_reviewers or\n    Phillipe's comments on v1.\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1860%2Fbenknoble%2Ffix-zsh-config-completion-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1860/benknoble/fix-zsh-config-completion-v3\nPull-Request: https://github.com/git/git/pull/1860\n\nRange-diff vs v2:\n\n 1:  daee7636106 ! 1:  13a42324658 completion: repair config completion for Zsh\n     @@ Commit message\n          global, or \"2 3 4\" as a single string, etc.).\n      \n          Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n     +    Acked-by: Philippe Blain <levraiphilippeblain@gmail.com>\n      \n       ## contrib/completion/git-completion.bash ##\n      @@ contrib/completion/git-completion.bash: __git_compute_config_vars_all ()\n\n\n contrib/completion/git-completion.bash | 17 +++++++++++------\n 1 file changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b3b6aa3bae2..413911be3be 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2737,12 +2737,17 @@ __git_compute_config_vars_all ()\n \t__git_config_vars_all=\"$(git --no-pager help --config)\"\n }\n \n+__git_indirect()\n+{\n+\teval printf '%s' \"\\\"\\$$1\\\"\"\n+}\n+\n __git_compute_first_level_config_vars_for_section ()\n {\n \tlocal section=\"$1\"\n \t__git_compute_config_vars\n \tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_first_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars\" | awk -F. \"/^${section}\\.[a-z]/ { print \\$2 }\")\"\n }\n@@ -2752,7 +2757,7 @@ __git_compute_second_level_config_vars_for_section ()\n \tlocal section=\"$1\"\n \t__git_compute_config_vars_all\n \tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\ttest -n \"${!this_section}\" ||\n+\ttest -n \"$(__git_indirect \"${this_section}\")\" ||\n \tprintf -v \"__git_second_level_config_vars_for_section_${section}\" %s \\\n \t\t\"$(echo \"$__git_config_vars_all\" | awk -F. \"/^${section}\\.</ { print \\$3 }\")\"\n }\n@@ -2907,7 +2912,7 @@ __git_complete_config_variable_name ()\n \t\tlocal section=\"${pfx%.*.}\"\n \t\t__git_compute_second_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_second_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp \"${!this_section}\" \"$pfx\" \"$cur_\" \"$sfx\"\n+\t\t__gitcomp \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"$sfx\"\n \t\treturn\n \t\t;;\n \tbranch.*)\n@@ -2917,7 +2922,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_direct \"$(__git_heads \"$pfx\" \"$cur_\" \".\")\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tpager.*)\n@@ -2934,7 +2939,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git_remotes)\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \tsubmodule.*)\n@@ -2944,7 +2949,7 @@ __git_complete_config_variable_name ()\n \t\t__gitcomp_nl \"$(__git config -f \"$(__git rev-parse --show-toplevel)/.gitmodules\" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')\" \"$pfx\" \"$cur_\" \".\"\n \t\t__git_compute_first_level_config_vars_for_section \"${section}\"\n \t\tlocal this_section=\"__git_first_level_config_vars_for_section_${section}\"\n-\t\t__gitcomp_nl_append \"${!this_section}\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n+\t\t__gitcomp_nl_append \"$(__git_indirect \"${this_section}\")\" \"$pfx\" \"$cur_\" \"${sfx:- }\"\n \t\treturn\n \t\t;;\n \t*.*)\n\nbase-commit: 306ab352f4e98f6809ce52fc4e5d63fb947d0635\n-- \ngitgitgadget\n"},{"id":"510037","messageId":"xmqq4j2bwh5g.fsf@gitster.g","threadId":"62710","inReplyTo":"pull.1860.v3.git.git.1736200026899.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] completion: repair config completion for Zsh","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-01-07T01:23:55Z","receivedAt":"2025-01-07T01:23:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"D. Ben Knoble via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: \"D. Ben Knoble\" <ben.knoble+github@gmail.com>\n>\n> Commit 1e0ee4087e (completion: add and use\n> __git_compute_first_level_config_vars_for_section, 2024-02-10) uses an\n> indirect variable syntax that is only valid for Bash, but the Zsh\n> completion code relies on the Bash completion code to function. Zsh\n> ...\n> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n> Acked-by: Philippe Blain <levraiphilippeblain@gmail.com>\n\nThanks, both.  Will queue.\n\n"}]}