diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index cea73d4..23b8863 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -376,3 +376,120 @@ describe('Test fetchDepth and fetchTags options', () => { ) }) }) + +describe('git user-agent with orchestration ID', () => { + beforeEach(async () => { + jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn()) + jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn()) + }) + + afterEach(() => { + jest.restoreAllMocks() + // Clean up environment variable to prevent test pollution + delete process.env['ACTIONS_ORCHESTRATION_ID'] + }) + + it('should include orchestration ID in user-agent when ACTIONS_ORCHESTRATION_ID is set', async () => { + const orchId = 'test-orch-id-12345' + process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + + let capturedEnv: any = null + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + // Capture env on any command + capturedEnv = options.env + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent includes the orchestration ID + expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + `git/2.18 (github-actions-checkout) actions_orchestration_id/${orchId}` + ) + }) + + it('should sanitize invalid characters in orchestration ID', async () => { + const orchId = 'test (with) special/chars' + process.env['ACTIONS_ORCHESTRATION_ID'] = orchId + + let capturedEnv: any = null + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + // Capture env on any command + capturedEnv = options.env + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent has sanitized orchestration ID (spaces, parentheses, slash replaced) + expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + 'git/2.18 (github-actions-checkout) actions_orchestration_id/test__with__special_chars' + ) + }) + + it('should not modify user-agent when ACTIONS_ORCHESTRATION_ID is not set', async () => { + delete process.env['ACTIONS_ORCHESTRATION_ID'] + + let capturedEnv: any = null + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.18')) + } + // Capture env on any command + capturedEnv = options.env + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + // Call a git command to trigger env capture after user-agent is set + await git.init() + + // Verify the user agent does NOT contain orchestration ID + expect(git).toBeDefined() + expect(capturedEnv).toBeDefined() + expect(capturedEnv['GIT_HTTP_USER_AGENT']).toBe( + 'git/2.18 (github-actions-checkout)' + ) + }) +}) diff --git a/dist/index.js b/dist/index.js index b9b34d3..4eab86e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1206,7 +1206,17 @@ class GitCommandManager { } } // Set the user agent - const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`; + let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)`; + // Append orchestration ID if set + const orchId = process.env['ACTIONS_ORCHESTRATION_ID']; + if (orchId) { + // Sanitize the orchestration ID to ensure it contains only valid characters + // Valid characters: 0-9, a-z, _, -, . + const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_'); + if (sanitizedId) { + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}`; + } + } core.debug(`Set git useragent to: ${gitHttpUserAgent}`); this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent; }); diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index a45e15a..eba285a 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -730,7 +730,19 @@ class GitCommandManager { } } // Set the user agent - const gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)` + let gitHttpUserAgent = `git/${this.gitVersion} (github-actions-checkout)` + + // Append orchestration ID if set + const orchId = process.env['ACTIONS_ORCHESTRATION_ID'] + if (orchId) { + // Sanitize the orchestration ID to ensure it contains only valid characters + // Valid characters: 0-9, a-z, _, -, . + const sanitizedId = orchId.replace(/[^a-z0-9_.-]/gi, '_') + if (sanitizedId) { + gitHttpUserAgent = `${gitHttpUserAgent} actions_orchestration_id/${sanitizedId}` + } + } + core.debug(`Set git useragent to: ${gitHttpUserAgent}`) this.gitEnv['GIT_HTTP_USER_AGENT'] = gitHttpUserAgent }