Working locally

Time for some action! Until this point we didn’t really use darcs for anything, but now we are familiar with its core concepts and we can have some fun. In this chapter we will explore how to work locally with darcs. We will create our own patches, review them and look at some workflows.

Creating our first repository

Every new project starts somewhere and if you are using version control it usually starts with initializing a repository for our patches to live in. So let’s jump right into the terminal and get going!

We want to create a new project and call it my-awesome-project, no need to be humble here, we are ambitious! A new repository is initialized using the init command.

$ darcs init my-awesome-project
Repository initialized.

That’s it! darcs tells us that the repository has been initialized. It automatically created a new folder my-awesome-project and inside of that folder we will find a folder called _darcs, this is where darcs keeps all its information regarding the repository. Whenever we are issuing a darcs command darcs will try to find this folder somewhere in our directory hierarchy. For the rest of this chapter I will assume that we are inside of the my-awesome-project folder.

If you want to create a darcs repository for an existing project, simply enter its directory and issue darcs init. darcs won’t touch any of your work but will only initialize a repository.

Ch-Ch-Ch Changes!

Okay, we now have a repository. Let’s find out what patches it holds. We can do this by issuing darcs log.

$ darcs log

Huh, darcs returns without reporting any patches. That’s not very surprising since we have not recorded any yet.

To warm up let’s just create a simple “Hello World” program written in Haskell inside of our newly created project folder and add that to our repository.

$ echo 'main = putStrLn "Hello World!"' > Main.hs

This creates a new file Main.hs with the content main = putStrLn "Hello World", of course you can also use your favorite text editor to do this. By doing this we have changed our working tree.

To find out what changes we have done to the working tree we can issue darcs status.

$ darcs status
a ./Main.hs

What darcs does here is that it compares the state of your working tree against the state of the repository (recall that the state of the repository is a set of changes). In our current repository there are no recorded changes so adding something to our working tree surely should constitute a change shouldn’t it?

Let’s take a closer look at the output of darcs status. What does the lower case a in front of ./Main.hs mean? Well, it means that the file Main.hs is currently unadded, more on that in a second. Let’s try to record a patch and see what happens.

When we want to record a new patch we can simply issue darcs record and darcs will compare our current working tree against the state of our repository and consider all unrecorded changes for the patch we are about to record.

$ darcs record
No changes!

What? But we just created a new file? How isn’t that a change? Well, darcs only looks at changes to added files, remember that darcs status reported Main.hs as unadded, so we need to inform it that we are interested in Main.hs. We do this by issuing darcs add Main.hs.

$ darcs add Main.hs
Adding 'Main.hs'
$ darcs status
A ./Main.hs

Look at that! The lower case a changed to an upper case A, darcs is now keeping track of changes to our Main.hs and we can record our first change. If you want to have a list of all the files in your working tree that darcs keeps track of darcs show files is your friend.

$ darcs record -m 'add Main.hs'
Each patch is attributed to its author, usually by email address (for
example, `Fred Bloggs <fred@example.net>').  Darcs could not determine
your email address, so you will be prompted for it.

Your address will be stored in /home/raichoo/.darcs
It will be used for all patches you record in ALL repositories.
If you move that file to _darcs/prefs/author, it will
be used for patches recorded in this repository only.
What is your email address? raichoo@example.com
addfile ./Main.hs
Shall I record this change? (1/2)  [ynW...], or ? for more options: y
hunk ./Main.hs 1
+main = putStrLn "Hello World!"
Shall I record this change? (2/2)  [ynW...], or ? for more options: y
Do you want to Record these changes? [Yglqk...], or ? for more options: y
Finished recording patch 'add Main.hs'

A lot of things happened here, so let’s break it down.

So what’s with the -m 'add Main.hs'? That’s actually the name we are giving this patch. We can later use it to refer to it and it’s also a small summary of what this patch is supposed to do. If you don’t specify a name darcs will open your text editor (which is specified by the $EDITOR environment variable) and you can provide the name there. Apart from only giving the patch a name you can also give some more detail about your new patch.

The first thing that darcs does is asking us for our email, this makes sense because every patch needs an author. Once we tell darcs who we are it stores our email address in ~/.darcs and won’t bother us again.

Now darcs asks us about the actual changes that we want to record for this patch. The first one is that we simply want to add the Main.hs file that we created. The type of this change is addfile. The second change it asks us about adds the content to the file, this change type is what we call a hunk. This hunk just adds our single line program as line 1 to Main.hs (indicated by the + in front of the line). We will look at different types of changes later in this chapter.

We have recorded our first patch!

Inspecting recorded patches

Once we start to record patches we will come to a point where we want to review our work. darcs log will show us our recorded patches.

$ darcs log
patch 2e484ff04a52871b0f2ee643a31c155b24eb1898
Author: raichoo@example.com
Date:   Tue Jun 26 22:54:24 CEST 2018
  * add Main.hs

Here you can see the name we have given to the patch namely ‘add Main.hs’, the author, time and date of its creation and a hash 2e484ff04a52871b0f2ee643a31c155b24eb1898. To refer to a specific patch you can use its name or the hash. The hash of a patch is supposed to be unique and is generated out of its name, name of its author, time stamp, a random seed, as well as some other information (not its content though). It stays the same for the entire lifetime of a patch. There are some special operations that allow you to fundamentally change a patch and consequently its hash changes as well. We will look at these operations in a later chapter.

To get a more detailed output we can issue darcs log -v and we can see the changes recorded in this patch.

patch 2e484ff04a52871b0f2ee643a31c155b24eb1898
Author: raichoo@exmaple.com
Date:   Tue Jun 26 22:54:24 CEST 2018
  * add Main.hs
    addfile ./Main.hs
    hunk ./Main.hs 1
    +main = putStrLn "Hello World!"

record is undoubtedly one of the central commands in darcs and therefore offers a lot of options to make working with it a lot more pleasant. You have seen that darcs asks you for every change if you want to record it or not. That might become super tedious, especially when you already know that you want to record all of them. In that case you can always issue darcs record -a. The -a is a short hand for --all and it simply tells darcs to record all of the unrecorded changes. If you just want to record the changes of one or more specific files you can pass them as arguments to record and darcs will only ask you about recording changes in these files. So if I only wanted to record changes for this chapter of the book I would say darcs record en/03-working-locally.md.

Recursively adding files

When you need to add quite a lot of files all at once you don’t want to do that one by one. add offers a --recursive flag and its short hand -r which instructs add to traverse the working tree and add as yet unadded files. Let me show you.

Here we create just a couple of files and add them all at once.

$ mkdir quux
$ touch foo bar baz quux/foo quux/bar quux/baz
$ darcs add -r .
Adding 'bar'
Adding 'baz'
Adding 'foo'
Adding 'quux'
Adding 'quux/bar'
Adding 'quux/baz'
Adding 'quux/foo'
$ darcs status
A ./bar
A ./baz
A ./foo
A ./quux/
A ./quux/bar
A ./quux/baz
A ./quux/foo

Hrm, now we have quite a lot of new changes and when we want to record them darcs will prompt us for each of them if we want to record that change. But as we have learned in the previous section record offers a flag -a that automatically says yes to all unrecorded changes. I think this is a good time to demonstrate this.

$ darcs record -a -m 'a lot of new files'
Finished recording patch 'a lot of new files'

BAMM! Seven with one stroke! While interactive commands are really helpful most of the time, sometimes we need to be able to express our intent with as little work as possible, and darcs can do exactly that without getting in our way.

If you accidentally added a file that you didn’t want to add you can use darcs remove FILENAME and the file in question will become unadded again.

Removing a file

At some point we might want to get rid of a file in our repository. We don’t really need remove for that. You can simply delete the file and record a new change. Don’t worry, your file is still contained in the history if you recorded it earlier, so you can always get it back.

$ rm Main.hs
$ darcs status
R ./Main.hs
$ darcs record -m 'remove Main.hs'

darcs status indicates that we have removed by displaying an upper case R in its output.

I like to move it!

For some reason we have decided that we do not like the name Main.hs for our file, Hello.hs is much more fitting. With darcs move we create a move change. Let’s take a look.

$ darcs move Main.hs Hello.hs
Moved: Main.hs to: Hello.hs
$ darcs status
./Main.hs -> ./Hello.hs
$ darcs record -m 'rename Main.hs'
move ./Main.hs ./Hello.hs
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 recording patch 'rename Main.hs'
$ darcs log -v
patch 785647f796cad98bcb63d2c30c8e88223600059b
Author: raichoo@exmaple.com
Date:   Tue Jun 26 23:23:00 CEST 2018
  * rename Main.hs
    move ./Main.hs ./Hello.hs

patch 2e484ff04a52871b0f2ee643a31c155b24eb1898
Author: raichoo@exmaple.com
Date:   Tue Jun 26 22:54:24 CEST 2018
  * add Main.hs
    addfile ./Main.hs
    hunk ./Main.hs 1
    +main = putStrLn "Hello World!"

move changes give darcs more context on what is actually happening. If we were to simply move the file using the Unix command mv to darcs it would look as if we had removed the file and added a new one.

$ ls
Main.hs _darcs
$ mv Main.hs Hello.hs
$ darcs status
R ./Main.hs
a ./Hello.hs

This is not what we want. But this pattern is so common that darcs is able to detect it. The --look-for-moves flag which is supported by status and record can figure this out.

$ darcs status --look-for-moves
 ./Main.hs -> ./Hello.hs
$ darcs record --look-for-moves -m 'rename Main.hs'
move ./Main.hs ./Hello.hs
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 recording patch 'rename Main.hs'

Great! darcs could figure this out even though we didn’t specify the move explicitly. This is quite useful if we forgot to use move or if some other tool that is unaware of darcs has moved the file without informing us.

What’s new pussy cat?

Since we currently only added and moved files it’s time for things to get a little more interesting. We are going to make some actual changes to our code. So let’s break out our text editor and change our "Hello World!" into "Hello Everyone!". I’ll use sed to change the program, but you can use whatever you want.

$ sed -i '' 's/World/Everyone/' Hello.hs
$ darcs status
M ./Hello.hs -1 +1

As you can see, darcs has detected a change. In particular it has detected that the file Hello.hs has been modified which is indicated by the upper case M. Furthermore it reports some more information about the modification. The -1 means that we have deleted a line while the +1 means that we have edited a line. To look at the actual change we can use darcs whatsnew.

$ darcs whatsnew
hunk ./Hello.hs 1
-main = putStrLn "Hello World!"
+main = putStrLn "Hello Everyone!"

darcs whatsnew has detected a hunk change that has happened to line 1. What it does is that it removes the entire line 1 (indicated by the - at the beginning of the line) and adds a new line (indicated by the +). So darcs has swapped out the entire line just because we have changed a single word as indicated by the hunk change. If we wanted to we could record a new patch based on this patch but let’s look at another way to do something like this.

One thing I like to do when revisiting my unrecorded changes is to use whatsnew -i which puts the command into interactive mode. That way I can step through all of the changes one by one rather that scrolling through what can be potentially a lot of output.

A little side note, status is basically just an alias for whatsnew -ls where -s is a short hand for --summary and -l is a short hand for --look-for-adds. The former gives us the nice concise description of the unrecorded changes in our working tree, the latter searches for unadded files.

Seek and replace

Let’s pretend that we didn’t do the last change where we replaced “World” with “Everyone”. darcs replace offers a different way to make this change without deleting the old line and inserting a new one.

$ darcs replace World Everyone Hello.hs
M ./Hello.hs r1
$ darcs whatsnew
replace ./Hello.hs [A-Za-z_0-9] World Everyone

Changes like this allow you to be a little more specific about what your intentions are. replace is often used when renaming functions or variables.

Even if you don’t replace explicitly you can instruct darcs to look for replaces for you with the --look-for-replaces flag.

$ cat Hello.hs
main = putStrLn "Hello World!"
$ sed -i '' 's/World/Everyone/' Hello.hs
$ darcs whatsnew
hunk ./Hello.hs 1
-main = putStrLn "Hello World!"
+main = putStrLn "Hello Everyone!"

Just as we suspected, darcs has detected a hunk instead of a replace. Let’s use --look-for-replaces and see what happens.

$ darcs whatsnew --look-for-replaces
replace ./Hello.hs [A-Za-z_0-9] World Everyone
$ darcs record --look-for-replaces -m Everyone
replace ./Hello.hs [A-Za-z_0-9] World Everyone
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 recording patch 'Everyone'

Just like with --look-for-moves darcs could figure this out on its own. In chapter 6 we will take a closer look at the benefits of this type of change.

Booooooooooring!!!

Remember that unadded files are displayed with a lower case a in the darcs status output? This can be a bit annoying because there might be some files in our working tree that we would never consider for a patch. For files like this we have a list of files that we consider boring.

Here’s an example. We now have our Hello.hs file in our working tree and we want to compile that program, when we do that our compiler will emit a new binary file Hello. Now, if we take a look at our status we get a surprise.

$ darcs status
a ./Hello

Hm, so now Hello is considered unadded but we never want to add it to the repository anyway, it’s a good common practice to only track the source code of a project rather than the product. This situation might not be so bad but imagine you have a bunch of these files. Your status output will soon become cluttered with files you actually want to ignore and things will get confusing pretty quickly. To help with this darcs maintains a boring file under _darcs/prefs/boring. This file contains a list of regular expressions to filter out files we are not interested in for version control. We can simply add our Hello file to it and won’t be bothered with it anymore.

$ darcs status
a ./Hello
$ echo Hello >> _darcs/prefs/boring
$ darcs status
No changes!

The boring file is a plain text file so you can simply edit it with your favorite text editor.

If you want darcs to use a different file as your boring file you can use darcs setpref boringfile FILENAME which will create a special kind of change called changepref.

Different types of changes

We have seen a few different types of changes throughout this chapter, maybe it’s time to look at them a little more, so this section summarizes all of the different types of changes that exist. As we have seen before, a patch is made up of changes so far we have met a couple of them.

Revisiting history

We briefly looked at darcs log to take a look at our recorded patches. One of the useful utilities of log is the -v flag which is a short hand for --verbose and it displays the changes that a certain patch represents. That might be a little to much sometimes, think about adding a large new text file to your repository. You almost certainly don’t want to see that entire file in your log output. The -s flag which is a short hand for --summary allows you to just look at a summary of changes, similar to the darcs status output.

$ darcs log -s
patch 0dd46bd7a710d66136d7293b667984bdaffb1509
Author: raichoo@example.com
Date:   Mon Jun 18 12:31:32 CEST 2018
  * display move

    M ./__fish_darcs_prompt.fish +6

patch d3cc32993f6ff5e6b69fd6e1ac5e4e5d9b2f83ec
Author: raichoo@example.com
Date:   Mon Jun 11 19:16:21 CEST 2018
  * initial record

    A ./__fish_darcs_prompt.fish

Another quite useful flag is --last which takes a number n and displays the last n changes. This is useful if you just want to look at the most recent changes.

$ darcs log --last 3
patch eec36aaa61a4fad924657cd86624ba162991860d
Author: raichoo@example.com
Date:   Mon Jun 18 12:32:34 CEST 2018
  * fish: add move color to darcs prompt

patch 745297151266fb6d76634d647084ad37a7b3bb1c
Author: raichoo@example.com
Date:   Thu Jun 14 20:43:08 CEST 2018
  * fish: clean up fish config

patch e791ff48013903112ea42071e27b4e9ab7b86327
Author: raichoo@example.com
Date:   Mon Jun 11 19:40:43 CEST 2018
  * move darcs prompt into plugin

Sometimes you just want to look at certain patches, this is where the name we provide whenever we are recording a new patch comes in handy. log takes a -p that allows us to specify a regular expression and darcs log will pick out only the patches where the regular expression matches the name.

I’m managing my system configuration files with darcs. Let’s say I want to have a summary of the patches that deal with the program dunst (dunst is the notification daemon I’m using to display messages on my desktop).

This is how I would do it.

$ darcs log -p dunst -s
patch 81fac56462c274f8a507f9e5497c22e8d418cae9
Author: raichoo@example.com
Date:   Tue Apr 17 17:48:15 CEST 2018
  * key shortcut to close dunst notifications

    M ./dunst/dunstrc -2 +2

patch 0743c619f5cf0d67fc9d36de96f83752c3c56b12
Author: raichoo@example.com
Date:   Fri Mar 30 18:28:54 CEST 2018
  * add dunst config

    A ./dunst/
    A ./dunst/dunstrc

The patch with the hash 81fac56462c274f8a507f9e5497c22e8d418cae9 looks interesting and I want to drill down further. I can use the -h flag to specify a patch hash. I don’t need to specify the entire hash but only a unique prefix. darcs log will display the first patch where the hash matches the given prefix.

$ darcs log -v -h 81fac
patch 81fac56462c274f8a507f9e5497c22e8d418cae9
Author: raichoo <raichoo@example.com>
Date:   Tue Apr 17 17:48:15 CEST 2018
  * key shortcut to close dunst notifications
    hunk ./dunst/dunstrc 231
    -    close = ctrl+space
    +    close = mod4+space
    hunk ./dunst/dunstrc 234
    -    close_all = ctrl+shift+space
    +    close_all = mod4+shift+space

darcs log is very powerful and I encourage you to look at darcs help log and the darcs manpage.