Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9a73992
Bump flatted from 3.2.9 to 3.4.2
dependabot[bot] Mar 20, 2026
4dd2008
Remove beta pill and "experimental" copy for Hooks settings
mxie Mar 23, 2026
72dd6ab
Add gitHooksEnvEnabled metric to daily stats
niik Mar 24, 2026
7a8872b
Release 3.5.7-beta3
tidy-dev Mar 24, 2026
2d6ca40
Add CODEOWNERS to auto-request reviews from code-reviewers
tidy-dev Mar 24, 2026
d9496c7
Cleanup outdated process docs to align with unified team process
tidy-dev Mar 25, 2026
41f619f
Update docs/process/pull-requests.md
tidy-dev Mar 25, 2026
b7b6288
Update docs/process/issue-triage.md
tidy-dev Mar 25, 2026
63d84e0
Merge pull request #21854 from desktop/cleanup-outdated-process-docs
tidy-dev Mar 25, 2026
8c74327
Bump picomatch from 2.2.2 to 2.3.2
dependabot[bot] Mar 25, 2026
fd7144f
store last selected worktree, and open that when reselecting repo
Mar 26, 2026
538dce7
lint fix
Mar 26, 2026
f0ba473
small bug fixes
Mar 26, 2026
ba3b2bf
minor updates
Mar 26, 2026
3420237
Add option to display commit author identity and git config source
voiduin Mar 29, 2026
30f6da9
Fix CLI install to it points to github-desktop-plus-cli.sh
gingerbeardman Mar 29, 2026
f80fcaa
FIX: indefinite fetch spinner.
Mar 30, 2026
5f900e2
Merge pull request #114 from gingerbeardman/patch-2
pol-rivero Mar 30, 2026
439dc30
Also update InstalledCLIPath and run Prettier
pol-rivero Mar 30, 2026
671e6e7
Run Prettier
pol-rivero Mar 30, 2026
6cf045f
Merge pull request #115 from Jacko1394/fix-hang-on-spinner
pol-rivero Mar 30, 2026
fe05013
Merge pull request #21845 from desktop/hooks-stats
niik Mar 30, 2026
7f852ac
Merge pull request #21849 from desktop/add-codeowners
tidy-dev Mar 30, 2026
c3a2f6d
liiiint
niik Mar 30, 2026
d48920c
Make ariaDescribedBy attributes associate correctly by setting id
niik Mar 30, 2026
66869b6
Consistent styling of setting descriptions across all preference tabs
niik Mar 30, 2026
8831839
Merge pull request #21846 from desktop/releases/3.5.7-beta3
tidy-dev Mar 30, 2026
afd1053
Fix for CI-CD linter and change view tooltip with config
voiduin Mar 30, 2026
e2445cc
Merge pull request #21856 from desktop/dependabot/npm_and_yarn/picoma…
sergiou87 Mar 30, 2026
0a538eb
Merge pull request #21830 from desktop/dependabot/npm_and_yarn/flatte…
sergiou87 Mar 30, 2026
e0ac10f
Replace checkbox to Git section and fix style in repo settings
voiduin Mar 30, 2026
92e22fb
feat: add interactive tooltip and catch
voiduin Mar 30, 2026
3380d03
Merge pull request #21844 from desktop/mx/remove-beta-pill
sergiou87 Mar 31, 2026
c5e0654
Release 3.5.7
tidy-dev Mar 31, 2026
90f899b
Merge pull request #112 from voiduin/feature/show-commit-author-confi…
pol-rivero Mar 31, 2026
3864b5d
feat: add visual warning for mismatched commit email
voiduin Mar 31, 2026
f5cef72
Merge pull request #118 from voiduin/feature/add-warning-yellow-email
pol-rivero Apr 1, 2026
9e02270
Merge remote-tracking branch 'upstream/releases/3.5.7'
pol-rivero Apr 1, 2026
62cf767
Fix first-click switch to main worktree
Apr 1, 2026
23ce8ac
Merge pull request #106 from coocoo1112/cooperj/storeLastSelectedWork…
pol-rivero Apr 2, 2026
eb89318
feat(sidebar): show worktrees under their repository
ignatremizov Mar 26, 2026
f0b2324
fix(sidebar): avoid stash metric failures for transient synthetic wor…
ignatremizov Apr 2, 2026
43a2ddc
fix(sidebar): let sidebar parent rows open the main worktree explicitly
ignatremizov Apr 2, 2026
7aae8cf
fix(sidebar): unify linked worktree grouping and transient selection …
ignatremizov Apr 3, 2026
cf8bcb6
fix(add-repository): make local repository dialog submission reliable
ignatremizov Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Automatically request review from desktop/code-reviewers on all PRs
* @desktop/code-reviewers
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"productName": "GitHub Desktop Plus",
"bundleID": "com.github.GitHubClient",
"companyName": "GitHub, Inc.",
"version": "3.5.7-beta2",
"version": "3.5.7",
"main": "./main.js",
"repository": {
"type": "git",
Expand Down
9 changes: 9 additions & 0 deletions app/src/lib/app-state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Account } from '../models/account'
import { CommitIdentity } from '../models/commit-identity'
import { IConfigValueOrigin } from './git/config'
import { IDiff, ImageDiffType } from '../models/diff'
import { Repository, ILocalRepositoryState } from '../models/repository'
import { Branch, IAheadBehind } from '../models/branch'
Expand Down Expand Up @@ -321,6 +322,9 @@ export interface IAppState {
/** Whether or not the worktrees dropdown should be shown in the toolbar */
readonly showWorktrees: boolean

/** Whether linked worktrees should be shown under their repository in the sidebar */
readonly showWorktreesInSidebar: boolean

/** Whether or not the Compare tab should be shown in the repository view */
readonly showCompareTab: boolean

Expand Down Expand Up @@ -363,6 +367,8 @@ export interface IAppState {
*/
readonly commitSpellcheckEnabled: boolean

readonly showCommitAuthorInfo: boolean

/**
* Record of what logged in users have been checked to see if thank you is in
* order for external contributions in latest release.
Expand Down Expand Up @@ -562,6 +568,9 @@ export interface IRepositoryState {
*/
readonly commitAuthor: CommitIdentity | null

readonly commitAuthorNameOrigin: IConfigValueOrigin | null
readonly commitAuthorEmailOrigin: IConfigValueOrigin | null

readonly branchesState: IBranchesState

readonly worktreesState: IWorktreesState
Expand Down
108 changes: 108 additions & 0 deletions app/src/lib/git/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,111 @@ async function removeConfigValueInPath(

await git(flags, path || __dirname, 'removeConfigValueInPath', options)
}

export interface IConfigValueOrigin {
readonly value: string
readonly scope: string
readonly origin: string
}

/**
* Look up a config value along with its source file and scope.
* Requires Git 2.26+ for --show-scope.
*/
export async function getConfigValueWithOrigin(
repository: Repository,
name: string
): Promise<IConfigValueOrigin | null> {
const result = await git(
['config', '--show-origin', '--show-scope', '-z', name],
repository.path,
'getConfigValueWithOrigin',
// 0 = found, 1 = key not set, 128 = not a git repo or git error
{ successExitCodes: new Set([0, 1, 128]) }
)

if (result.exitCode !== 0) {
return null
}

const parts = result.stdout.split('\0')
if (parts.length >= 3) {
return {
scope: parts[0],
origin: parts[1],
value: parts[2],
}
}

return null
}

/**
* Extract the file path from a config value origin, stripping the `file:` prefix.
* When repositoryPath is provided, relative paths (e.g. `.git/config` for local
* scope) are resolved to absolute paths.
*/
export function getOriginFilePath(
origin: IConfigValueOrigin,
repositoryPath?: string
): string {
const filePath = origin.origin.replace(/^file:/, '')
// Git returns relative paths for local/worktree scope (e.g. `.git/config`)
if (repositoryPath && !/^([a-zA-Z]:|[/\\])/.test(filePath)) {
const base = repositoryPath.replace(/[\\/]+$/, '')
return `${base}/${filePath}`
}
return filePath
}

/**
* Check whether a global-scoped config value comes from a conditionally
* included file (via includeIf directive) rather than a standard location.
*/
export function isConditionalInclude(origin: IConfigValueOrigin): boolean {
if (origin.scope !== 'global') {
return false
}
const filePath = getOriginFilePath(origin)
return (
!/[/\\]\.gitconfig$/i.test(filePath) &&
!/[/\\]\.config[/\\]git[/\\]config$/i.test(filePath)
)
}

/** Format a human-readable scope description for a config value origin. */
export function formatConfigScope(origin: IConfigValueOrigin): string {
if (origin.scope === 'local') {
return 'local'
} else if (origin.scope === 'system') {
return 'system'
} else if (origin.scope === 'worktree') {
return 'worktree'
} else if (origin.scope === 'global') {
return isConditionalInclude(origin) ? 'global, via [includeIf]' : 'global'
}
return origin.scope
}

/**
* Format the file path for a config value origin.
* For local/worktree scope, displays the path with a `<repo>` prefix.
*/
export function formatConfigPath(
origin: IConfigValueOrigin,
repositoryPath: string
): string {
const rawPath = origin.origin.replace(/^file:/, '')
if (origin.scope === 'local' || origin.scope === 'worktree') {
// Git returns relative paths for local scope (e.g. `.git/config`)
if (!/^([a-zA-Z]:|[/\\])/.test(rawPath)) {
return '<repo>/' + rawPath
}
// Absolute path — strip repo prefix
const normalized = repositoryPath.replace(/[\\/]+$/, '')
if (rawPath.toLowerCase().startsWith(normalized.toLowerCase())) {
return '<repo>' + rawPath.slice(normalized.length)
}
}
return rawPath
}
60 changes: 52 additions & 8 deletions app/src/lib/git/worktree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import type { WorktreeEntry, WorktreeType } from '../../models/worktree'
import { git } from './core'
import { normalizePath } from '../helpers/path'

function getDotGitPath(repositoryPath: string): string {
return Path.join(repositoryPath, '.git')
}

export interface IWorktreePathInfo {
readonly isLinkedWorktree: boolean
readonly mainWorktreePath: string | null
}

export function parseWorktreePorcelainOutput(
stdout: string
): ReadonlyArray<WorktreeEntry> {
Expand Down Expand Up @@ -104,6 +113,10 @@ export async function removeWorktree(
await git(args, repository.path, 'removeWorktree')
}

export async function pruneWorktrees(repository: Repository): Promise<void> {
await git(['worktree', 'prune'], repository.path, 'pruneWorktrees')
}

export async function moveWorktree(
repository: Repository,
oldPath: string,
Expand Down Expand Up @@ -135,17 +148,48 @@ export async function getMainWorktreePath(
return main?.path ?? null
}

/**
* Synchronously checks if a repository path is a linked worktree by examining
* whether `.git` is a file (linked worktree) or directory (main worktree).
*/
export function isLinkedWorktreeSync(repositoryPath: string): boolean {
export function getWorktreePathInfoSync(
repositoryPath: string
): IWorktreePathInfo | null {
try {
const dotGit = Path.join(repositoryPath, '.git')
const dotGit = getDotGitPath(repositoryPath)
// eslint-disable-next-line no-sync
const stats = Fs.statSync(dotGit)
return stats.isFile()

if (stats.isDirectory()) {
return { isLinkedWorktree: false, mainWorktreePath: repositoryPath }
}

if (!stats.isFile()) {
return null
}

// eslint-disable-next-line no-sync
const contents = Fs.readFileSync(dotGit, 'utf8').trim()
if (!contents.startsWith('gitdir: ')) {
return null
}

const gitDirPath = Path.resolve(
repositoryPath,
contents.substring('gitdir: '.length)
)

// eslint-disable-next-line no-sync
const commondir = Fs.readFileSync(
Path.join(gitDirPath, 'commondir'),
'utf8'
).trim()
if (commondir.length === 0) {
return null
}

const commonGitDir = Path.resolve(gitDirPath, commondir)
return {
isLinkedWorktree: true,
mainWorktreePath: Path.dirname(commonGitDir),
}
} catch {
return false
return null
}
}
5 changes: 5 additions & 0 deletions app/src/lib/stats/stats-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { getRendererGUID } from '../get-renderer-guid'
import { ValidNotificationPullRequestReviewState } from '../valid-notification-pull-request-review'
import { useExternalCredentialHelperKey } from '../trampoline/use-external-credential-helper'
import { getUserAgent } from '../http'
import { getHooksEnvEnabled } from '../hooks/config'

type PullRequestReviewStatFieldInfix =
| 'Approved'
Expand Down Expand Up @@ -428,6 +429,9 @@ interface ICalculatedStats {
* Whether or not the user has the filtering changes enabled
**/
readonly filteringChangesEnabled: boolean

/** Whether or not the user has the git hooks environment enabled */
readonly gitHooksEnvEnabled: boolean
}

type DailyStats = ICalculatedStats &
Expand Down Expand Up @@ -646,6 +650,7 @@ export class StatsStore implements IStatsStore {
diffCheckMarksVisible,
useExternalCredentialHelper,
filteringChangesEnabled,
gitHooksEnvEnabled: getHooksEnvEnabled(),
}
}

Expand Down
Loading