mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 16:02:56 +00:00
* Add project Windows runtime selection * Fix project Windows runtime selection Co-authored-by: Orca <help@stably.ai> * fix: preserve WSL shell variables --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <neil@stably.ai>
544 lines
19 KiB
TypeScript
544 lines
19 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { gitExecFileAsyncMock } = vi.hoisted(() => ({
|
|
gitExecFileAsyncMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('./runner', () => ({
|
|
gitExecFileAsync: gitExecFileAsyncMock
|
|
}))
|
|
|
|
import { getUpstreamStatus } from './upstream'
|
|
|
|
const missingTrackingRefError = new Error(
|
|
"fatal: ambiguous argument 'HEAD@{u}': unknown revision or path not in the working tree.\n" +
|
|
"Use '--' to separate paths from revisions, like this:\n" +
|
|
"'git <command> [<revision>...] -- [<file>...]'"
|
|
)
|
|
|
|
describe('getUpstreamStatus', () => {
|
|
beforeEach(() => {
|
|
gitExecFileAsyncMock.mockReset()
|
|
})
|
|
|
|
it('returns upstream and ahead/behind counts when tracking is configured', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'main\n' })
|
|
.mockResolvedValueOnce({ stdout: 'origin/main\n' })
|
|
.mockResolvedValueOnce({ stdout: '2\t3\n' })
|
|
.mockResolvedValueOnce({ stdout: '+ abc123 remote work\n' })
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/main',
|
|
ahead: 2,
|
|
behind: 3,
|
|
behindCommitsArePatchEquivalent: false
|
|
})
|
|
})
|
|
|
|
it('marks diverged upstream commits as patch-equivalent after a rebase', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockResolvedValueOnce({ stdout: 'origin/feature\n' })
|
|
.mockResolvedValueOnce({ stdout: '14\t3\n' })
|
|
.mockResolvedValueOnce({
|
|
stdout:
|
|
'= ac503deae Stabilize pull request creation flow\n' +
|
|
'= 7dc0fc1a6 Clean up fork PR remotes after worktree deletion\n'
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 14,
|
|
behind: 3,
|
|
behindCommitsArePatchEquivalent: true
|
|
})
|
|
})
|
|
|
|
it('keeps configured local-branch upstreams', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockResolvedValueOnce({ stdout: 'main\n' })
|
|
.mockResolvedValueOnce({ stdout: '1\t0\n' })
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'main',
|
|
ahead: 1,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('returns hasUpstream=false when upstream output is empty', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockResolvedValueOnce({ stdout: '\n' })
|
|
.mockRejectedValueOnce(new Error('missing branch remote'))
|
|
.mockRejectedValueOnce(new Error('missing branch merge'))
|
|
.mockRejectedValueOnce(new Error('missing branch base'))
|
|
.mockRejectedValueOnce(new Error('missing remote branch'))
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('returns hasUpstream=false when upstream is missing', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockRejectedValueOnce(new Error('fatal: no upstream configured'))
|
|
.mockRejectedValueOnce(new Error('missing branch remote'))
|
|
.mockRejectedValueOnce(new Error('missing branch merge'))
|
|
.mockRejectedValueOnce(new Error('missing branch base'))
|
|
.mockRejectedValueOnce(new Error('missing remote branch'))
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('returns hasUpstream=false when the configured tracking ref is missing', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockRejectedValueOnce(missingTrackingRefError)
|
|
.mockRejectedValueOnce(new Error('missing branch remote'))
|
|
.mockRejectedValueOnce(new Error('missing branch merge'))
|
|
.mockRejectedValueOnce(new Error('missing branch base'))
|
|
.mockRejectedValueOnce(new Error('missing remote branch'))
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('uses the same-name origin branch when a legacy worktree tracks origin/main', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: 'feature\n' })
|
|
.mockResolvedValueOnce({ stdout: 'origin/main\n' })
|
|
.mockResolvedValueOnce({ stdout: 'abc123\n' })
|
|
.mockResolvedValueOnce({ stdout: '3\t1\n' })
|
|
.mockResolvedValueOnce({ stdout: '+ def456 remote work\n' })
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/feature',
|
|
ahead: 3,
|
|
behind: 1,
|
|
behindCommitsArePatchEquivalent: false
|
|
})
|
|
})
|
|
|
|
it('uses a named remote that matches a URL-valued branch remote', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'imp/chinese-translation\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.remote')) {
|
|
return Promise.resolve({ stdout: 'https://github.com/pynickle/orca.git\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/imp/chinese-translation\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.base')) {
|
|
return Promise.reject(new Error('missing branch base'))
|
|
}
|
|
if (args[0] === 'remote' && args[1] === 'get-url' && args[2] === 'origin') {
|
|
return Promise.resolve({ stdout: 'https://github.com/stablyai/orca.git\n' })
|
|
}
|
|
if (args[0] === 'remote' && args[1] === 'get-url' && args[2] === 'pr-pynickle-orca') {
|
|
return Promise.resolve({ stdout: 'https://github.com/pynickle/orca.git\n' })
|
|
}
|
|
if (args[0] === 'remote') {
|
|
return Promise.resolve({ stdout: 'origin\npr-pynickle-orca\n' })
|
|
}
|
|
if (
|
|
args[0] === 'rev-parse' &&
|
|
args.includes('refs/remotes/pr-pynickle-orca/imp/chinese-translation')
|
|
) {
|
|
return Promise.resolve({ stdout: 'fork-head\n' })
|
|
}
|
|
if (args[0] === 'rev-list') {
|
|
return Promise.resolve({ stdout: '2\t0\n' })
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'pr-pynickle-orca/imp/chinese-translation',
|
|
ahead: 2,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('uses a fork head branch even when its name matches the base branch', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'review/pr-1\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.remote')) {
|
|
return Promise.resolve({ stdout: 'fork\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/main\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.base')) {
|
|
return Promise.resolve({ stdout: 'refs/remotes/origin/main\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/fork/main')) {
|
|
return Promise.resolve({ stdout: 'fork-head\n' })
|
|
}
|
|
if (args[0] === 'rev-list') {
|
|
return Promise.resolve({ stdout: '3\t0\n' })
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'fork/main',
|
|
ahead: 3,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('marks a URL-valued branch push target when no matching remote is configured', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'imp/chinese-translation\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.pushRemote')) {
|
|
return Promise.resolve({ stdout: 'https://github.com/pynickle/orca.git\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('remote.pushDefault')) {
|
|
return Promise.reject(new Error('missing pushDefault'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.remote')) {
|
|
return Promise.resolve({ stdout: 'https://github.com/pynickle/orca.git\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/imp/chinese-translation\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.imp/chinese-translation.base')) {
|
|
return Promise.reject(new Error('missing branch base'))
|
|
}
|
|
if (args[0] === 'remote' && args[1] === 'get-url') {
|
|
return Promise.resolve({ stdout: 'https://github.com/stablyai/orca.git\n' })
|
|
}
|
|
if (args[0] === 'remote') {
|
|
return Promise.resolve({ stdout: 'origin\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/imp/chinese-translation')) {
|
|
return Promise.reject(new Error('missing origin tracking ref'))
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0,
|
|
hasConfiguredPushTarget: true
|
|
})
|
|
})
|
|
|
|
it('marks a fork head push target when the same-named base branch is on another remote', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'review/pr-1\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.pushRemote')) {
|
|
return Promise.resolve({ stdout: 'fork\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('remote.pushDefault')) {
|
|
return Promise.reject(new Error('missing pushDefault'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.remote')) {
|
|
return Promise.resolve({ stdout: 'fork\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/main\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.review/pr-1.base')) {
|
|
return Promise.resolve({ stdout: 'refs/remotes/origin/main\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/fork/main')) {
|
|
return Promise.reject(new Error('missing fork tracking ref'))
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/review/pr-1')) {
|
|
return Promise.reject(new Error('missing origin review branch'))
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0,
|
|
hasConfiguredPushTarget: true
|
|
})
|
|
})
|
|
|
|
it('does not mark origin base-branch config as a push target', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'feature\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature.pushRemote')) {
|
|
return Promise.reject(new Error('missing pushRemote'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('remote.pushDefault')) {
|
|
return Promise.reject(new Error('missing pushDefault'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature.remote')) {
|
|
return Promise.resolve({ stdout: 'origin\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/main\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature.base')) {
|
|
return Promise.resolve({ stdout: 'refs/remotes/origin/main\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/main')) {
|
|
return Promise.reject(new Error('missing origin/main tracking ref'))
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/feature')) {
|
|
return Promise.reject(new Error('missing origin/feature tracking ref'))
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('does not mark remote.pushDefault plus origin base branch as a push target', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'feature/fix\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.reject(new Error('fatal: no upstream configured'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature/fix.pushRemote')) {
|
|
return Promise.reject(new Error('missing pushRemote'))
|
|
}
|
|
if (args[0] === 'config' && args.includes('remote.pushDefault')) {
|
|
return Promise.resolve({ stdout: 'fork\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature/fix.remote')) {
|
|
return Promise.resolve({ stdout: 'origin\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature/fix.merge')) {
|
|
return Promise.resolve({ stdout: 'refs/heads/main\n' })
|
|
}
|
|
if (args[0] === 'config' && args.includes('branch.feature/fix.base')) {
|
|
return Promise.resolve({ stdout: 'refs/remotes/origin/main\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/feature/fix')) {
|
|
return Promise.reject(new Error('missing origin feature branch'))
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: false,
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('keeps a configured upstream whose remote name contains a slash', async () => {
|
|
gitExecFileAsyncMock.mockImplementation((args: string[]) => {
|
|
if (args[0] === 'symbolic-ref') {
|
|
return Promise.resolve({ stdout: 'feature\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('HEAD@{u}')) {
|
|
return Promise.resolve({ stdout: 'origin/team/feature\n' })
|
|
}
|
|
if (args[0] === 'remote') {
|
|
return Promise.resolve({ stdout: 'origin\norigin/team\n' })
|
|
}
|
|
if (args[0] === 'rev-parse' && args.includes('refs/remotes/origin/feature')) {
|
|
return Promise.resolve({ stdout: 'origin-feature-oid\n' })
|
|
}
|
|
if (args[0] === 'rev-list' && args.includes('HEAD...origin/team/feature')) {
|
|
return Promise.resolve({ stdout: '2\t0\n' })
|
|
}
|
|
if (args[0] === 'rev-list' && args.includes('HEAD...origin/feature')) {
|
|
return Promise.resolve({ stdout: '9\t9\n' })
|
|
}
|
|
throw new Error(`unexpected git args: ${args.join(' ')}`)
|
|
})
|
|
|
|
const result = await getUpstreamStatus('/repo')
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'origin/team/feature',
|
|
ahead: 2,
|
|
behind: 0
|
|
})
|
|
})
|
|
|
|
it('uses an explicit publish target instead of the configured upstream', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'abc123\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: '1\t2\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: '+ def456 remote work\n', stderr: '' })
|
|
|
|
const result = await getUpstreamStatus('/repo', {
|
|
remoteName: 'fork',
|
|
branchName: 'feature/fix'
|
|
})
|
|
|
|
expect(result).toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'fork/feature/fix',
|
|
ahead: 1,
|
|
behind: 2,
|
|
behindCommitsArePatchEquivalent: false
|
|
})
|
|
expect(gitExecFileAsyncMock.mock.calls).toEqual([
|
|
[['check-ref-format', '--branch', 'feature/fix'], { cwd: '/repo' }],
|
|
[['rev-parse', '--verify', '--quiet', 'refs/remotes/fork/feature/fix'], { cwd: '/repo' }],
|
|
[
|
|
['rev-list', '--left-right', '--count', 'HEAD...refs/remotes/fork/feature/fix'],
|
|
{ cwd: '/repo' }
|
|
],
|
|
[
|
|
[
|
|
'log',
|
|
'--oneline',
|
|
'--cherry-mark',
|
|
'--right-only',
|
|
'HEAD...refs/remotes/fork/feature/fix',
|
|
'--'
|
|
],
|
|
{ cwd: '/repo' }
|
|
]
|
|
])
|
|
})
|
|
|
|
it('routes explicit publish-target probes through the selected WSL distro', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: 'abc123\n', stderr: '' })
|
|
.mockResolvedValueOnce({ stdout: '0\t0\n', stderr: '' })
|
|
|
|
await expect(
|
|
getUpstreamStatus(
|
|
'/repo',
|
|
{
|
|
remoteName: 'fork',
|
|
branchName: 'feature/fix'
|
|
},
|
|
{ wslDistro: 'Ubuntu' }
|
|
)
|
|
).resolves.toEqual({
|
|
hasUpstream: true,
|
|
upstreamName: 'fork/feature/fix',
|
|
ahead: 0,
|
|
behind: 0
|
|
})
|
|
expect(gitExecFileAsyncMock.mock.calls).toEqual([
|
|
[['check-ref-format', '--branch', 'feature/fix'], { cwd: '/repo', wslDistro: 'Ubuntu' }],
|
|
[
|
|
['rev-parse', '--verify', '--quiet', 'refs/remotes/fork/feature/fix'],
|
|
{ cwd: '/repo', wslDistro: 'Ubuntu' }
|
|
],
|
|
[
|
|
['rev-list', '--left-right', '--count', 'HEAD...refs/remotes/fork/feature/fix'],
|
|
{ cwd: '/repo', wslDistro: 'Ubuntu' }
|
|
]
|
|
])
|
|
})
|
|
|
|
it('reports no upstream when an explicit publish target has not been fetched yet', async () => {
|
|
gitExecFileAsyncMock
|
|
.mockResolvedValueOnce({ stdout: '', stderr: '' })
|
|
.mockRejectedValueOnce(Object.assign(new Error('git exited with 1.'), { stderr: '' }))
|
|
|
|
await expect(
|
|
getUpstreamStatus('/repo', {
|
|
remoteName: 'fork',
|
|
branchName: 'feature/fix'
|
|
})
|
|
).resolves.toEqual({
|
|
hasUpstream: false,
|
|
upstreamName: 'fork/feature/fix',
|
|
ahead: 0,
|
|
behind: 0,
|
|
hasConfiguredPushTarget: true
|
|
})
|
|
})
|
|
|
|
it('does not hide git failures while checking an explicit publish target', async () => {
|
|
gitExecFileAsyncMock.mockResolvedValueOnce({ stdout: '', stderr: '' }).mockRejectedValueOnce(
|
|
Object.assign(new Error('fatal: not a git repository'), {
|
|
stderr: 'fatal: not a git repository'
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
getUpstreamStatus('/repo', {
|
|
remoteName: 'fork',
|
|
branchName: 'feature/fix'
|
|
})
|
|
).rejects.toThrow('fatal: not a git repository')
|
|
})
|
|
})
|