Conflict Management
Conflicts are bound to happen when more than one person is working on something. Two people might end up editing the same portion of text with different results. When you have two people creating changes that change the same part of a project and you pull them into your repository you will and up with a so called conflict. darcs
does not know what change you prefer, so it will inform you that a conflict has occurred and asks you to resolve it.
A working example
I have created a fairly simple piece of code that simply prints out This is a test!
.
$ darcs log -v
patch 7561a52b319ae52ddd1bd321d53cc01fb14fa94c
Author: raichoo@example.com
Date: Sun Jul 1 19:32:26 CEST 2018
* initial record
addfile ./Main.hs
hunk ./Main.hs 1
+main = putStrLn "This is a test!"
Sandra and xanderio both clone my repository and make their own changes to the message that this program is supposed to print out.
xanderio is rather excited about the project and creates the following change.
$ darcs log -v --last 1
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is the best test!"
Sandra has seen better things and thinks that the message should be a bit more modest, so she is creating a change that looks like this.
$ darcs log -v --last 1
patch 1891536411e86f9666219cc1a95f89587da08de3
Author: sandra@example.com
Date: Sun Jul 1 19:52:12 CEST 2018
* change message
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is a quite okay test!"
Obviously both have changed the same line of the project so when I pull in those changes darcs
won’t just decide on its own but rather ask me to do the conflict resolution. That’s a good thing, you don’t want some random program making decisions regarding your project, who knows what it might come up with!
Before we are going to pull in our changes let’s check the status of our working tree. It’ll become clear why I’m doing this, for now just roll along.
$ darcs status
No changes!
Okay, there are no unrecorded changes present so let’s pull in xanderio’s and Sandra’s changes. I can give pull
multiple repositories to pull
from. This makes it quite convenient if you want to pull in changes from your entire team all that once.
$ ls
Main.hs _darcs
$ darcs pull \
https://hub.darcs.net/xanderio/project \
https://hub.darcs.net/sandra/project
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
Shall I pull this patch? (1/2) [ynW...], or ? for more options: y
patch 1891536411e86f9666219cc1a95f89587da08de3
Author: sandra@example.com
Date: Sun Jul 1 19:52:12 CEST 2018
* change message
Shall I pull this patch? (2/2) [ynW...], or ? for more options: y
Do you want to Pull these patches? [Yglqk...], or ? for more options: y
Backing up ./Main.hs(.~0~)
We have conflicts in the following files:
./Main.hs
Finished pulling.
$ ls
Main.hs Main.hs.~0~ _darcs
As we anticipated, we ended up with a conflict in Main.hs
. Also another mysterious file by the name of Main.hs.~0~
appeared in our working tree, we will take a look at that one later. First, let’s take a look at what that our repository looks like.
$ darcs log -s
patch 1891536411e86f9666219cc1a95f89587da08de3
Author: sandra@example.com
Date: Sun Jul 1 19:52:12 CEST 2018
* change message
M! ./Main.hs -1 +1
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
M ./Main.hs -1 +1
patch 7561a52b319ae52ddd1bd321d53cc01fb14fa94c
Author: raichoo@example.com
Date: Sun Jul 1 19:32:26 CEST 2018
* initial record
A ./Main.hs
In the summary of Sandra’s patch we can see an exclamation point. This tells us that there are changes in our repository that conflict with the changes that patch introduces. To see what line actually causes that problem let’s take a look at the verbose output of log
.
$ darcs log -v
patch 1891536411e86f9666219cc1a95f89587da08de3
Author: sandra@example.com
Date: Sun Jul 1 19:52:12 CEST 2018
* change message
conflictor [
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is the best test!"
]
|:
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is a quite okay test!"
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is the best test!"
patch 7561a52b319ae52ddd1bd321d53cc01fb14fa94c
Author: raichoo@example.com
Date: Sun Jul 1 19:32:26 CEST 2018
* initial record
addfile ./Main.hs
hunk ./Main.hs 1
+main = putStrLn "This is a test!"
Here we can see that the conflict is marked with a conflictor which tells us which changes are in conflict with one another. The line that xanderio’s patch changed is in direct conflict with Sandra’s change and therefore that line is marked with a conflictor. But what happened to your Main.hs
?
Conflict Markers
When a conflict occurs darcs
will mark these conflicts in our working tree. Remember that we checked for unrecorded changed in the working tree before pulling in the changes? Let’s check again.
$ darcs status
M ./Main.hs +6
Interesting, darcs
seems to have changed something in our Main.hs
. If we take a look at the file it’s now going to look like this.
$ cat Main.hs
v v v v v v v
main = putStrLn "This is a test!"
=============
main = putStrLn "This is a quite okay test!"
*************
main = putStrLn "This is the best test!"
^ ^ ^ ^ ^ ^ ^
These are so called conflict markers. These markers consist out of multiple parts and there are a different delimiters separating those parts.
v v v v v v v
Marks the beginning of a conflict, it is followed by the initial state of our file, which is its state before the conflict occurred.
=============
Marks the end of the initial state, it is followed by a list of conflicting changes.
*************
This marker separates conflicting changes. Depending on how many changes we have pulled in that conflict with one other there can be multiple changes listed here. Each of them is separated by a marker like this. In our example we just have those two conflicting changes but if we happen to
pull
in from more repositories there might potentially be more.^ ^ ^ ^ ^ ^ ^
Marks the end of a conflict.
darcs
also created a file called Main.hs.~0~
the captures the state of our conflicting file before the we pulled in the changes.
$ cat Main.hs.~0~
main = putStrLn "This is a test!"
You can get rid of the conflict markers be simply issuing darcs revert
, this does not solve your conflict but it takes you back to the state of the file before the conflicting changes were applied. In our situation our Main.hs
would look like this.
$ darcs revert -a
Finished reverting.
$ cat Main.hs
main = putStrLn "This is a test!"
To get our conflict markers back we can always issue mark-conflicts
.
$ darcs mark-conflicts
Marking conflicts in: "Main.hs".
Finished marking conflicts.
$ cat Main.hs
v v v v v v v
main = putStrLn "This is a test!"
=============
main = putStrLn "This is a quite okay test!"
*************
main = putStrLn "This is the best test!"
^ ^ ^ ^ ^ ^ ^
Solving the issue at hand
Okay, now we know what the problem is but what’s the solution? It’s actually pretty simple. We just record a patch that resolves our conflict. The situation above calls a change that somewhat takes the two conflicting patches into account to resolve the issue.
$ echo 'main = putStrLn "This is a good test, Bront!"' > Main.hs
$ darcs record -m 'resolve "change message" conflict'
hunk ./Main.hs 1
-main = putStrLn "This is a test!"
+main = putStrLn "This is a good test, Bront!"
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 'resolve "change message" conflict'
Never skip a beat, --skip-conflicts
!
As you can see, conflicts can cause quite a lot of work, but when we pull in work from others not every patch contained in the remote repository might cause a conflict. So how about just pulling in those that don’t cause any trouble for now and continue working with those before tackling the task of resolving any issues? Sounds nice and darcs pull
offers a flag that does exactly that, --skip-conflicts
.
Let’s pretend that we didn’t pull
in any changes yet and now we are carefully pulling them in from our other example repositories.
$ darcs pull https://hub.darcs.net/xanderio/project
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
Shall I pull this patch? (1/1) [ynW...], or ? for more options: y
Do you want to Pull these patches? [Yglqk...], or ? for more options: y
Finished pulling.
$ darcs pull --skip-conflicts https://hub.darcs.net/sandra/project
Skipping some patches which would cause conflicts.
No remote patches to pull in!
$ darcs log
patch b769c1ae2bc57d0b8254deff9bb83c9c1000b93e
Author: xanderio@example.com
Date: Sun Jul 1 19:50:24 CEST 2018
* change message
patch 7561a52b319ae52ddd1bd321d53cc01fb14fa94c
Author: raichoo@example.com
Date: Sun Jul 1 19:32:26 CEST 2018
* initial record
As you can see we didn’t pull in Sandra’s patch that would have conflicted with xanderio’s. On the other hand if Sandra would have had other changes that would not conflict we would have pulled them in only leaving out the culprit. This is one of the nice and powerful properties of patch based version control systems like darcs
.
Avoiding conflicts with replace
In chapter 3 we were introduced to the replace
change and until now it got relatively little attention. I would like to show you a very nice application of this kind of change that allows us to reduce the attack surface for conflicts.
As a simple example I’m going write a simple piece of Haskell code that only contains a function called foo
.
$ cat Fun.hs
foo :: Int -> Int
foo x = x + 1
I’m working on this piece of code with my friends alexej and bodems. They both have a copy of my repository and apply the following changes to it.
alexej wants the foo
function to be a little more generic so they change the type signature of the function to reflect that.
$ darcs log --last 1 -v
patch 9ca832bb0187d46d37e129b05837af39275246f4
Author: alexej@example.com
Date: Tue Jul 3 11:41:22 CEST 2018
* make foo more generic
hunk ./Fun.hs 1
-foo :: Int -> Int
+foo :: Num a => a -> a
By doing this alexej introduced a hunk
that replaces the first line of the file with a new one that features the more generic type signature.
bodems thinks that the name foo
is just not descriptive enough. So they change it by introducing the following change.
$ darcs log --last 1 -v
patch 592af55bbf1395376c745fffc47da598e3233149
Author: bodems@example.com
Date: Tue Jul 3 11:41:04 CEST 2018
* give foo an appropriate name
hunk ./Fun.hs 1
-foo :: Int -> Int
-foo x = x + 1
+inc :: Int -> Int
+inc x = x + 1
Once again darcs
uses a hunk change to reflect that change and swaps out entire lines just to change the name.
Now I come along to pull in both of their changes. I’m careful and therefore using the --dry-run
flag of pull
to take a brief look at their changes first.
$ darcs pull --dry-run -v \
https://hub.darcs.net/bodems/project \
https://hub.darcs.net/alexej/project
They have the following patches to pull:
patch 592af55bbf1395376c745fffc47da598e3233149
Author: bodems@example.com
Date: Tue Jul 3 11:41:04 CEST 2018
* give foo an appropriate name
patch 9ca832bb0187d46d37e129b05837af39275246f4
Author: alexej@example.com
Date: Tue Jul 3 11:41:22 CEST 2018
* make foo more generic
Would pull the following changes:
patch 592af55bbf1395376c745fffc47da598e3233149
Author: bodems@example.com
Date: Tue Jul 3 11:41:04 CEST 2018
* give foo an appropriate name
hunk ./Fun.hs 1
-foo :: Int -> Int
-foo x = x + 1
+inc :: Int -> Int
+inc x = x + 1
patch 9ca832bb0187d46d37e129b05837af39275246f4
Author: alexej@example.com
Date: Tue Jul 3 11:41:22 CEST 2018
* make foo more generic
conflictor [
hunk ./Fun.hs 1
-foo :: Int -> Int
-foo x = x + 1
+inc :: Int -> Int
+inc x = x + 1
]
:
hunk ./Fun.hs 1
-foo :: Int -> Int
+foo :: Num a => a -> a
Making no changes: this is a dry run.
Oh snap! We have a conflict! Not really surprising when we consider that both changes are changing the sames lines of code.
Now thankfully Sandra comes along, being very good at solving conflicts they use replace
instead of a plain hunk
.
$ darcs replace foo inc Fun.hs
$ darcs whatsnew
replace ./Fun.hs [A-Za-z_0-9] foo inc
$ darcs record -a -m 'give foo an appropriate name'
Finished recording patch 'give foo an appropriate name'
No when I pull in this patch instead of the one bodems made this is going to happen.
$ darcs pull --dry-run -v \
https://hub.darcs.net/sandra/project \
https://hub.darcs.net/alexej/project
They have the following patches to pull:
patch 9ea86844a018aeb9c7cf96657d0466960787b359
Author: sandra@example.com
Date: Tue Jul 3 12:00:09 CEST 2018
* give foo an appropriate name
patch 9ca832bb0187d46d37e129b05837af39275246f4
Author: alexej@example.com
Date: Tue Jul 3 11:41:22 CEST 2018
* make foo more generic
Would pull the following changes:
patch 9ea86844a018aeb9c7cf96657d0466960787b359
Author: sandra@example.com
Date: Tue Jul 3 12:00:09 CEST 2018
* give foo an appropriate name
replace ./Fun.hs [A-Za-z_0-9] foo inc
patch 9ca832bb0187d46d37e129b05837af39275246f4
Author: alexej@example.com
Date: Tue Jul 3 11:41:22 CEST 2018
* make foo more generic
hunk ./Fun.hs 1
-inc :: Int -> Int
+inc :: Num a => a -> a
Making no changes: this is a dry run.
Voilà! No conflicts at all! darcs
could figure out that these changes do not conflict because of the properties of the replace
change. If I’d pull
in those patches my Fun.hs
would have changed based on both of my collaborator’s changes.
$ cat Fun.hs
inc :: Num a => a -> a
inc x = x + 1
replace
changes are quite unique to darcs
. I have yet to find another version control system that features this. On the other hand they do not make a lot of sense in snapshot based version control systems anyway, whereas in a patch based system they do.