~jan0sch/darcs-book
~jan0sch/darcs-book/en/03-working-locally.md
~jan0sch/darcs-book/en/03-working-locally.md
title: Working locally prev: chapter02 next: chapter04 toclink: yes ---
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.
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.
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!
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
.
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.
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.
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.
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.
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.
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
.
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 performing
add
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 and
darcs
will create an addfile
change. Adding a file will not record
its
content, this is what hunk
changes are for, it will merely inform darcs
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 a binary
change. If darcs
does not
recognize a binary file as such you can inform it by adding its name to
_darcs/prefs/binaries
which works exactly like the boring
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 the setpref
command. These changes should be handled
with extreme care since they also allow you to specify a shell command for the
darcs test
command. You should never blindly execute commands from
strangers, so be a bit careful with these changes. We will talk about test
in a later chapter. For now it is sufficient to know that you can change your
boring
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 a hunk
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 file Hello.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 called from
and want to rename it to to
. 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 file A
and someone else moves file A
to B
darcs
can commute these changes. That means that for darcs
is does not
matter if you first change the content of the file and then move
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 use replace
changes. They play a lot better with other
changes than the line-wise of a hunk
.
$ 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
the rmfile
track a removal but in this case it's the removal of
a file.
$ rm file
$ darcs whatsnew
rmfile ./file
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.