mirror of
https://github.com/desktop/desktop
synced 2024-10-05 23:59:33 +00:00
Merge branch 'development' into releases/2.8.1
This commit is contained in:
commit
d82c1431aa
|
@ -27,6 +27,7 @@ Want to test out new features and get fixes before everyone else? Install the
|
|||
beta channel to get access to early builds of Desktop:
|
||||
|
||||
- [macOS](https://central.github.com/deployments/desktop/desktop/latest/darwin?env=beta)
|
||||
- [macOS (Apple Silicon)](https://central.github.com/deployments/desktop/desktop/latest/darwin-arm64?env=beta)
|
||||
- [Windows](https://central.github.com/deployments/desktop/desktop/latest/win32?env=beta)
|
||||
- [Windows (ARM64)](https://central.github.com/deployments/desktop/desktop/latest/win32-arm64?env=beta)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"app-path": "^3.2.0",
|
||||
"app-path": "^3.3.0",
|
||||
"byline": "^5.0.0",
|
||||
"chalk": "^2.3.0",
|
||||
"classnames": "^2.2.5",
|
||||
|
@ -25,20 +25,20 @@
|
|||
"codemirror-mode-elixir": "^1.1.2",
|
||||
"compare-versions": "^3.6.0",
|
||||
"deep-equal": "^1.0.1",
|
||||
"desktop-trampoline": "desktop/desktop-trampoline#v0.9.4",
|
||||
"desktop-trampoline": "desktop/desktop-trampoline#v0.9.7",
|
||||
"dexie": "^2.0.0",
|
||||
"double-ended-queue": "^2.1.0-0",
|
||||
"dugite": "^1.102.0",
|
||||
"dugite": "^1.103.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"event-kit": "^2.0.0",
|
||||
"file-metadata": "^1.0.0",
|
||||
"file-uri-to-path": "^2.0.0",
|
||||
"file-url": "^2.0.2",
|
||||
"focus-trap-react": "^8.1.0",
|
||||
"fs-admin": "^0.15.0",
|
||||
"fs-admin": "^0.19.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
"fuzzaldrin-plus": "^0.6.0",
|
||||
"keytar": "^7.2.0",
|
||||
"keytar": "^7.7.0",
|
||||
"mem": "^4.3.0",
|
||||
"memoize-one": "^4.0.3",
|
||||
"moment": "^2.24.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"react-dom": "^16.8.4",
|
||||
"react-transition-group": "^4.4.1",
|
||||
"react-virtualized": "^9.20.0",
|
||||
"registry-js": "^1.12.0",
|
||||
"registry-js": "^1.15.0",
|
||||
"source-map-support": "^0.4.15",
|
||||
"strip-ansi": "^4.0.0",
|
||||
"textarea-caret": "^3.0.2",
|
||||
|
|
|
@ -78,7 +78,7 @@ export function enableTextDiffExpansion(): boolean {
|
|||
|
||||
/** Should we allow apps running from Rosetta to auto-update to ARM64 builds? */
|
||||
export function enableUpdateFromRosettaToARM64(): boolean {
|
||||
return false
|
||||
return enableBetaFeatures()
|
||||
}
|
||||
|
||||
/** Should we allow using the save dialog when choosing where to clone a repo */
|
||||
|
|
|
@ -64,7 +64,7 @@ function mapStatus(
|
|||
export async function getCommits(
|
||||
repository: Repository,
|
||||
revisionRange: string,
|
||||
limit: number,
|
||||
limit?: number,
|
||||
additionalArgs: ReadonlyArray<string> = []
|
||||
): Promise<ReadonlyArray<Commit>> {
|
||||
const { formatArgs, parse } = createLogParser({
|
||||
|
@ -82,22 +82,22 @@ export async function getCommits(
|
|||
refs: '%D',
|
||||
})
|
||||
|
||||
const result = await git(
|
||||
[
|
||||
'log',
|
||||
revisionRange,
|
||||
`--date=raw`,
|
||||
`--max-count=${limit}`,
|
||||
...formatArgs,
|
||||
'--no-show-signature',
|
||||
'--no-color',
|
||||
...additionalArgs,
|
||||
'--',
|
||||
],
|
||||
repository.path,
|
||||
'getCommits',
|
||||
{ successExitCodes: new Set([0, 128]) }
|
||||
const args = ['log', revisionRange, `--date=raw`]
|
||||
|
||||
if (limit !== undefined) {
|
||||
args.push(`--max-count=${limit}`)
|
||||
}
|
||||
|
||||
args.push(
|
||||
...formatArgs,
|
||||
'--no-show-signature',
|
||||
'--no-color',
|
||||
...additionalArgs,
|
||||
'--'
|
||||
)
|
||||
const result = await git(args, repository.path, 'getCommits', {
|
||||
successExitCodes: new Set([0, 128]),
|
||||
})
|
||||
|
||||
// if the repository has an unborn HEAD, return an empty history of commits
|
||||
if (result.exitCode === 128) {
|
||||
|
|
|
@ -32,6 +32,7 @@ import { stageFiles } from './update-index'
|
|||
import { getStatus } from './status'
|
||||
import { getCommitsBetweenCommits } from './rev-list'
|
||||
import { Branch } from '../../models/branch'
|
||||
import { getCommits } from './log'
|
||||
|
||||
/** The app-specific results from attempting to rebase a repository */
|
||||
export enum RebaseResult {
|
||||
|
@ -433,7 +434,8 @@ export async function continueRebase(
|
|||
repository: Repository,
|
||||
files: ReadonlyArray<WorkingDirectoryFileChange>,
|
||||
manualResolutions: ReadonlyMap<string, ManualConflictResolution> = new Map(),
|
||||
progressCallback?: (progress: IRebaseProgress) => void
|
||||
progressCallback?: (progress: IRebaseProgress) => void,
|
||||
gitEditor: string = ':'
|
||||
): Promise<RebaseResult> {
|
||||
const trackedFiles = files.filter(f => {
|
||||
return f.status.kind !== AppFileStatusKind.Untracked
|
||||
|
@ -478,7 +480,7 @@ export async function continueRebase(
|
|||
GitError.UnresolvedConflicts,
|
||||
]),
|
||||
env: {
|
||||
GIT_EDITOR: ':',
|
||||
GIT_EDITOR: gitEditor,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -524,3 +526,64 @@ export async function continueRebase(
|
|||
|
||||
return parseRebaseResult(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for initiating interactive rebase in the app.
|
||||
*
|
||||
* In order to modify the interactive todo list during interactive rebase, we
|
||||
* create a temporary todo list of our own. Pass that file's path into our
|
||||
* interactive rebase and using the sequence.editor to cat replace the
|
||||
* interactive todo list with the contents of our generated one.
|
||||
*
|
||||
* @param pathOfGeneratedTodo path to generated todo list for interactive rebase
|
||||
* @param lastRetainedCommitRef the commit before the earliest commit to be changed during the interactive rebase
|
||||
* @param action a description of the action to be displayed in the progress dialog - i.e. Squash, Amend, etc..
|
||||
*/
|
||||
export async function rebaseInteractive(
|
||||
repository: Repository,
|
||||
pathOfGeneratedTodo: string,
|
||||
lastRetainedCommitRef: string,
|
||||
action: string = 'interactive rebase',
|
||||
gitEditor: string = ':',
|
||||
progressCallback?: (progress: IRebaseProgress) => void
|
||||
): Promise<RebaseResult> {
|
||||
const baseOptions: IGitExecutionOptions = {
|
||||
expectedErrors: new Set([GitError.RebaseConflicts]),
|
||||
env: {
|
||||
GIT_EDITOR: gitEditor,
|
||||
},
|
||||
}
|
||||
|
||||
let options = baseOptions
|
||||
|
||||
if (progressCallback !== undefined) {
|
||||
const commits = await getCommits(repository, lastRetainedCommitRef)
|
||||
|
||||
if (commits === null) {
|
||||
log.warn(`Unable to interactively rebase if no commits in revision`)
|
||||
return RebaseResult.Error
|
||||
}
|
||||
|
||||
// TODO: pass in a rebase action for parser - Rebase, Squash, etc..
|
||||
options = configureOptionsForRebase(baseOptions, {
|
||||
commits,
|
||||
progressCallback,
|
||||
})
|
||||
}
|
||||
|
||||
const result = await git(
|
||||
[
|
||||
'-c',
|
||||
// This replaces interactive todo with contents of file at pathOfGeneratedTodo
|
||||
`sequence.editor=cat "${pathOfGeneratedTodo}" >`,
|
||||
'rebase',
|
||||
'-i',
|
||||
lastRetainedCommitRef,
|
||||
],
|
||||
repository.path,
|
||||
action,
|
||||
options
|
||||
)
|
||||
|
||||
return parseRebaseResult(result)
|
||||
}
|
||||
|
|
106
app/src/lib/git/squash.ts
Normal file
106
app/src/lib/git/squash.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
import * as FSE from 'fs-extra'
|
||||
import { getCommits, revRange } from '.'
|
||||
import { CommitOneLine } from '../../models/commit'
|
||||
import { Repository } from '../../models/repository'
|
||||
import { getTempFilePath } from '../file-system'
|
||||
import { rebaseInteractive, RebaseResult } from './rebase'
|
||||
|
||||
/**
|
||||
* Squashes provided commits by calling interactive rebase
|
||||
*
|
||||
* @param toSquash - commits to squash onto another commit
|
||||
* @param squashOnto - commit to squash the `toSquash` commits onto
|
||||
* @param lastRetainedCommitRef - sha of commit before commits in squash
|
||||
* @param commitMessage - the first line of the string provided will be the
|
||||
* summary and rest the body (similar to commit implementation)
|
||||
*/
|
||||
export async function squash(
|
||||
repository: Repository,
|
||||
toSquash: ReadonlyArray<CommitOneLine>,
|
||||
squashOnto: CommitOneLine,
|
||||
lastRetainedCommitRef: string,
|
||||
commitMessage: string
|
||||
): Promise<RebaseResult> {
|
||||
let messagePath, todoPath
|
||||
let result: RebaseResult
|
||||
|
||||
try {
|
||||
if (toSquash.length === 0) {
|
||||
throw new Error('[squash] No commits provided to squash.')
|
||||
}
|
||||
|
||||
const commits = await getCommits(
|
||||
repository,
|
||||
revRange(lastRetainedCommitRef, 'HEAD')
|
||||
)
|
||||
|
||||
if (commits.length === 0) {
|
||||
throw new Error(
|
||||
'[squash] Could not find commits in log for last retained commit ref.'
|
||||
)
|
||||
}
|
||||
|
||||
todoPath = await getTempFilePath('squashTodo')
|
||||
let foundSquashOntoCommitInLog = false
|
||||
// need to traverse in reverse so we do oldest to newest (replay commits)
|
||||
for (let i = commits.length - 1; i >= 0; i--) {
|
||||
// Ignore commits to squash because those are written right next to the target commit
|
||||
if (toSquash.map(sq => sq.sha).includes(commits[i].sha)) {
|
||||
continue
|
||||
}
|
||||
|
||||
await FSE.appendFile(
|
||||
todoPath,
|
||||
`pick ${commits[i].sha} ${commits[i].summary}\n`
|
||||
)
|
||||
|
||||
// If it's the target commit, write a `squash` line for every commit to squash
|
||||
if (commits[i].sha === squashOnto.sha) {
|
||||
foundSquashOntoCommitInLog = true
|
||||
for (let j = 0; j < toSquash.length; j++) {
|
||||
await FSE.appendFile(
|
||||
todoPath,
|
||||
`squash ${toSquash[j].sha} ${toSquash[j].summary}\n`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSquashOntoCommitInLog) {
|
||||
throw new Error(
|
||||
'[squash] The commit to squash onto was not in the log. Continuing would result in dropping the commits in the toSquash array.'
|
||||
)
|
||||
}
|
||||
|
||||
if (commitMessage.trim() !== '') {
|
||||
messagePath = await getTempFilePath('squashCommitMessage')
|
||||
await FSE.writeFile(messagePath, commitMessage)
|
||||
}
|
||||
|
||||
// if no commit message provided, accept default editor
|
||||
const gitEditor =
|
||||
messagePath !== undefined ? `cat "${messagePath}" >` : undefined
|
||||
|
||||
result = await rebaseInteractive(
|
||||
repository,
|
||||
todoPath,
|
||||
lastRetainedCommitRef,
|
||||
'squash',
|
||||
gitEditor
|
||||
// TODO: add progress
|
||||
)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
return RebaseResult.Error
|
||||
} finally {
|
||||
if (todoPath !== undefined) {
|
||||
FSE.remove(todoPath)
|
||||
}
|
||||
|
||||
if (messagePath !== undefined) {
|
||||
FSE.remove(messagePath)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
318
app/test/unit/git/squash-test.ts
Normal file
318
app/test/unit/git/squash-test.ts
Normal file
|
@ -0,0 +1,318 @@
|
|||
import * as FSE from 'fs-extra'
|
||||
import * as Path from 'path'
|
||||
import {
|
||||
continueRebase,
|
||||
getChangedFiles,
|
||||
getCommit,
|
||||
getCommits,
|
||||
getRebaseInternalState,
|
||||
RebaseResult,
|
||||
} from '../../../src/lib/git'
|
||||
import { CommitOneLine } from '../../../src/models/commit'
|
||||
import { Repository } from '../../../src/models/repository'
|
||||
import { setupEmptyRepositoryDefaultMain } from '../../helpers/repositories'
|
||||
import { makeCommit } from '../../helpers/repository-scaffolding'
|
||||
import { squash } from '../../../src/lib/git/squash'
|
||||
import { GitProcess } from 'dugite'
|
||||
import { getStatusOrThrow } from '../../helpers/status'
|
||||
import { getTempFilePath } from '../../../src/lib/file-system'
|
||||
|
||||
describe('git/cherry-pick', () => {
|
||||
let repository: Repository
|
||||
let initialCommit: CommitOneLine
|
||||
|
||||
beforeEach(async () => {
|
||||
repository = await setupEmptyRepositoryDefaultMain()
|
||||
initialCommit = await makeSquashCommit(repository, 'initialize')
|
||||
})
|
||||
|
||||
it('squashes one commit onto the next (non-conflicting)', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit],
|
||||
firstCommit,
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.CompletedWithoutError)
|
||||
|
||||
const log = await getCommits(repository, 'HEAD', 5)
|
||||
const squashed = log[0]
|
||||
expect(squashed.summary).toBe('Test Summary')
|
||||
expect(squashed.body).toBe('Test Body\n')
|
||||
expect(log.length).toBe(2)
|
||||
|
||||
// verify squashed commit contains changes from squashed commits
|
||||
const squashedFiles = await getChangedFiles(repository, squashed.sha)
|
||||
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
|
||||
expect(squashedFilePaths).toContain('first.md')
|
||||
expect(squashedFilePaths).toContain('second.md')
|
||||
})
|
||||
|
||||
it('squashes multiple commit onto one (non-conflicting)', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
const thirdCommit = await makeSquashCommit(repository, 'third')
|
||||
const fourthCommit = await makeSquashCommit(repository, 'fourth')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit, thirdCommit, fourthCommit],
|
||||
firstCommit,
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.CompletedWithoutError)
|
||||
|
||||
const log = await getCommits(repository, 'HEAD', 5)
|
||||
const squashed = log[0]
|
||||
expect(squashed.summary).toBe('Test Summary')
|
||||
expect(squashed.body).toBe('Test Body\n')
|
||||
expect(log.length).toBe(2)
|
||||
|
||||
// verify squashed commit contains changes from squashed commits
|
||||
const squashedFiles = await getChangedFiles(repository, squashed.sha)
|
||||
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
|
||||
expect(squashedFilePaths).toContain('first.md')
|
||||
expect(squashedFilePaths).toContain('second.md')
|
||||
expect(squashedFilePaths).toContain('third.md')
|
||||
expect(squashedFilePaths).toContain('fourth.md')
|
||||
})
|
||||
|
||||
it('squashes multiple commit non-sequential commits (reorders, non-conflicting)', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
await makeSquashCommit(repository, 'third')
|
||||
const fourthCommit = await makeSquashCommit(repository, 'fourth')
|
||||
|
||||
// From oldest to newest, log looks like:
|
||||
// - initial commit
|
||||
// - 'first' commit
|
||||
// - 'second' commit
|
||||
// - 'third' commit
|
||||
// - 'fourth' commit
|
||||
|
||||
// Squashing 'second' and 'fourth' onto 'first'
|
||||
// Thus, reordering 'fourth' to be before 'third'
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit, fourthCommit],
|
||||
firstCommit,
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.CompletedWithoutError)
|
||||
|
||||
// From oldest to newest, log should look like:
|
||||
// - initial commit - log[2]
|
||||
// - the squashed commit 'Test Summary' - log[1]
|
||||
// - 'third' commit - log[0]
|
||||
const log = await getCommits(repository, 'HEAD', 5)
|
||||
const squashed = log[1]
|
||||
expect(squashed.summary).toBe('Test Summary')
|
||||
expect(squashed.body).toBe('Test Body\n')
|
||||
expect(log[0].summary).toBe('third')
|
||||
expect(log.length).toBe(3)
|
||||
|
||||
// verify squashed commit contains changes from squashed commits
|
||||
const squashedFiles = await getChangedFiles(repository, squashed.sha)
|
||||
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
|
||||
expect(squashedFilePaths).toContain('first.md')
|
||||
expect(squashedFilePaths).toContain('second.md')
|
||||
expect(squashedFilePaths).toContain('fourth.md')
|
||||
expect(squashedFilePaths).not.toContain('third.md')
|
||||
})
|
||||
|
||||
it('handles squashing a conflicting commit', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
|
||||
// make a commit with a commit message 'second' and adding file 'second.md'
|
||||
await makeSquashCommit(repository, 'second')
|
||||
|
||||
// make a third commit modifying 'second.md' from secondCommit
|
||||
const thirdCommit = await makeSquashCommit(repository, 'third', 'second')
|
||||
|
||||
// squash third commit onto first commit
|
||||
// Will cause a conflict due to modifications to 'second.md' - a file that
|
||||
// does not exist in the first commit.
|
||||
const result = await squash(
|
||||
repository,
|
||||
[thirdCommit],
|
||||
firstCommit,
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.ConflictsEncountered)
|
||||
|
||||
let status = await getStatusOrThrow(repository)
|
||||
let { files } = status.workingDirectory
|
||||
|
||||
// resolve conflicts by adding the conflicting file
|
||||
await GitProcess.exec(
|
||||
['add', Path.join(repository.path, 'second.md')],
|
||||
repository.path
|
||||
)
|
||||
|
||||
// If there are conflicts, we need to resend in git editor for changing the
|
||||
// git message on continue
|
||||
const messagePath = await getTempFilePath('squashCommitMessage')
|
||||
await FSE.writeFile(messagePath, 'Test Summary\n\nTest Body')
|
||||
|
||||
// continue rebase
|
||||
let continueResult = await continueRebase(
|
||||
repository,
|
||||
files,
|
||||
undefined,
|
||||
undefined,
|
||||
`cat "${messagePath}" >`
|
||||
)
|
||||
|
||||
// This will now conflict with the 'second' commit since it is going to now
|
||||
// apply the second commit which now modifies the same lines in the
|
||||
// 'second.md' that the squashed first commit does.
|
||||
expect(continueResult).toBe(RebaseResult.ConflictsEncountered)
|
||||
|
||||
status = await getStatusOrThrow(repository)
|
||||
files = status.workingDirectory.files
|
||||
|
||||
await FSE.writeFile(
|
||||
Path.join(repository.path, 'second.md'),
|
||||
'# resolve conflict from adding add after resolving squash'
|
||||
)
|
||||
|
||||
continueResult = await continueRebase(
|
||||
repository,
|
||||
files,
|
||||
undefined,
|
||||
undefined,
|
||||
// Only reason I did this here is to show it does not cause harm.
|
||||
// In case of multiple commits being squashed/reordered before the squash
|
||||
// completes, we may not be able to tell which conflict the squash
|
||||
// message will need to go after so we will be sending it on all
|
||||
// continues.
|
||||
`cat "${messagePath}" >`
|
||||
)
|
||||
expect(continueResult).toBe(RebaseResult.CompletedWithoutError)
|
||||
|
||||
const log = await getCommits(repository, 'HEAD', 5)
|
||||
expect(log.length).toBe(3)
|
||||
const squashed = log[1]
|
||||
expect(squashed.summary).toBe('Test Summary')
|
||||
expect(squashed.body).toBe('Test Body\n')
|
||||
|
||||
// verify squashed commit contains changes from squashed commits
|
||||
const squashedFiles = await getChangedFiles(repository, squashed.sha)
|
||||
const squashedFilePaths = squashedFiles.map(f => f.path).join(' ')
|
||||
expect(squashedFilePaths).toContain('first.md')
|
||||
expect(squashedFilePaths).toContain('second.md')
|
||||
})
|
||||
|
||||
it('squashes with default merged commit message/description if commit message not provided', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit],
|
||||
firstCommit,
|
||||
initialCommit.sha,
|
||||
''
|
||||
)
|
||||
expect(result).toBe(RebaseResult.CompletedWithoutError)
|
||||
|
||||
const log = await getCommits(repository, 'HEAD', 5)
|
||||
const squashed = log[0]
|
||||
expect(squashed.summary).toBe('first')
|
||||
expect(squashed.body).toBe('second\n')
|
||||
expect(log.length).toBe(2)
|
||||
})
|
||||
|
||||
it('returns error on invalid lastRetainedCommitRef', async () => {
|
||||
const firstCommit = await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit],
|
||||
firstCommit,
|
||||
'INVALID INVALID',
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.Error)
|
||||
|
||||
// Rebase will not start - As it won't be able retrieve a commits to build a
|
||||
// todo and then interactive rebase would fail for bad revision. Added logic
|
||||
// to short circuit to prevent unnecessary attempt at an interactive rebase.
|
||||
const isRebaseStillOngoing = await getRebaseInternalState(repository)
|
||||
expect(isRebaseStillOngoing !== null).toBeFalse()
|
||||
})
|
||||
|
||||
it('returns error on invalid commit to squashOnto', async () => {
|
||||
await makeSquashCommit(repository, 'first')
|
||||
const secondCommit = await makeSquashCommit(repository, 'second')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[secondCommit],
|
||||
{ sha: 'INVALID', summary: 'INVALID' },
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.Error)
|
||||
|
||||
// Rebase should not start - if we did attempt this, it could result in
|
||||
// dropping commits.
|
||||
const isRebaseStillOngoing = await getRebaseInternalState(repository)
|
||||
expect(isRebaseStillOngoing !== null).toBeFalse()
|
||||
})
|
||||
|
||||
it('returns error on empty toSquash', async () => {
|
||||
const first = await makeSquashCommit(repository, 'first')
|
||||
await makeSquashCommit(repository, 'second')
|
||||
|
||||
const result = await squash(
|
||||
repository,
|
||||
[],
|
||||
first,
|
||||
initialCommit.sha,
|
||||
'Test Summary\n\nTest Body'
|
||||
)
|
||||
|
||||
expect(result).toBe(RebaseResult.Error)
|
||||
|
||||
// Rebase should not start - technically there would be no harm in this
|
||||
// rebase as it would just replay history, but we should not use squash to
|
||||
// replay history.
|
||||
const isRebaseStillOngoing = await getRebaseInternalState(repository)
|
||||
expect(isRebaseStillOngoing !== null).toBeFalse()
|
||||
})
|
||||
})
|
||||
|
||||
async function makeSquashCommit(
|
||||
repository: Repository,
|
||||
desc: string,
|
||||
file?: string
|
||||
): Promise<CommitOneLine> {
|
||||
file = file || desc
|
||||
const commitTree = {
|
||||
commitMessage: desc,
|
||||
entries: [
|
||||
{
|
||||
path: file + '.md',
|
||||
contents: '# ' + desc + ' \n',
|
||||
},
|
||||
],
|
||||
}
|
||||
await makeCommit(repository, commitTree)
|
||||
|
||||
return (await getCommit(repository, 'HEAD'))!
|
||||
}
|
|
@ -55,10 +55,10 @@ ansi-styles@^3.1.0:
|
|||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
app-path@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/app-path/-/app-path-3.2.0.tgz#06d426e0c988885264e0aa0a766dfa2723491633"
|
||||
integrity sha512-PQPaKXi64FZuofJkrtaO3I5RZESm9Yjv7tkeJaDz4EZMeBBfGtr5MyQ3m5AC7F0HVrISBLatPxAAAgvbe418fQ==
|
||||
app-path@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/app-path/-/app-path-3.3.0.tgz#0342a909db37079c593979c720f99e872475eba3"
|
||||
integrity sha512-EAgEXkdcxH1cgEePOSsmUtw9ItPl0KTxnh/pj9ZbhvbKbij9x0oX6PWpGnorDr0DS5AosLgoa5n3T/hZmKQpYA==
|
||||
dependencies:
|
||||
execa "^1.0.0"
|
||||
|
||||
|
@ -311,9 +311,12 @@ delegates@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||
|
||||
desktop-trampoline@desktop/desktop-trampoline#v0.9.4:
|
||||
version "0.9.4"
|
||||
resolved "https://codeload.github.com/desktop/desktop-trampoline/tar.gz/7eabea0f1a3f8432307719a1bd16d1ffde2bf6f6"
|
||||
desktop-trampoline@desktop/desktop-trampoline#v0.9.7:
|
||||
version "0.9.7"
|
||||
resolved "https://codeload.github.com/desktop/desktop-trampoline/tar.gz/38c590851f84e1dc856b6fd7e49920ef360c2ca0"
|
||||
dependencies:
|
||||
node-addon-api "^3.1.0"
|
||||
prebuild-install "^6.0.0"
|
||||
|
||||
detect-libc@^1.0.3:
|
||||
version "1.0.3"
|
||||
|
@ -364,10 +367,10 @@ double-ended-queue@^2.1.0-0:
|
|||
resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c"
|
||||
integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=
|
||||
|
||||
dugite@^1.102.0:
|
||||
version "1.102.0"
|
||||
resolved "https://registry.yarnpkg.com/dugite/-/dugite-1.102.0.tgz#15fde8ec6726f03dabcee06e7e5ee5492bf9bf74"
|
||||
integrity sha512-z3eqQd0sCAt/6HgEDFDKw/VFp9LJG5v7MpR0wQAZh4Y/vz/pt5rdjJozVGVX9kGP3sHbIVz251Ooi8lrLupUeQ==
|
||||
dugite@^1.103.0:
|
||||
version "1.103.0"
|
||||
resolved "https://registry.yarnpkg.com/dugite/-/dugite-1.103.0.tgz#2229c83790782116f96b87763d9ea1a0f2a55842"
|
||||
integrity sha512-8rKO/jQX2HKfSd5wNG/l3HnUfQPKqyC3+D+3CR5Go4+BJOyCPScQwiAVW+eeKLqHFOvjq/w67+ymMyPGxUqhIA==
|
||||
dependencies:
|
||||
checksum "^0.1.1"
|
||||
got "^9.6.0"
|
||||
|
@ -536,13 +539,13 @@ focus-trap@^6.1.0:
|
|||
dependencies:
|
||||
tabbable "^5.1.0"
|
||||
|
||||
fs-admin@^0.15.0:
|
||||
version "0.15.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-admin/-/fs-admin-0.15.0.tgz#ffa7daab2f1184ca81df73e7db8365ee1747410c"
|
||||
integrity sha512-2czA7rZNnu/RSVwxZUSX4fF48PRj8gJ7fKMKpY+G3ESiEzHMPCHvNQaC02PhU+PAyzBUiqfiMHJisnj4LONwLA==
|
||||
fs-admin@^0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-admin/-/fs-admin-0.19.0.tgz#c2b077b21607ca1982bf9bc8c3fc096be7a1186e"
|
||||
integrity sha512-GtJUlSqX95Daw1zlH9PtqMIpr+yQqUnCRKxupuwdlPGy4ds+ICNT3apyQlnT1yXiXvAdnTK06ag/4jMS/jzhXQ==
|
||||
dependencies:
|
||||
nan "^2.13.2"
|
||||
prebuild-install "5.3.5"
|
||||
node-addon-api "^3.1.0"
|
||||
prebuild-install "^6.0.0"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
|
@ -814,10 +817,10 @@ keyboardevents-areequal@^0.2.1:
|
|||
resolved "https://registry.yarnpkg.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194"
|
||||
integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw==
|
||||
|
||||
keytar@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.2.0.tgz#4db2bec4f9700743ffd9eda22eebb658965c8440"
|
||||
integrity sha512-ECSaWvoLKI5SI0pGpZQeUV1/lpBYfkaxvoSp3zkiPOz05VavwSfLi8DdEaa9N2ekQZv3Chy+o7aP6n9mairBgw==
|
||||
keytar@^7.7.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.7.0.tgz#3002b106c01631aa79b1aa9ee0493b94179bbbd2"
|
||||
integrity sha512-YEY9HWqThQc5q5xbXbRwsZTh2PJ36OSYRjSv3NN2xf5s5dpLTjEZnC2YikR29OaVybf9nQ0dJ/80i40RS97t/A==
|
||||
dependencies:
|
||||
node-addon-api "^3.0.0"
|
||||
prebuild-install "^6.0.0"
|
||||
|
@ -985,11 +988,6 @@ ms@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
nan@^2.13.2, nan@^2.14.1:
|
||||
version "2.14.1"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
|
||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||
|
||||
napi-build-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.1.tgz#1381a0f92c39d66bf19852e7873432fc2123e508"
|
||||
|
@ -1012,6 +1010,11 @@ node-addon-api@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681"
|
||||
integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==
|
||||
|
||||
node-addon-api@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239"
|
||||
integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw==
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
|
@ -1144,7 +1147,7 @@ plist@^2.1.0:
|
|||
xmlbuilder "8.2.2"
|
||||
xmldom "0.1.x"
|
||||
|
||||
prebuild-install@5.3.5, prebuild-install@^5.3.5:
|
||||
prebuild-install@^5.3.5:
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.5.tgz#e7e71e425298785ea9d22d4f958dbaccf8bb0e1b"
|
||||
integrity sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==
|
||||
|
@ -1371,12 +1374,12 @@ regenerator-runtime@^0.13.4:
|
|||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
|
||||
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
|
||||
|
||||
registry-js@^1.12.0:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/registry-js/-/registry-js-1.12.0.tgz#35ecfba4d3c3777ff1605e239abaa823fa32979f"
|
||||
integrity sha512-5xk/L83Ph3u7JY+6tb8XrnB78iDoyCwilY4/5C1VhZYiw0jeTUkdTn77kXycWpPK8jQ22LL5DQiAlNcluh+eZw==
|
||||
registry-js@^1.15.0:
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/registry-js/-/registry-js-1.15.0.tgz#eb028503df2358291e2d107fb6dc316f688e47b1"
|
||||
integrity sha512-gNLm7hV3g2M5iKAdcQwoa2Zj8fhkArbzbXyQMVrK/6OfXOCaKnRNqgBnOGOJoJw4uwW9XImJuXijLf1ZKtSR3Q==
|
||||
dependencies:
|
||||
nan "^2.14.1"
|
||||
node-addon-api "^3.1.0"
|
||||
prebuild-install "^5.3.5"
|
||||
|
||||
responselike@^1.0.2:
|
||||
|
|
|
@ -3,6 +3,13 @@
|
|||
"2.8.1": [
|
||||
"[Fixed] Disable partial change selection in split view while whitespace changes are hidden - #12129"
|
||||
],
|
||||
"2.8.1-beta2": [
|
||||
"[Improved] Add complete support for macOS on Apple Silicon devices - #12091",
|
||||
"[Improved] From now on, macOS x64 builds running on Apple Silicon devices will auto update to arm64 builds"
|
||||
],
|
||||
"2.8.1-beta1": [
|
||||
"[New] Preliminary support for macOS on Apple Silicon devices - #9691. Thanks @dennisameling!"
|
||||
],
|
||||
"2.8.0": [
|
||||
"[New] Expand diffs to view more context around your changes - #7014",
|
||||
"[New] Create aliases for repositories you want to be displayed differently in the repository list - #7856",
|
||||
|
|
Loading…
Reference in a new issue