diff --git a/.gitignore b/.gitignore index cd1f03c..5f16633 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ __test__/_temp _temp/ lib/ node_modules/ -.vscode/ \ No newline at end of file +.vscode/ +.idea/ diff --git a/__test__/git-command-manager.test.ts b/__test__/git-command-manager.test.ts index cea73d4..dbe0c4e 100644 --- a/__test__/git-command-manager.test.ts +++ b/__test__/git-command-manager.test.ts @@ -134,6 +134,7 @@ describe('Test fetchDepth and fetchTags options', () => { '-c', 'protocol.version=2', 'fetch', + '--tags', '--prune', '--no-recurse-submodules', '--filter=filterValue', @@ -248,6 +249,7 @@ describe('Test fetchDepth and fetchTags options', () => { '-c', 'protocol.version=2', 'fetch', + '--tags', '--prune', '--no-recurse-submodules', '--filter=filterValue', @@ -364,6 +366,7 @@ describe('Test fetchDepth and fetchTags options', () => { '-c', 'protocol.version=2', 'fetch', + '--tags', '--prune', '--no-recurse-submodules', '--progress', @@ -376,3 +379,225 @@ describe('Test fetchDepth and fetchTags options', () => { ) }) }) + +describe('Test git 2.48 tag fetching behavior', () => { + beforeEach(async () => { + jest.spyOn(fshelper, 'fileExistsSync').mockImplementation(jest.fn()) + jest.spyOn(fshelper, 'directoryExistsSync').mockImplementation(jest.fn()) + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + it('should perform separate tag fetch for git 2.48 when fetchTags is true', async () => { + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.48.1')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + const refSpec = ['refspec1'] + const options = { + fetchTags: true + } + + await git.fetch(refSpec, options) + + // First call: main fetch with --no-tags + expect(mockExec).toHaveBeenNthCalledWith( + 2, // First call is version check + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--no-tags', + '--prune', + '--no-recurse-submodules', + 'origin', + 'refspec1' + ], + expect.any(Object) + ) + + // Second call: separate tag fetch + expect(mockExec).toHaveBeenNthCalledWith( + 3, + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--tags', + '--prune', + 'origin' + ], + expect.any(Object) + ) + + expect(mockExec).toHaveBeenCalledTimes(3) // version + main fetch + tag fetch + }) + + it('should perform separate tag fetch with progress for git 2.48', async () => { + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.48.0')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + const refSpec = ['refspec1'] + const options = { + fetchTags: true, + showProgress: true + } + + await git.fetch(refSpec, options) + + // Main fetch with --no-tags and --progress + expect(mockExec).toHaveBeenNthCalledWith( + 2, + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--no-tags', + '--prune', + '--no-recurse-submodules', + '--progress', + 'origin', + 'refspec1' + ], + expect.any(Object) + ) + + // Separate tag fetch with --progress + expect(mockExec).toHaveBeenNthCalledWith( + 3, + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--tags', + '--prune', + '--progress', + 'origin' + ], + expect.any(Object) + ) + }) + + it('should NOT perform separate tag fetch for git 2.48 when fetchTags is false', async () => { + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.48.1')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + const refSpec = ['refspec1'] + const options = { + fetchTags: false + } + + await git.fetch(refSpec, options) + + // Only one fetch call with --no-tags + expect(mockExec).toHaveBeenNthCalledWith( + 2, + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--no-tags', + '--prune', + '--no-recurse-submodules', + 'origin', + 'refspec1' + ], + expect.any(Object) + ) + + expect(mockExec).toHaveBeenCalledTimes(2) // version + single fetch only + }) + + it('should use normal behavior for non-2.48 git versions', async () => { + mockExec.mockImplementation((path, args, options) => { + if (args.includes('version')) { + options.listeners.stdout(Buffer.from('2.47.0')) + } + return 0 + }) + jest.spyOn(exec, 'exec').mockImplementation(mockExec) + + const workingDirectory = 'test' + const lfs = false + const doSparseCheckout = false + git = await commandManager.createCommandManager( + workingDirectory, + lfs, + doSparseCheckout + ) + + const refSpec = ['refspec1'] + const options = { + fetchTags: true + } + + await git.fetch(refSpec, options) + + // Single fetch with --tags + expect(mockExec).toHaveBeenNthCalledWith( + 2, + expect.any(String), + [ + '-c', + 'protocol.version=2', + 'fetch', + '--tags', + '--prune', + '--no-recurse-submodules', + 'origin', + 'refspec1' + ], + expect.any(Object) + ) + + expect(mockExec).toHaveBeenCalledTimes(2) // version + single fetch only + }) +}) diff --git a/dist/index.js b/dist/index.js index b0db713..009a26b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -653,8 +653,8 @@ class GitCommandManager { fetch(refSpec, options) { return __awaiter(this, void 0, void 0, function* () { const args = ['-c', 'protocol.version=2', 'fetch']; - if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) { - args.push('--no-tags'); + if (!refSpec.some(x => x === refHelper.tagsRefSpec)) { + args.push(options.fetchTags ? '--tags' : '--no-tags'); } args.push('--prune', '--no-recurse-submodules'); if (options.showProgress) { diff --git a/src/git-command-manager.ts b/src/git-command-manager.ts index 8e42a38..2d1303b 100644 --- a/src/git-command-manager.ts +++ b/src/git-command-manager.ts @@ -261,8 +261,12 @@ class GitCommandManager { } ): Promise { const args = ['-c', 'protocol.version=2', 'fetch'] - if (!refSpec.some(x => x === refHelper.tagsRefSpec) && !options.fetchTags) { - args.push('--no-tags') + const hasTagsRefSpec = refSpec.some(x => x === refHelper.tagsRefSpec) + const needsSeparateTagFetch = this.gitVersion.toString().startsWith('2.48') && options.fetchTags && !hasTagsRefSpec + + if (!hasTagsRefSpec) { + // For git 2.48, skip --tags here if we need separate fetch + args.push(needsSeparateTagFetch || !options.fetchTags ? '--no-tags' : '--tags') } args.push('--prune', '--no-recurse-submodules') @@ -293,6 +297,19 @@ class GitCommandManager { await retryHelper.execute(async () => { await that.execGit(args) }) + + // Separate tag fetch for git 2.48 + if (needsSeparateTagFetch) { + const tagArgs = ['-c', 'protocol.version=2', 'fetch', '--tags', '--prune'] + if (options.showProgress) { + tagArgs.push('--progress') + } + tagArgs.push('origin') + + await retryHelper.execute(async () => { + await that.execGit(tagArgs) + }) + } } async getDefaultBranch(repositoryUrl: string): Promise {