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.
adddir
darcs
unlike some other version control systems is aware of directories and has its own type of change to track them. This change is created by performingadd
on a directory.$ mkdir directory $ darcs add directory Adding 'directory' $ darcs whatsnew adddir ./directory
addfile
This change we have seen before, but for the sake of completeness let’s have a look at it again. To keep track of a file we can perform
add
on it anddarcs
will create anaddfile
change. Adding a file will notrecord
its content, this is whathunk
changes are for, it will merely informdarcs
of the file’s existence.$ touch file $ darcs add file Adding 'file' $ darcs whatsnew addfile ./file
binary
Binary files can be a bit of a hassle, especially when it comes to tracking how they change throughout the lifetime of the project.
darcs
simply stores them in their entirety by creating abinary
change. Ifdarcs
does not recognize a binary file as such you can inform it by adding its name to_darcs/prefs/binaries
which works exactly like theboring
file we have looked at in the previous section.$ ghc Main.hs $ darcs add Main Adding 'Main' $ darcs whatsnew binary ./Main
changepref
This kind of change is somewhat advanced and allows you to
record
preference changes of your repository and distribute them to others. To create such a change one utilizes thesetpref
command. These changes should be handled with extreme care since they also allow you to specify a shell command for thedarcs test
command. You should never blindly execute commands from strangers, so be a bit careful with these changes. We will talk abouttest
in a later chapter. For now it is sufficient to know that you can change yourboring
file as well as your binaries file with it.$ touch myboring $ darcs setpref boringfile myboring $ darcs whatsnew changepref boringfile myboring
hunk
When working with plain text files you will see this kind of change the most. A
hunk
is used to track line-wise changes in a file. As we have seen in the previous sections ahunk
looks like this and is created by editing a tracked file.$ darcs whatsnew hunk ./Hello.hs 1 -main = putStrLn "Hello World!" +main = putStrLn "Hello Everyone!"
The
hunk
change states the file it is modifying as well as the line number it affects, in this case that would be line 1 in the fileHello.hs
. Line removals are indicated by a-
symbol in front of the line while line additions are indicated by a+
symbol.move
move
changes keep track of file and directory movements alike. Imagine you have a file calledfrom
and want to rename it toto
. This is how you should go about doing that.$ darcs move from to Moved: from to: to $ darcs whatsnew move ./from ./to
Using this kind of change gives
darcs
more context then just removing the file and adding it as a new one. For example, someone wrote a patch that changes the contents of a fileA
and someone else moves fileA
toB
darcs
can commute these changes. That means that fordarcs
is does not matter if you first change the content of the file and thenmove
it, or the other way around.replace
If we want to be a little bit more fine grained than a
hunk
when it comes to text changes we can usereplace
changes. They play a lot better with other changes than the line-wise of ahunk
.$ darcs replace World Everyone Hello.hs M ./Hello.hs r1 $ darcs whatsnew replace ./Hello.hs [A-Za-z_0-9] World Everyone
This type of change gives us a bit more freedom when collaborating with other people. We are going to talk about the benefits of
replace
in chapter 6.rmdir
This type is change unsurprisingly tracks the removal of a directory, it’s created by simply removing a directory.
$ rmdir directory $ darcs whatsnew rmdir ./directory
rmfile
Like
rmdir
thermfile
track a removal but in this case it’s the removal of a file.$ rm file $ darcs whatsnew rmfile ./file
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.