diff --git a/README.md b/README.md index 46fafad..4ebf891 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ In order to make it simpler to call common commands, CodeTour will prompt you wi When you record a tour, you'll be asked which git "ref" to associate it with. This allows you to define how resilient you want the tour to be, as changes are made to the respective codebase. - + You can choose to associate with the tour with the following ref types: @@ -183,6 +183,7 @@ You can choose to associate with the tour with the following ref types: - `Current Branch` - The tour is restricted to the current branch. This can have the same resiliency challenges as `None`, but, it allows you to maintain a special branch for your tours that can be versioned seperately. If the end-user has the associated branch checked out, then the tour will enable them to make edits to files as its taken. Otherwise, the tour will replay with read-only files. - `Current Commit` - The tour is restricted to the current commit, and therefore, will never get out of sync. If the end-user's `HEAD` points at the specified commit, then the tour will enable them to make edits to files as its taken. Otherwise, the tour will replay with read-only files. - Tags - The tour is restricted to the selected tag, and therefore, will never get out of sync. The repo's entire list of tags will be displayed, which allows you to easily select one. +- `Last Commit that modified the tour` - The tour is restricted to the most recent commit that modified the tour file. If the end-user's `HEAD` points at the specified commit, then the tour will enable them to make edits to files as its taken. Otherwise, the tour will replay with read-only files. This allows you to bundle a code tour along with the changes that it's explaining into a single git commit. At any time, you can edit the tour's ref by right-clicking it in the `CodeTour` tree and selecting `Change Git Ref`. This let's you "rebase" a tour to a tag/commit as you change/update your code and/or codebase. diff --git a/src/git.ts b/src/git.ts index 2ffc161..5026499 100644 --- a/src/git.ts +++ b/src/git.ts @@ -21,8 +21,25 @@ export interface RepositoryState { readonly refs: Ref[]; } +export interface LogOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; + readonly path?: string; +} + +export interface Commit { + readonly hash: string; + readonly message: string; + readonly parents: string[]; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; + readonly commitDate?: Date; +} + export interface Repository { readonly state: RepositoryState; + log(options?: LogOptions): Promise; } interface GitAPI { diff --git a/src/notebook/index.ts b/src/notebook/index.ts index 439c080..20ff7cf 100644 --- a/src/notebook/index.ts +++ b/src/notebook/index.ts @@ -21,7 +21,7 @@ class CodeTourNotebookProvider implements vscode.NotebookSerializer { let steps: any[] = []; for (let item of tour.steps) { - const uri = await getStepFileUri(item, workspaceRoot, tour.ref); + const uri = await getStepFileUri(item, workspaceRoot, tour); const document = await vscode.workspace.openTextDocument(uri); const startLine = item.line! > 10 ? item.line! - 10 : 0; diff --git a/src/player/index.ts b/src/player/index.ts index fa85659..59d3e79 100644 --- a/src/player/index.ts +++ b/src/player/index.ts @@ -242,7 +242,7 @@ async function renderCurrentStep() { } const workspaceRoot = store.activeTour?.workspaceRoot; - const uri = await getStepFileUri(step, workspaceRoot, currentTour.ref); + const uri = await getStepFileUri(step, workspaceRoot, currentTour); let line = step.line ? step.line - 1 diff --git a/src/recorder/commands.ts b/src/recorder/commands.ts index 54a008c..38320ab 100644 --- a/src/recorder/commands.ts +++ b/src/recorder/commands.ts @@ -872,6 +872,13 @@ export function registerRecorderCommands() { description: "Keep the tour associated with a specific commit", ref: repository.state.HEAD ? repository.state.HEAD.commit! : "", alwaysShow: true + }, + { + label: "$(git-commit) Last commit that modified the tour", + description: + "Keep the tour associated with the most recent commit that modified the tour", + ref: "last-edit", + alwaysShow: true } ]; diff --git a/src/store/actions.ts b/src/store/actions.ts index f70a462..facc13a 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -220,7 +220,7 @@ export async function exportTour(tour: CodeTour) { } const workspaceRoot = getWorkspaceUri(tour); - const stepFileUri = await getStepFileUri(step, workspaceRoot, tour.ref); + const stepFileUri = await getStepFileUri(step, workspaceRoot, tour); const contents = await readUriContents(stepFileUri); delete step.markerTitle; diff --git a/src/utils.ts b/src/utils.ts index 3fef5ed..2eff1b0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -69,8 +69,9 @@ export function getFileUri(file: string, workspaceRoot?: Uri) { export async function getStepFileUri( step: CodeTourStep, workspaceRoot?: Uri, - ref?: string + tour?: CodeTour ): Promise { + const ref = tour?.ref; let uri; if (step.contents) { uri = Uri.parse(`${FS_SCHEME}://current/${step.file}`); @@ -79,10 +80,26 @@ export async function getStepFileUri( ? Uri.parse(step.uri) : getFileUri(step.file!, workspaceRoot); - if (api && ref && ref !== "HEAD") { + if (api && tour && ref && ref !== "HEAD") { const repo = api.getRepository(uri); - if ( + if (ref === "last-edit") { + const tourUri = Uri.parse(tour.id); + const tourPath = tourUri.path; + + if (repo && repo.state.HEAD) { + const logResults = await repo.log({ + maxEntries: 1, + path: tourPath + }); + if (logResults.length > 0) { + const commit = logResults[0]; + if (repo.state.HEAD.commit !== commit.hash) { + uri = await api.toGitUri(uri, commit.hash); + } + } + } + } else if ( repo && repo.state.HEAD && repo.state.HEAD.name !== ref && // The tour refs the user's current branch @@ -184,7 +201,7 @@ async function updateMarkerTitleForStep(tour: CodeTour, stepNumber: number) { const uri = await getStepFileUri( tour.steps[stepNumber], getWorkspaceUri(tour), - tour.ref + tour ); const document = await workspace.openTextDocument(uri);