Rewriting History

Amending patches

When you start using version control you will quickly encounter situations where you have recorded a patch and realize that you have made a mistake. For example you have recorded a patch with a typo or you have forgotten to state a dependency in a patch. That’s really super annoying. What you could do now is record a new patch correcting that error but that would make our history more and more confusing as time progresses and this kind patches start to accumulate. This is where darcs amend comes in handy, it let’s you change a patch the you have already recorded without creating a new one.

As an example we are going to pretend that we accidentally didn’t state the dependency of our use B patch in the above section correctly, so our repository once again looks like this.

So let’s use amend to right that wrong.

$ darcs amend --ask-deps -p 'use B'
patch 610c31ea41237cd407464ff41720d04bce214ef2
Author: raichoo@example.com
Date:   Mon Jul  9 20:05:30 CEST 2018
  * use B
Shall I amend this patch? [yNjk...], or ? for more options: y
patch 376b1bc0febb10802797a5afe7d55e35e42ac89f
Author: raichoo@example.com
Date:   Mon Jul  9 19:04:01 CEST 2018
  * change B
Shall I depend on this patch? (1/2)  [ynW...], or ? for more options: y
Will not ask whether to depend on 1 already decided patch.
Do you want to Depend on these patches? [Yglqk...], or ? for more options: y
Finished amending patch:
patch 9890bbfa50839674d45c416395bdb7e3bcd0ac00
Author: raichoo@example.com
Date:   Mon Jul  9 20:07:22 CEST 2018
  * use B

We have used the -p flag to state a pattern for the patch we want to amend and instructed darcs to prompt us for dependencies. We don’t need to specify a pattern but sometimes if we know exactly what patch we want to amend it can certainly speed up the process. After selecting the patch we want to take care of, darcs will prompt us for changes just like it would to when recording a change. Our repository now looks like we want it to.

amend works very much like record but rather than recording a new patch it prompts you for an existing patch that you intend to improve first and then proceeds just like we are used to when recording patches. We can add more changes to our patch, change hunks, messages, author etcetera. Keep in mind that you should not rewrite patches that have already been pushed to a public repository since this action is local only, pushed patches will remain in your remote repository even if you amend them locally. If you want to change the state of a remote repository your best option is to record a new patch and push it there.

You also cannot amend patches that are a dependency of another patch. That would be like pulling a rug away from under their feet. There are more powerful tools that let you do even that, but that’s something for a later chapter.

Another example that is a bit more common deals with typos. Typos are probably the most annoying thing when you deal with recording patches. They show up in parts of your projects as well as patch names. Heck, they are pretty much everywhere, just look at this patch gone horribly wrong.

$ darcs log -v
patch a7f5a2efe162787b3e7f6ceb215f53fa300f6590
Author: raichoo@example.com
Date:   Tue Jul 10 19:41:56 CEST 2018
  * initial rceord
    addfile ./Main.hs
    hunk ./Main.hs 1
    +main = "Hello Wrold!"

Oh boy, looks like we weren’t quite awake yet when we recorded that patch. We misspelled “World” as well as “record”. That’s quite embarrassing, surely we don’t want to push that! Thankfully we can still amend that patch before its publication.

$ sed -i '' 's/Wrold/World/' Main.hs
$ darcs amend -m 'initial record'
patch a7f5a2efe162787b3e7f6ceb215f53fa300f6590
Author: raichoo@example.com
Date:   Tue Jul 10 19:41:56 CEST 2018
  * initial rceord
Shall I amend this patch? [yNjk...], or ? for more options: y
hunk ./Main.hs 1
-main = "Hello Wrold!"
+main = "Hello World!"
Shall I record this change? (1/1)  [ynW...], or ? for more options: y
Do you want to Record these changes? [Yglqk...], or ? for more options: y
Finished amending patch:
patch 1e00a2684025c2ee9e1cbbb809d71d8167482458
Author: raichoo@example.com
Date:   Tue Jul 10 19:45:57 CEST 2018
  * initial record

Patch quality is certainly an important thing to take care of, so be sure to double check before you publish something important.

Unrecording patches

Sometimes we want to get rid of patches without getting rid of the work we have done. Maybe we want to fold a bunch of patches into one, or maybe we want to organize our patches a little differently. Whatever the case, sometimes some patches need to be removed from our local repository and unrecord is how we can do that.

Let’s say we are currently working on a new feature in our project and our history currently looks something like this.

We started out with a Haskell program just printing out “Hello!” and then we got the feature request asking us to add two more messages to the output. First we added some code that additionally prints out “World!”. At that point wanted to capture our progress, so we decided to record a patch and call it WIP. Then we proceeded to add the other message requested in the feature and record that as well.

$ cat Main.hs
main = do
  putStrLn "Hello!"
  putStrLn "World!"
  putStrLn "Everyone!"
$ darcs log -v
patch 4f43a522ae0162dcef8dab6aab0d354ede209767
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:56 CEST 2018
  * WIP
    hunk ./Main.hs 4
    +  putStrLn "Everyone!"

patch b7cf79bc1e3454f7dfdee86a480e72f312b43a14
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:42 CEST 2018
  * WIP
    hunk ./Main.hs 3
    +  putStrLn "World!"

patch faf56a95edb01004d10f3bd4b72ad8d2b5ad2044
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:10 CEST 2018
  * initial record
    addfile ./Main.hs
    hunk ./Main.hs 1
    +main = do
    +  putStrLn "Hello!"

That’s not a very useful history and really not fit for publishing since it’s showing our work in progress rather than a finished patch. It certainly served its purpose, but it really needs to be cleaned up before we show it to the rest of the world. We have recorded the state of our working tree whenever we felt confident and then moved onward to the next step. We want to squash the two WIP patches into a single patch. The first thing we do is that we unrecord them. Since we have named all our “work in progress” patches WIP we can specify a pattern for unrecord using the -p flag so darcs will only ask us if it should unrecord patches that have a name matching this pattern.

$ darcs unrecord -p WIP
patch 4f43a522ae0162dcef8dab6aab0d354ede209767
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:56 CEST 2018
  * WIP
Shall I unrecord this patch? (1/2)  [ynW...], or ? for more options: y
patch b7cf79bc1e3454f7dfdee86a480e72f312b43a14
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:42 CEST 2018
  * WIP
Shall I unrecord this patch? (2/2)  [ynW...], or ? for more options: y
Do you want to Unrecord these patches? [Yglqk...], or ? for more options: y
Finished unrecording.
$ darcs log
patch faf56a95edb01004d10f3bd4b72ad8d2b5ad2044
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:10 CEST 2018
  * initial record

Okay, good. We are now back with only one patch in our repository. We have decided that this is the only thing that we want to keep and re-record our work on top of that. Since unrecord does not change our working tree still contains our work.

$ cat Main.hs
main = do
  putStrLn "Hello!"
  putStrLn "World!"
  putStrLn "Everyone!"

Now all the modifications that we have done to our project since the initial record are now unrecorded again and darcs whatsnew will report them as such.

$ darcs whatsnew
hunk ./Main.hs 3
+  putStrLn "World!"
+  putStrLn "Everyone!"

So let’s create a new patch single patch out of these changes and call it add cool new feature.

$ darcs record -a -m 'add cool new feature'
Finished recording patch 'add cool new feature'
$ darcs log -v
patch 07729b85848c949e03ae856005a8c90bf42c785a
Author: raichoo@example.com
Date:   Sun Jul  8 15:11:42 CEST 2018
  * add cool new feature
    hunk ./Main.hs 3
    +  putStrLn "World!"
    +  putStrLn "Everyone!"

patch faf56a95edb01004d10f3bd4b72ad8d2b5ad2044
Author: raichoo@example.com
Date:   Sun Jul  8 14:51:10 CEST 2018
  * initial record
    addfile ./Main.hs
    hunk ./Main.hs 1
    +main = do
    +  putStrLn "Hello!"

That’s better. We are now happy with our new patch. Again, keep in mind that you should not do this with patches you have already published. Just like amend this operation is local only.

Unrecording individual changes

In the previous sections we have met amend and unrecord and sometimes it would be great if we could bring those two functionalities together. Image you have recorded a patch but instead of adding something to it by using amend you would like to get rid of a change in that patch. Right now we only know how to add things to a patch by using amend or unrecord to get right of the whole thing. So here’s a situation where things went a bit wrong. We have recorded a patch and added a change that we didn’t really want.

patch 319dfd6efb2f69bcb5205263aa716cfe6e5d54ea
Author: raichoo@example.com
Date:   Mon Jul 16 20:20:28 CEST 2018
  * adding some stuff
    hunk ./file.txt 1
    +I want to add this
    hunk ./file.txt 3
    +but not this…

So one possibility would be to unrecord the whole patch and record it again, this time without the change that we didn’t want to have in there. In this situation that might not be that big of an issue, but if we had a larger patch and only wanted to get rid of a single line this approach is unacceptable. However, amend has a very handy --unrecord flag that allows us to unrecord individual changes from a patch. Super useful!

$ darcs amend --unrecord
patch 319dfd6efb2f69bcb5205263aa716cfe6e5d54ea
Author: raichoo@example.com
Date:   Mon Jul 16 20:20:28 CEST 2018
  * adding some stuff
Shall I amend this patch? [yNjk...], or ? for more options: y
hunk ./file.txt 1
+I want to add this
Shall I unrecord this change? (1/2)  [ynW...], or ? for more options: n
hunk ./file.txt 3
+but not this…
Shall I unrecord this change? (2/2)  [ynW...], or ? for more options: y
Do you want to Unrecord these changes? [Yglqk...], or ? for more options: y
Finished amending patch:
patch 796db1adfd5a11c00feec4ee50fff63e4fd4ddd1
Author: raichoo@example.com
Date:   Mon Jul 16 20:25:22 CEST 2018
  * adding some stuff
$ darcs log -v --last 1
patch 796db1adfd5a11c00feec4ee50fff63e4fd4ddd1
Author: raichoo@example.com
Date:   Mon Jul 16 20:25:22 CEST 2018
  * adding some stuff
    hunk ./file.txt 1
    +I want to add this
$ darcs whatsnew
hunk ./file.txt 3
+but not this…

That’s a lot more convenient and this way we can make a lot less mistakes than if we were to re-record our patch.

OB-LIT-E-RATE!

Sometimes we want to go even further than unrecord. For example when a feature we are working on turns out to be completely broken and we think that we are better off to start from scratch. Of course we could just do an unrecord followed by a revert to make the patches disappear from our repository and then make the changes disappear from our working tree. If that is what you want you can use obliterate. That command sounds scary for a reason. It basically throws away work permanently.

So here’s our repository from the first section again.

$ ls
A  B _darcs
$ darcs log
patch 9890bbfa50839674d45c416395bdb7e3bcd0ac00
Author: raichoo@example.com
Date:   Mon Jul  9 20:07:22 CEST 2018
  * use B

patch 376b1bc0febb10802797a5afe7d55e35e42ac89f
Author: raichoo@example.com
Date:   Mon Jul  9 19:04:01 CEST 2018
  * change B

patch 1f9b4b1566ce3410c70ad5231ff21abb0dfed04b
Author: raichoo@example.com
Date:   Mon Jul  9 19:02:49 CEST 2018
  * change A

patch 0d87b94c8d03fea3a2b613c43032df8da8cbff93
Author: raichoo@example.com
Date:   Mon Jul  9 19:02:02 CEST 2018
  * add B

patch 0b2f173ea79d311c34d8e321b6a8d86582d18ff4
Author: raichoo@example.com
Date:   Mon Jul  9 19:01:03 CEST 2018
  * add A

We now want to get rid of all of the changes involving the file B and we want to get rid of them for good.

$ darcs obliterate
patch 9890bbfa50839674d45c416395bdb7e3bcd0ac00
Author: raichoo@example.com
Date:   Mon Jul  9 20:07:22 CEST 2018
  * use B
Shall I obliterate this patch? (1/5)  [ynW...], or ? for more options: y
patch 376b1bc0febb10802797a5afe7d55e35e42ac89f
Author: raichoo@example.com
Date:   Mon Jul  9 19:04:01 CEST 2018
  * change B
Shall I obliterate this patch? (2/5)  [ynW...], or ? for more options: y
patch 1f9b4b1566ce3410c70ad5231ff21abb0dfed04b
Author: raichoo@example.com
Date:   Mon Jul  9 19:02:49 CEST 2018
  * change A
Shall I obliterate this patch? (3/5)  [ynW...], or ? for more options: n
patch 0d87b94c8d03fea3a2b613c43032df8da8cbff93
Author: raichoo@example.com
Date:   Mon Jul  9 19:02:02 CEST 2018
  * add B
Shall I obliterate this patch? (4/5)  [ynW...], or ? for more options: y
Will not ask whether to obliterate 1 already decided patch.
Do you want to Obliterate these patches? [Yglqk...], or ? for more options: y
Finished obliterating.
$ darcs log
patch 1f9b4b1566ce3410c70ad5231ff21abb0dfed04b
Author: raichoo@example.com
Date:   Mon Jul  9 19:02:49 CEST 2018
  * change A

patch 0b2f173ea79d311c34d8e321b6a8d86582d18ff4
Author: raichoo@example.com
Date:   Mon Jul  9 19:01:03 CEST 2018
  * add A
$ ls
A  _darcs

obliterate has done its name justice. All the patches involving B are gone as well as the related files in our working tree. Notice how darcs has kept all our changes regarding file A even though the changes to A and B where interleaved in our first darcs log output. But there is no relationship between those changes so darcs could separate those two cleanly from one another.

There is also a way to obliterate all patches that are not in a remote repository. This is particularly useful if you have applied local patches to your repository that clash with remote patches. To remove local-only patches just add the --not-in-remote flag.

All your rebase are belong to us!

Sometimes amend just does not cut it. We might need to go deeper because we have spotted a mistake we have made in a patch further down our dependency chain. Imagine the following situation.

$ darcs log
patch 7f4723c5a539c75fce70dd7a3e7b12fcb284aa7b
Author: raichoo@example.com
Date:   Mon Aug 17 12:16:01 CEST 2020
  * C

patch 7ed8dfe47953186ca4d97cee32898e926bf5eb7e
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:24 CEST 2020
  * B

patch d3fbcaf6e187b1e582caf031d39f29ddf332570b
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:08 CEST 2020
  * A

As it turns out, I made a mistake in patch B which I want to repair before pushing my changes to the remote repository. So let’s try to amend B.

$ darcs amend
patch 7f4723c5a539c75fce70dd7a3e7b12fcb284aa7b
Author: raichoo@example.com
Date:   Mon Aug 17 12:16:01 CEST 2020
  * C
Shall I amend this patch? [yNjk...], or ? for more options: n

Skipping depended-upon patch:
patch 7ed8dfe47953186ca4d97cee32898e926bf5eb7e
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:24 CEST 2020
  * B

Skipping depended-upon patch:
patch d3fbcaf6e187b1e582caf031d39f29ddf332570b
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:08 CEST 2020
  * A
Cancelling amend since no patch was selected.

Oh no! What happened? We cannot amend B. darcs does not even prompt it as an option here. Why is that? Let’s take a look at the dependency graph to shed a little light onto the situation and things will become a little clearer.

Ah! C depends on B and A, so we can not simply amend B because we would pull the rug from under C’s feet (remember that amend changes patch identity, but C already depends on B). What we could do is just unrecord C, then amend B and record C again. That would work, but it’s not a very good approach if you have to go multiple patches deeper. You don’t want to re-record all of them again, it would be way too much work and also quite error prone.

Here is where rebase comes into play. As the name suggests, it allows us to change the dependencies a patch it based on. Naturally this means we are changing the patches identity that we are rebasing, since we are changing what they are depending on.

So how does it work?

The rebase command allows us to suspend and unsuspend patches. Suspending means that we putting a patch on the side, it doesn’t really apply to our repository anymore, but it is still floating around in a suspended state though. So we want to work on B, that means that we first have to suspend C.

$ darcs rebase suspend
patch 7f4723c5a539c75fce70dd7a3e7b12fcb284aa7b
Author: raichoo@example.com
Date:   Mon Aug 17 12:16:01 CEST 2020
  * C
Shall I suspend this patch? (1/3)  [ynW...], or ? for more options: y
patch 7ed8dfe47953186ca4d97cee32898e926bf5eb7e
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:24 CEST 2020
  * B
Shall I suspend this patch? (2/3)  [ynW...], or ? for more options: d
Rebase in progress: 1 suspended patch

In the last line tells us that there is one suspended patch, waiting for us to take care of it. If we log take a look at our log we can see that C is no longer part of our repository. This is going to happen with every command that we issue at this point, just to make sure that we do not forget about the patches we are currently rebasing.

$ darcs log
patch 7ed8dfe47953186ca4d97cee32898e926bf5eb7e
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:24 CEST 2020
  * B

patch d3fbcaf6e187b1e582caf031d39f29ddf332570b
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:08 CEST 2020
  * A
Rebase in progress: 1 suspended patch

We can also take a look at all our suspended patches using darcs rebase log.

$ darcs rebase log
patch 0919c58f931ffcc93bc5fbe41f8fdcc0a9fab1ba
Author: raichoo@example.com
Date:   Mon Aug 17 12:16:01 CEST 2020
  * C
Rebase in progress: 1 suspended patch

Perfect! C is waiting for us to finish our work. We can now do all the operations we want without taking our suspended patch into account. You might also have realized that the hash of the suspended patch has changed. This happens every time we suspend a patch, so should not do this with patches you have already published.

We have now brought B into shape. It’s time to unsuspend C again!

$ darcs rebase unsuspend
patch 0919c58f931ffcc93bc5fbe41f8fdcc0a9fab1ba
Author: raichoo@example.com
Date:   Mon Aug 17 12:16:01 CEST 2020
  * C
Shall I unsuspend this patch? (1/1)  [ynW...], or ? for more options: y
Do you want to Unsuspend these patches? [Yglqk...], or ? for more options: y
Rebase finished!

$ darcs log
patch bcd766f6817c2c4757101db96f241e86cb47571f
Author: raichoo@example.com
Date:   Mon Aug 17 14:20:14 CEST 2020
  * C

patch 08d1ebf883423391e80152a9d0562b569df8b4cf
Author: raichoo@example.com
Date:   Mon Aug 17 13:16:12 CEST 2020
  * B

patch d3fbcaf6e187b1e582caf031d39f29ddf332570b
Author: raichoo@example.com
Date:   Mon Aug 17 12:15:08 CEST 2020
  * A

Great, we are back to where we started but we have adjusted B to our liking. Once we unsuspend our patch, it changes its hash one last time and the rebase is completed.