Skip to content

Feature/reset #389

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 19 additions & 5 deletions src/channel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
import { showOutput } from '../services/testRunner/output'
import { exec } from '../services/node'
import { WORKSPACE_ROOT, TUTORIAL_URL } from '../environment'
import reset from '../services/reset'
import getLastCommitHash from '../services/reset/lastHash'

const readFileAsync = promisify(readFile)

Expand Down Expand Up @@ -320,17 +322,29 @@ class Channel implements Channel {
case 'EDITOR_RUN_TEST':
vscode.commands.executeCommand(COMMANDS.RUN_TEST, action?.payload)
return
case 'EDITOR_RUN_RESET_SCRIPT':
case 'EDITOR_RUN_RESET':
// reset to timeline
const tutorial: TT.Tutorial | null = this.context.tutorial.get()
const position: T.Position = this.context.position.get()

// get last pass commit
const hash = getLastCommitHash(position, tutorial?.levels || [])

const branch = tutorial?.config.repo.branch

if (!branch) {
console.error('No repo branch found for tutorial')
return
}

// load timeline until last pass commit
reset({ branch, hash })

// if tutorial.config.reset.command, run it
if (tutorial?.config?.reset?.command) {
await exec({ command: tutorial.config.reset.command })
}
return
case 'EDITOR_RUN_RESET_TO_LAST_PASS':
return
case 'EDITOR_RUN_RESET_TO_TIMELINE':
return
default:
logger(`No match for action type: ${actionType}`)
return
Expand Down
3 changes: 1 addition & 2 deletions src/services/git/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as TT from 'typings/tutorial'
import { exec, exists } from '../node'
import logger from '../logger'
import { stringify } from 'querystring'

const gitOrigin = 'coderoad'
export const gitOrigin = 'coderoad'

const stashAllFiles = async (): Promise<never | void> => {
// stash files including untracked (eg. newly created file)
Expand Down
12 changes: 0 additions & 12 deletions src/services/git/lastPass.ts

This file was deleted.

5 changes: 5 additions & 0 deletions src/services/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { promisify } from 'util'
import { WORKSPACE_ROOT } from '../../environment'

const asyncExec = promisify(cpExec)
const asyncRemoveFile = promisify(fs.unlink)

interface ExecParams {
command: string
Expand All @@ -19,3 +20,7 @@ export const exec = (params: ExecParams): Promise<{ stdout: string; stderr: stri
export const exists = (...paths: string[]): boolean | never => {
return fs.existsSync(join(WORKSPACE_ROOT, ...paths))
}

export const removeFile = (...paths: string[]) => {
return asyncRemoveFile(join(WORKSPACE_ROOT, ...paths))
}
70 changes: 70 additions & 0 deletions src/services/reset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { exec, removeFile } from '../node'

interface Input {
hash: string
branch: string
}

const ignoreError = () => {}

// note: attempted to do this as a bash script
// but requires the bash script has git permissions
const reset = async ({ branch, hash }: Input): Promise<void> => {
const remote = 'coderoad'

try {
// if no git init, will initialize
// otherwise re-initializes git
await exec({ command: 'git init' }).catch(console.log)

// capture current branch
const hasBranch = await exec({ command: 'git branch --show-current' })
const localBranch = hasBranch.stdout
// check if coderoad remote exists
const hasRemote = await exec({ command: 'git remote -v' }).catch(console.warn)
if (!hasRemote || !hasRemote.stdout || !hasRemote.stdout.length) {
throw new Error('No remote found')
} else if (!hasRemote.stdout.match(new RegExp(remote))) {
throw new Error(`No "${remote}" remote found`)
}

// switch to an empty branch
await exec({
command: 'git checkout --orphan reset-orphan-branch',
})
// stash any current work
await exec({
command: 'git stash',
}).catch(ignoreError)

// remove any other files
await exec({
command: 'git rm -rf .',
}).catch(ignoreError)
await removeFile('.gitignore').catch(ignoreError)

await exec({
command: `git branch -D ${localBranch}`,
})
await exec({
command: `git checkout -b ${localBranch}`,
})

// load git timeline
await exec({
command: `git fetch coderoad ${branch}`,
})
await exec({
command: `git merge coderoad/${branch}`,
})
// reset to target commit hash
await exec({
command: `git reset --hard ${hash}`,
})
} catch (error) {
console.error('Error resetting')
console.error(error.message)
}
}

export default reset
56 changes: 56 additions & 0 deletions src/services/reset/lastHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as TT from '../../../typings/tutorial'
import * as T from '../../../typings'
import getLastCommitHash from './lastHash'

describe('lastHash', () => {
it('should grab the last passing hash from a step', () => {
const position: T.Position = { levelId: '1', stepId: '1.2' }
const levels: TT.Level[] = [
{
id: '1',
title: '',
summary: '',
content: '',
steps: [
{
id: '1.1',
content: '',
setup: { commits: ['abcdef1'] },
},
{
id: '1.2',
content: '',
setup: { commits: ['abcdef2'] },
},
],
},
]
const result = getLastCommitHash(position, levels)
expect(result).toBe('abcdef2')
})
it('should grab the last passing hash from a step with several commits', () => {
const position: T.Position = { levelId: '1', stepId: '1.2' }
const levels: TT.Level[] = [
{
id: '1',
title: '',
summary: '',
content: '',
steps: [
{
id: '1.1',
content: '',
setup: { commits: ['abcdef1'] },
},
{
id: '1.2',
content: '',
setup: { commits: ['abcdef2', 'abcdef3'] },
},
],
},
]
const result = getLastCommitHash(position, levels)
expect(result).toBe('abcdef3')
})
})
23 changes: 23 additions & 0 deletions src/services/reset/lastHash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as TT from '../../../typings/tutorial'
import * as T from '../../../typings'

const getLastCommitHash = (position: T.Position, levels: TT.Level[]) => {
// get previous position
const { levelId, stepId } = position

const level: TT.Level | undefined = levels.find((l) => levelId === l.id)
if (!level) {
throw new Error(`No level found matching ${levelId}`)
}
const step = level.steps.find((s) => stepId === s.id)
if (!step) {
throw new Error(`No step found matching ${stepId}`)
}
const commits = step.setup.commits
if (!commits.length) {
throw new Error(`No commits found on step ${stepId}`)
}
return commits[commits.length - 1]
}

export default getLastCommitHash
2 changes: 1 addition & 1 deletion typings/tutorial.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type Step = {
id: string
content: string
setup: StepActions
solution: Maybe<StepActions>
solution?: Maybe<StepActions>
hints?: string[]
subtasks?: string[]
}
Expand Down
6 changes: 3 additions & 3 deletions web-app/src/containers/Tutorial/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import StepProgress from './components/StepProgress'
import { DISPLAY_RUN_TEST_BUTTON } from '../../environment'
import formatLevels from './formatLevels'
// import SettingsPage from './containers/Settings'
// import Reset from './components/Reset'
import Reset from './components/Reset'

const styles = {
header: {
Expand Down Expand Up @@ -95,7 +95,7 @@ const TutorialPage = (props: PageProps) => {
}

const onReset = (): void => {
// TODO
props.send({ type: 'RUN_RESET' })
}

const [menuVisible, setMenuVisible] = React.useState(false)
Expand Down Expand Up @@ -148,7 +148,7 @@ const TutorialPage = (props: PageProps) => {

{/* Center */}
<div css={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
{/* <Reset onReset={onReset} disabled={processes.length > 0} /> */}
<Reset onReset={onReset} disabled={processes.length > 0} />
</div>

{/* Right */}
Expand Down
4 changes: 2 additions & 2 deletions web-app/src/services/state/actions/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ export default (editorSend: any) => ({
payload: { position: context.position },
})
},
runResetScript() {
runReset() {
editorSend({
type: 'EDITOR_RUN_RESET_SCRIPT',
type: 'EDITOR_RUN_RESET',
})
},
})
4 changes: 2 additions & 2 deletions web-app/src/services/state/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ export const createMachine = (options: any) => {
RUN_TEST: {
actions: ['runTest'],
},
RESET_SCRIPT: {
actions: ['runResetScript'],
RUN_RESET: {
actions: ['runReset'],
},
},
},
Expand Down