Skip to content

Commit

Permalink
Add new 'last-edit' tour versioning scheme.
Browse files Browse the repository at this point in the history
The Current Commit versioning strategy for tours is quite brittle, because it is
based on commit hashes. The moment history gets rewritten by a rebase, the tour
becomes untethered from the commit that we are trying to attach it to, because
the commit hash that's recorded in the tour file no longer exists.

Also, the Current Commit versioning strategy means that a tour commit must be in a
separate commit, becuase the commit that it's attached to must already exist so
that its hash can be recoreded in the `ref` field of the tour.

Introduce a 'last-edit' versioning scheme. If the value of the ref is
`'last-edit'`, then the tour is associated with the most recent commit that
modifies the tour file.

This will enable a code change and a tour which documents the code change to
exist in the same commit. If during code review, the git history is rewritten by
a rebase, the tour will still properly be associated with the correct commit.
This should provide a far more robust and simpler user experience than the
Current Commit versioning strategy.
  • Loading branch information
trcoffman committed Jul 29, 2022
1 parent 1ef66d7 commit beeb6ee
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 8 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,15 @@ 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.

<img width="600px" src="https://user-images.githubusercontent.com/116461/76692462-3f8ff700-6614-11ea-88a1-6fbded8e8507.png" />
<img width="600px" src="https://user-images.githubusercontent.com/6195185/181656075-e90b479c-fbf0-45fb-a49d-65cb40b43320.png" />

You can choose to associate with the tour with the following ref types:

- `None` - The tour isn't associated with any ref. The benefit of this option is that it enables the code to be edited as part of the tour, since the tour will walk the user through whichever branch/commit they have checked out (e.g. interactive tutorials).
- `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.

Expand Down
17 changes: 17 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Commit[]>;
}

interface GitAPI {
Expand Down
2 changes: 1 addition & 1 deletion src/notebook/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/recorder/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
];

Expand Down
2 changes: 1 addition & 1 deletion src/store/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 21 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ export function getFileUri(file: string, workspaceRoot?: Uri) {
export async function getStepFileUri(
step: CodeTourStep,
workspaceRoot?: Uri,
ref?: string
tour?: CodeTour
): Promise<Uri> {
const ref = tour?.ref;
let uri;
if (step.contents) {
uri = Uri.parse(`${FS_SCHEME}://current/${step.file}`);
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit beeb6ee

Please sign in to comment.