Skip to content

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:

bash
./scripts/create-playground.sh rebase-int_edit

Check the commit log history in the new playground repository.

bash
cd playgrounds/rebase-int_edit ; git log --oneline
5fba6a2 (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 specifications

WARNING

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()"

bash
git diff $(git log --format='%h~1' --grep 'feat:.*dequeue')
Understanding this command

This Bash command performs two actions:

  1. git log searches for the commit hash of the commit we want
  2. git diff show 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
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:

bash
git rebase -i HEAD~4

Rebase starts and the editor opens "git-rebase-todo" with rebase commands:

git-rebase
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 one

As 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:

git-rebase
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 one

Save and close. The rebase will now stop to let you update this commit.

console
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 --continue

Edit the source file

Open the source file and apply the following changes:

  • remove the debug lines;
  • add missing return in size();
  • and replace let with const.
typescript
/*
 
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:

bash
git status
bash
git diff queue-example.ts
diff
diff --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.

  1. stage the source file:
bash
git add queue-example.ts
  1. amend previous commit:
bash
git commit --amend
  1. editor will open a "COMMIT_EDITMSG" file to let you update the message
  2. we don't want to change the message, so just close.
  3. the shell will return.
  4. check the status.
bash
git status
console
interactive 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 clean

Last step is to complete the rebase:

bash
git rebase --continue

Solve 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
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:

bash
git add queue-example.ts

Now that cnflict is solved and changes are staged, we can continue the rebase:

bash
git rebase --continue

Git 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
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
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:

bash
./script/create-playground.sh rebase-int_edit--noconflict

Go the folder and restart a rebase:

bash
cd playgrounds/rebase-int_edit--noconflict ; git rebase -i HEAD~4

We want to edit the same commit, so your rebase actions should be edited as bellow:

git-rebase
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 one

Open the source file and only remove the lines with console.debug(). Save the change. Running git diff queue-example.ts should show:

diff
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:

bash
git add queue-example.ts
git rebase --continue

When 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

bash
./scripts/create-playground.sh rebase-int_edit--split
cd playgrounds/rebase-int_edit--split
git rebase -i HEAD~5

Edit again the commit of dequeue() implementation:

git-rebase
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 one

Save and close. Rebase stops on the "feat: implement dequeue()" commit.

Remove the logs in the dequeue() method.

diff
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:

bash
git add queue-example.ts
git commit --amend

Close the commit message editor to continue. Now we will remove debug logs on the enqueue():

diff
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
bash
git add queue-example.ts
  • commit this
bash
git commit -m 'fix: remove debug `in enqueue()`'
  • and continue the rebase
bash
git rebase --continue

You 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:

bash
git log --oneline
9b8df07 (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 specifications

What 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.