-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose operations from TaskChampion #373
Comments
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
This support will require access to all of the operations ever performed on a task, which is not currently exposed by TaskChampion (but see #2928)
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
TaskChampion does not make the necessary information available to accomplish this, but see #2928.
@masaeedu is considering working on this, so I'll un-assign from myself. |
Status update: #372 has handled making operations a first-class thing in TaskChampion (not just for undo). What remains for this issue is:
Then Taskwarrior can read those operations and construct a journal in the |
Progress / To-Do:
Think carefully about:
|
OK, that work is done. However, I just realized that this will only store local operations, not those that arrived via sync. So, a little more work is required! |
Also, UndoPoints are never deleted (since they do not correspond to a task). They should probably be deleted when the sync is complete. |
It's not obvious how to represent operations performed on a task in the presence of sync. Recall this diagram from taskchampion/taskchampion/src/taskdb/sync.rs Lines 152 to 162 in 2e29f16
To faithfully reproduce the set of operations on a task, we need to choose a path through that diagram. The operations considered in the sync process are SyncOp, which means that unlike So, I think the task history will follow the left-most side of the diagram, meaning that local operations will always precede remote operations. One consequence is that operations may not be in chronological order. Imagine I sync daily at midnight, and I make a change on my home machine at 9am, and a change on my work machine at 10am. When those sync together at midnight, the local change will appear first. So on the home machine I will see [9am change, 10am change], while on the work machine I will see [10am change, 9am change]. In fact, if those two changes interfere with one another (say, home sets the status to taskchampion/taskchampion/src/server/op.rs Lines 104 to 116 in 2e29f16
I think this is OK, and I don't think OT can give us a better solution, other than not trying to (ab)use the ops from OT as history. That is, an alternative is to add a new @ryneeverett what do you think? |
It seems that there are two behaviors you're not completely satisfied with:
It seems to me that these issues could potentially be treated separately:
At the end of the day though, I agree that the behavior issues you're describing are OK if they're not reasonable to solve. I'm not sure anyone expects |
Good summary, thanks! I'm not so worried about (1) - sorting would work, as long as it's understood that the Adding an operation to the operations table without applying it would violate the invariant that the replica state is the result of all operations. However, maybe we could add an operation in such a way that the invariant was always preserved? Thinking that through..
State On the home replica, when performing the sync, On the work replica, What if the transpose method could return two operations, to replace both the local and remote operation? Then on the home replica, it would return That involves "editing" history a little bit, and I'm a little worried that it means the remote replica no longer went through state In the interim, though, I will start making PRs for the earlier part of this work. |
I'm assuming "the transpose method" refers to
I'm not sure if I'm restating what you're saying or missing the point. |
Yes, that's the method I mean. It's a conflict in the sense that the two replicas have diverged, and the effects of one of the changes will be removed. The word is probably not too important. What you're suggesting is what I want to do, but the tricky thing is that the task db is in either state b (home) or state c (office) when the transpose method runs, meaning that the |
OK, after some scratch-pad diamond diagrams, I think I have a workable solution that is not much different from the current solution. The transform method will, when a conflict occurs, return the "winning" operation for both The sync process can then keep a list of these "losing" operations for each side (local and remote). Locally, it would append that to the set of operations in the DB before applying the transformed operations from the server. Those transformed operations from the server will be winning operations, so by the invariant they will overwrite the effects of the losing operations. The sync process would prepare a remote version containing the "losing" local operations followed by the transformed local operations. Again, the invariant ensures that the result is the same as if the losing operations were not included at all. I won't draw out the whole diamond, but the following situation demonstrates the results:
In the existing implementation, the transformed remote operations would be [modified=3 at 11am] and the transformed local operations would be [status=C at 10am, status=P at noon]. In the new implementation:
Both locally and remote, we end up with modified=3 and status=P, so the replica invariant is upheld. The only downside is, operations are repeated. In fact, sometimes a local operation ends up in the losing remote operations list (here, status=C at 10am) when it wins and then loses a conflict. I think all of this is manageable -- the display in Thinking about compatibility with 0.7.0, the only remotely-visible change here is a bunch of "losing" operations prepended to each version, with the invariant that another operation in the same version makes those losing operations irrelevant. So, no problem for a 0.7.0 replica. And if a new implementation gets a version generated by a 0.7.0 replica, it will just be missing the "losing" operations, so the ultimate task state will be correct, just with some missing historical operations -- which is about the best we can ask for. I was concerned earlier about getting the |
Thinking a little about the duplication of operations, mentioned in #449 -- that would only happen on conflicts, and those are unusual, so even with arbitrarily-large operation values, I don't think this would lead to significantly more space used within a replica. Currently, if a local and remote Update operation conflict, and the remote operation "wins", then nothing is sent to the server. With the suggested change, the losing operation and the winning operation would both be sent (the losing operation so other replicas are aware of it, and the winning operation so the final task state is correct). So, there is a good bit more data sent to the server, but at least half of that (the "losing" operation) is precisely what's required to get the same task history everywhere. |
I think I need to finish the above-discussed functionality before landing the remaining patches, just to keep |
I've called this the "WINNING INVARIANT":
However, given operations Create(uuid) and an Update(uuid, prop, value), neither "winner" would satisfy that invariant. Admittedly, a Update informally implies that the task had already been created when it was applied, but the operational transform needs to be able to deal with any operations in any state, so that informal implication feels like the kind of "cheat" that creates strange bugs years later. I need to think about this some more. It's possible this whole idea doesn't work :( |
Maybe the trick is to redefine how the operations apply so that every combination can satisfy the WINNING INVARIANT. I think the critical change would be that an Update should create the task if it does not exist. Then Update can win over both Create and Delete.
Here are the WINNING INVARIANT statements for each conflict:
This sort of suggests that we never needed the |
Preliminary work implementing this suggests it's going to work! |
Background
Every change to a task is represented internally as an "Operation"
https://github.com/GothenburgBitFactory/taskwarrior/blob/41992d484909bd865bc252e99e588c5c3f37c71a/taskchampion/taskchampion/src/storage/op.rs#L10-L38
This is how we accomplish
task sync
-- different replicas share the set of operations they have applied, and resolve any conflicts in a reasonable, consistent way.Note that a single command-line invocation may produce several operations. For example
would create four
Update
operations: one to set thestart
property to the current time, one to add thetag_inprogress
property, one to set thepriority
property toM
and one to update themodified
property to the current time.There are also
UndoPoint
operations, which do not actually change anything but act as a signpost for how many operations to revert when runningtask undo
-- that command basically reverts operations until it reaches anUndoPoint
. TheUndoPoint
operations are not sent to the server.Once operations are successfully sent to the server, they are no longer stored locally. This avoids the local task database growing forever.
Currently, none of this is visible in Taskchampion's public API. This bug would change that.
Motivation
We'll need this for a few reasons:
The
task undo
command previously showed a "diff" of what it would change, and asked the user for permission. This is not possible with the current Taskchampion API (see GothenburgBitFactory/taskwarrior@4b814bc). But if there was a way to query Taskchampion to say "hey, what are the operations back to the latestUndoPoint
?" then it could generate a diff view from that.The
task info
command has historically used the undo information to show the history for a particular task. This operated by scanning the entire undo history for records matching that task's uuid, and formatting those nicely for display. Again, that's currently impossible (support was removed in GothenburgBitFactory/taskwarrior#3060). If there was a way to query Taskchampion to say "hey, what operations have happened for this task?" then we could bring back that functionality.Looking forward, one of the things we've considered in #372 is to allow a user of the Taskchampion API to provide a list of operations to apply, instead of calling methods like
task.start(..)
ortask.set_description(..)
. A different possibility we've considered is that a user of the Taskchampion API could call methods liketask.start(..)
ortask.set_description(..)
and build up a list of operations, then "commit" those all at once. This would allow thetask
command to confirm changes with the user before committing them, for example. All of this paragraph is out of scope for this issue, but should be kept in mind when designing the API.Details
Off the top of my head, I think this can be broken into two parts:
Operations for Undo
taskchampion-lib
task undo
Operations for Info
all of the above, and..
taskchampion-lib
task info
when thejournal.info
config is enabled.The text was updated successfully, but these errors were encountered: