Edit
Overview
The "edit" command allow you to change a commit content during the interactive rebase.
Setup playground
From the course repository root, run the following commands:
./scripts/create-playground.sh rebase-int_editCheck the commit log history in the new playground repository.
cd playgrounds/rebase-int_edit ; git log --oneline5fba6a2 (HEAD -> main) fix: dequeue must take first item, not last one
cef98c4 feat: implement `size()`
d285007 feat: implement `dequeue()`
054015f feat: implement `enqueue()`
a9b2493 feat: class skeleton
560a42d test: add `size()`
e266b08 test: add `dequeue()`
fbe0e8e test: add `enqueue()`
38dc7ec feat: create file with draft specificationsWARNING
As playground rebuild a new repository each time you create one, the SHA1 in this course are only example. Only consider those in your local repository as reference.
Activity goal
This is our TypeScript implementation for a queue data structure. Compared to other activity repositories, this version has a clean and ordered list of commits.
But I went a step too far when I squashed commits and I no longer have a commit that adds debug traces.
Check the content of the commit for "feat: implement dequeue()"
git diff $(git log --format='%h~1' --grep 'feat:.*dequeue')Understanding this command
This Bash command performs two actions:
git logsearches for the commit hash of the commit we wantgit diffshow the code changes
How do we search for the commit:
- commits messages must match the regular expression
feat:.*dequeue - we only display the hash followed by
~1.
The result of this command is captured using the $() syntax and passed as argument to the git diff command.
diff --git a/queue-example.ts b/queue-example.ts
index 44c64f9..76593a7 100644
--- a/queue-example.ts
+++ b/queue-example.ts
@@ -18,15 +18,20 @@ class Queue<T> {
}
size(): number {
- // TODO
+ return this.items.length;
}
enqueue(item: T): void {
+ console.debug("enqueue", item);
this.items.push(item);
+ console.debug("updated queue:", this.items);
}
dequeue(): T | undefined {
- // TODO
+ console.debug("dequeue from:", this.items);
+ let item = this.items.shift();
+ console.debug("updated queue:", this.items);
+ return item;
}
}You can see we have mixed the implementation of dequeue() with the addition of debug logs.
In this activity we will edit this commit to remove the debug logs and improve the code.
Edit commit
Start the rebase
We first need to start the interactive rebase on the last 4 commits:
git rebase -i HEAD~4Rebase starts and the editor opens "git-rebase-todo" with rebase commands:
pick 054015f # feat: implement `enqueue()`
pick d285007 # feat: implement `dequeue()`
pick cef98c4 # feat: implement `size()`
pick 5fba6a2 # fix: dequeue must take first item, not last oneAs we want to change the content of the second commit "feat: implement dequeue()", it's now time to add an "edit" command on the line for that commit:
pick 054015f # feat: implement `enqueue()`
edit d285007 # feat: implement `dequeue()`
pick cef98c4 # feat: implement `size()`
pick 5fba6a2 # fix: dequeue must take first item, not last oneSave and close. The rebase will now stop to let you update this commit.
topped at d285007... # feat: implement `dequeue()`
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continueEdit the source file
Open the source file and apply the following changes:
- remove the debug lines;
- add missing return in
size(); - and replace
letwithconst.
/*
A simple queue example
A generic class to handle object queues.
- `size()` returns the number of items in the queue
- `enqueue()` adds the specified value to tail of the queue
- `dequeue()` remove the head of the queue and returns the value.
*/
class Queue<T> {
/** Queue is a collection of values */
private items: T[];
/** Constructor just creates an empty queue */
constructor() {
this.items = [];
}
size(): number {
// TODO
return 0;
}
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
const item = this.items.pop();
return item;
}
}
// ----- USAGE
// Create a queue
const queue = new Queue<number>();
console.log("After creation queue is empty.", 0 === queue.size());
// Add values to the queue
queue.enqueue(12);
queue.enqueue(40);
queue.enqueue(1);
console.log("Expecting queue to contain 3 items.", 3 === queue.size());
// Remove values from queue
let value: number | undefined;
value = queue.dequeue();
console.log("First value is 12? ", 12 === value);
value = queue.dequeue();
console.log("Second value is 40? ", 40 === value);
value = queue.dequeue();
console.log("Third value is 1? ", 1 === value);
console.log(
"Undefined is returned by dequeue when queue is empty",
undefined === queue.dequeue()
);
console.log(
"After fetching mast value, expect size to be zero.",
0 === queue.size()
);Save the changes and check the status in your shell:
git statusgit diff queue-example.tsdiff --git a/queue-example.ts b/queue-example.ts
index ee8ab74..627dd3c 100644
--- a/queue-example.ts
+++ b/queue-example.ts
@@ -19,18 +19,15 @@ class Queue<T> {
size(): number {
// TODO
+ return 0;
}
enqueue(item: T): void {
- console.debug("enqueue", item);
this.items.push(item);
- console.debug("updated queue:", this.items);
}
dequeue(): T | undefined {
- console.debug("dequeue from:", this.items);
- let item = this.items.pop();
- console.debug("updated queue:", this.items);
+ const item = this.items.pop();
return item;
}
}
@@ -38,7 +35,7 @@ class Queue<T> {
// ----- USAGE
// Create a queue
-let queue = new Queue<number>();
+const queue = new Queue<number>();
console.log("After creation queue is empty.", 0 === queue.size());It's now time to commit your edit.
- stage the source file:
git add queue-example.ts- amend previous commit:
git commit --amend- editor will open a "COMMIT_EDITMSG" file to let you update the message
- we don't want to change the message, so just close.
- the shell will return.
- check the status.
git statusinteractive rebase in progress; onto a9b2493
Last commands done (2 commands done):
pick 054015f # feat: implement `enqueue()`
edit d285007 # feat: implement `dequeue()`
Next commands to do (2 remaining commands):
pick cef98c4 # feat: implement `size()`
pick 5fba6a2 # fix: dequeue must take first item, not last one
(use "git rebase --edit-todo" to view and edit)
You are currently editing a commit while rebasing branch 'main' on 'a9b2493'.
(use "git commit --amend" to amend the current commit)
(use "git rebase --continue" once you are satisfied with your changes)
nothing to commit, working tree cleanLast step is to complete the rebase:
git rebase --continueSolve conflict on size()
You will have a conflict:
Auto-merging queue-example.ts
CONFLICT (content): Merge conflict in queue-example.ts
error: could not apply cef98c4... feat: implement `size()`
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Could not apply cef98c4... # feat: implement `size()`This conflict is caused by our change on the added return in size() implementation. Checking the status with git diff queue-example.ts will shoz the following:
diff --cc queue-example.ts
index 627dd3c,b78be4a..0000000
--- a/queue-example.ts
+++ b/queue-example.ts
@@@ -18,8 -18,7 +18,12 @@@ class Queue<T>
}
size(): number {
++<<<<<<< HEAD
+ // TODO
+ return 0;
++=======
+ return this.items.length;
++>>>>>>> cef98c4 (feat: implement `size()`)
}
enqueue(item: T): void {Just keep incoming changes, save and stage the fixed conflict to mark it as solved:
git add queue-example.tsNow that cnflict is solved and changes are staged, we can continue the rebase:
git rebase --continueGit will open the editor to give you a chance to update the commit message. We keep the message, so just close the editor now to finish.
But we have another conflict!
Solve conflict on dequeue()
This time on the implementation of dequeue() because we replaced the let with a const.
Check the conflict with git diff queue-example.ts:
diff --cc queue-example.ts
index f6fc334,76593a7..0000000
--- a/queue-example.ts
+++ b/queue-example.ts
@@@ -26,7 -27,10 +26,13 @@@ class Queue<T>
}
dequeue(): T | undefined {
++<<<<<<< HEAD
+ const item = this.items.pop();
++=======
+ console.debug("dequeue from:", this.items);
+ let item = this.items.shift();
+ console.debug("updated queue:", this.items);
++>>>>>>> 5fba6a2 (fix: dequeue must take first item, not last one)
return item;
}
}So, now we want the incoming changes but we also want to replace the let with a const. Edit the file and check that new diff is similar to the following:
diff --cc queue-example.ts
index f6fc334,76593a7..0000000
--- a/queue-example.ts
+++ b/queue-example.ts
@@@ -26,7 -27,10 +26,7 @@@ class Queue<T>
}
dequeue(): T | undefined {
- const item = this.items.pop();
- console.debug("dequeue from:", this.items);
- let item = this.items.shift();
- console.debug("updated queue:", this.items);
++ const item = this.items.shift();
return item;
}
}Stage again your change and continue the rebase git rebase --continue. Close the editor that opens for the commit message.
The rebase is finally completed with success!
Keep it simple
Best practice
In the previous section, we managed to edit a file’s content.
But as we applied more changes than just removing the debug logs, we introduced some unnecessary conflicts in the rebase.
To prove the point we will redo this rebase but with limited edit.
Create a new playground:
./script/create-playground.sh rebase-int_edit--noconflictGo the folder and restart a rebase:
cd playgrounds/rebase-int_edit--noconflict ; git rebase -i HEAD~4We want to edit the same commit, so your rebase actions should be edited as bellow:
pick 0a691d2 # feat: implement `enqueue()`
e c8bc343 # feat: implement `dequeue()`
pick 97cc0da # feat: implement `size()`
pick 3ec60f4 # fix: dequeue must take first item, not last oneOpen the source file and only remove the lines with console.debug(). Save the change. Running git diff queue-example.ts should show:
diff --git a/queue-example.ts b/queue-example.ts
index ee8ab74..d751413 100644
--- a/queue-example.ts
+++ b/queue-example.ts
@@ -22,15 +22,11 @@ class Queue<T> {
}
enqueue(item: T): void {
- console.debug("enqueue", item);
this.items.push(item);
- console.debug("updated queue:", this.items);
}
dequeue(): T | undefined {
- console.debug("dequeue from:", this.items);
let item = this.items.pop();
- console.debug("updated queue:", this.items);
return item;
}
}Let's continue the rebase:
git add queue-example.ts
git rebase --continueWhen the editor prompts you to edit the commit message, just close it. Continue the interactive rebase until completion.
You will still get the second conflict as we edited same set of lines. But you can see that conflict on the added return is no longer there.
INFO
Keeping minimal changes in each commit is not a garanty that you won't get conflicts.
As you saw in this simple example you can avoid conflicts if you limit your changes.
Insert new commit
We saw how to edit and amend a commit.
But we can do more complex edition and even create extract commits.
Create a new playground
./scripts/create-playground.sh rebase-int_edit--split
cd playgrounds/rebase-int_edit--split
git rebase -i HEAD~5Edit again the commit of dequeue() implementation:
pick e3e969b # feat: class skeleton
pick ccb7b57 # feat: implement `enqueue()`
e c240015 # feat: implement `dequeue()`
pick d0cc789 # feat: implement `size()`
pick a40e98f # fix: dequeue must take first item, not last oneSave and close. Rebase stops on the "feat: implement dequeue()" commit.
Remove the logs in the dequeue() method.
diff --git a/queue-example.ts b/queue-example.ts
index ee8ab74..5a2c0e3 100644
--- a/queue-example.ts
+++ b/queue-example.ts
@@ -28,9 +28,7 @@ class Queue<T> {
}
dequeue(): T | undefined {
- console.debug("dequeue from:", this.items);
let item = this.items.pop();
- console.debug("updated queue:", this.items);
return item;
}
}Stage this change and amend the commit:
git add queue-example.ts
git commit --amendClose the commit message editor to continue. Now we will remove debug logs on the enqueue():
diff --git a/queue-example.ts b/queue-example.ts
index 5a2c0e3..d751413 100644
--- a/queue-example.ts
+++ b/queue-example.ts
@@ -22,9 +22,7 @@ class Queue<T> {
}
enqueue(item: T): void {
- console.debug("enqueue", item);
this.items.push(item);
- console.debug("updated queue:", this.items);
}
dequeue(): T | undefined {- stage the change
git add queue-example.ts- commit this
git commit -m 'fix: remove debug `in enqueue()`'- and continue the rebase
git rebase --continueYou will have the same conflict on the dequeue() method. Solve it and continue.
Once rebase is completed you can check that history include a new commit:
git log --oneline9b8df07 (HEAD -> main) fix: dequeue must take first item, not last one
2ad2c7f feat: implement `size()`
beb9769 fix: remove debug `in enqueue()`
e726594 feat: implement `dequeue()`
ccb7b57 feat: implement `enqueue()`
e3e969b feat: class skeleton
1edc8ae test: add `size()`
d0325d8 test: add `dequeue()`
ce209b1 test: add `enqueue()`
2b48a5c feat: create file with draft specificationsWhat we learned
Thanks to the "edit" command you can update the content of an existing commit.
- Keeping changes minimal helps to reduce conflicts, but you won't always avoid them.
- You can amend a commit after edition, but it is also possible to create additional commits that will be inserted in the log history.
Editing a commit can be way to introduce feedback from pull requests without adding new commits in your delivery.