Skip to content

Commit e094afc

Browse files
committed
feat: Add new 'last-edit' tour versioning scheme.
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.
1 parent 1ef66d7 commit e094afc

File tree

7 files changed

+50
-8
lines changed

7 files changed

+50
-8
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,15 @@ In order to make it simpler to call common commands, CodeTour will prompt you wi
175175

176176
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.
177177

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

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

182182
- `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).
183183
- `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.
184184
- `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.
185185
- 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.
186+
- `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.
186187

187188
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.
188189

src/git.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,25 @@ export interface RepositoryState {
2121
readonly refs: Ref[];
2222
}
2323

24+
export interface LogOptions {
25+
/** Max number of log entries to retrieve. If not specified, the default is 32. */
26+
readonly maxEntries?: number;
27+
readonly path?: string;
28+
}
29+
30+
export interface Commit {
31+
readonly hash: string;
32+
readonly message: string;
33+
readonly parents: string[];
34+
readonly authorDate?: Date;
35+
readonly authorName?: string;
36+
readonly authorEmail?: string;
37+
readonly commitDate?: Date;
38+
}
39+
2440
export interface Repository {
2541
readonly state: RepositoryState;
42+
log(options?: LogOptions): Promise<Commit[]>;
2643
}
2744

2845
interface GitAPI {

src/notebook/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class CodeTourNotebookProvider implements vscode.NotebookSerializer {
2121
let steps: any[] = [];
2222

2323
for (let item of tour.steps) {
24-
const uri = await getStepFileUri(item, workspaceRoot, tour.ref);
24+
const uri = await getStepFileUri(item, workspaceRoot, tour);
2525
const document = await vscode.workspace.openTextDocument(uri);
2626

2727
const startLine = item.line! > 10 ? item.line! - 10 : 0;

src/player/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ async function renderCurrentStep() {
242242
}
243243

244244
const workspaceRoot = store.activeTour?.workspaceRoot;
245-
const uri = await getStepFileUri(step, workspaceRoot, currentTour.ref);
245+
const uri = await getStepFileUri(step, workspaceRoot, currentTour);
246246

247247
let line = step.line
248248
? step.line - 1

src/recorder/commands.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,13 @@ export function registerRecorderCommands() {
872872
description: "Keep the tour associated with a specific commit",
873873
ref: repository.state.HEAD ? repository.state.HEAD.commit! : "",
874874
alwaysShow: true
875+
},
876+
{
877+
label: "$(git-commit) Last commit that modified the tour",
878+
description:
879+
"Keep the tour associated with the most recent commit that modified the tour",
880+
ref: "last-edit",
881+
alwaysShow: true
875882
}
876883
];
877884

src/store/actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export async function exportTour(tour: CodeTour) {
220220
}
221221

222222
const workspaceRoot = getWorkspaceUri(tour);
223-
const stepFileUri = await getStepFileUri(step, workspaceRoot, tour.ref);
223+
const stepFileUri = await getStepFileUri(step, workspaceRoot, tour);
224224
const contents = await readUriContents(stepFileUri);
225225

226226
delete step.markerTitle;

src/utils.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ export function getFileUri(file: string, workspaceRoot?: Uri) {
6969
export async function getStepFileUri(
7070
step: CodeTourStep,
7171
workspaceRoot?: Uri,
72-
ref?: string
72+
tour?: CodeTour
7373
): Promise<Uri> {
74+
const ref = tour?.ref;
7475
let uri;
7576
if (step.contents) {
7677
uri = Uri.parse(`${FS_SCHEME}://current/${step.file}`);
@@ -79,10 +80,26 @@ export async function getStepFileUri(
7980
? Uri.parse(step.uri)
8081
: getFileUri(step.file!, workspaceRoot);
8182

82-
if (api && ref && ref !== "HEAD") {
83+
if (api && tour && ref && ref !== "HEAD") {
8384
const repo = api.getRepository(uri);
8485

85-
if (
86+
if (ref === "last-edit") {
87+
const tourUri = Uri.parse(tour.id);
88+
const tourPath = tourUri.path;
89+
90+
if (repo && repo.state.HEAD) {
91+
const logResults = await repo.log({
92+
maxEntries: 1,
93+
path: tourPath
94+
});
95+
if (logResults.length > 0) {
96+
const commit = logResults[0];
97+
if (repo.state.HEAD.commit !== commit.hash) {
98+
uri = await api.toGitUri(uri, commit.hash);
99+
}
100+
}
101+
}
102+
} else if (
86103
repo &&
87104
repo.state.HEAD &&
88105
repo.state.HEAD.name !== ref && // The tour refs the user's current branch
@@ -184,7 +201,7 @@ async function updateMarkerTitleForStep(tour: CodeTour, stepNumber: number) {
184201
const uri = await getStepFileUri(
185202
tour.steps[stepNumber],
186203
getWorkspaceUri(tour),
187-
tour.ref
204+
tour
188205
);
189206

190207
const document = await workspace.openTextDocument(uri);

0 commit comments

Comments
 (0)