• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GothenburgBitFactory / taskwarrior / 14494393478

16 Apr 2025 01:49PM UTC coverage: 85.255% (+0.1%) from 85.151%
14494393478

push

github

web-flow
change implementation from has (#3849)

Change from get(uuid, task) to get_task_data to always have a full uuid
match and to improve read performance for the diagnostics command.

See #3848 for details.

0 of 1 new or added line in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

19560 of 22943 relevant lines covered (85.25%)

23390.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.87
/src/TDB2.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez.
4
//
5
// Permission is hereby granted, free of charge, to any person obtaining a copy
6
// of this software and associated documentation files (the "Software"), to deal
7
// in the Software without restriction, including without limitation the rights
8
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
// copies of the Software, and to permit persons to whom the Software is
10
// furnished to do so, subject to the following conditions:
11
//
12
// The above copyright notice and this permission notice shall be included
13
// in all copies or substantial portions of the Software.
14
//
15
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
// SOFTWARE.
22
//
23
// https://www.opensource.org/licenses/mit-license.php
24
//
25
////////////////////////////////////////////////////////////////////////////////
26

27
#include <cmake.h>
28
// cmake.h include header must come first
29

30
#include <Color.h>
31
#include <Context.h>
32
#include <Datetime.h>
33
#include <TDB2.h>
34
#include <Table.h>
35
#include <format.h>
36
#include <shared.h>
37
#include <stdlib.h>
38
#include <util.h>
39

40
#include <algorithm>
41
#include <unordered_set>
42
#include <vector>
43

44
bool TDB2::debug_mode = false;
45
static void dependency_scan(std::vector<Task>&);
46

47
////////////////////////////////////////////////////////////////////////////////
48
void TDB2::open_replica(const std::string& location, bool create_if_missing, bool read_write) {
4,557✔
49
  _replica = tc::new_replica_on_disk(location, create_if_missing, read_write);
4,558✔
50
}
4,556✔
51

52
////////////////////////////////////////////////////////////////////////////////
53
void TDB2::open_replica_in_memory() { _replica = tc::new_replica_in_memory(); }
1✔
54

55
////////////////////////////////////////////////////////////////////////////////
56
// Add the new task to the replica.
57
void TDB2::add(Task& task) {
2,993✔
58
  // Ensure the task is consistent, and provide defaults if necessary.
59
  // bool argument to validate() is "applyDefault", to apply default values for
60
  // properties not otherwise given.
61
  task.validate(true);
2,993✔
62

63
  rust::Vec<tc::Operation> ops;
2,993✔
64
  maybe_add_undo_point(ops);
2,993✔
65

66
  auto uuid = task.get("uuid");
2,993✔
67
  changes[uuid] = task;
2,993✔
68
  tc::Uuid tcuuid = tc::uuid_from_string(uuid);
2,993✔
69

70
  // run hooks for this new task
71
  Context::getContext().hooks.onAdd(task);
2,993✔
72

73
  auto taskdata = tc::create_task(tcuuid, ops);
2,986✔
74

75
  // add the task attributes
76
  for (auto& attr : task.all()) {
19,839✔
77
    // TaskChampion does not store uuid or id in the task data
78
    if (attr == "uuid" || attr == "id") {
16,853✔
79
      continue;
2,986✔
80
    }
81

82
    taskdata->update(attr, task.get(attr), ops);
13,867✔
83
  }
2,986✔
84
  replica()->commit_operations(std::move(ops));
2,986✔
85

86
  invalidate_cached_info();
2,986✔
87

88
  // get the ID that was assigned to this task
89
  auto id = working_set()->by_uuid(tcuuid);
2,986✔
90
  if (id > 0) {
2,986✔
91
    task.id = id;
2,866✔
92
  }
93
}
3,000✔
94

95
////////////////////////////////////////////////////////////////////////////////
96
// Modify the task in storage to match the given task.
97
//
98
// Note that there are a few race conditions to consider here.  Taskwarrior
99
// loads the enitre task into memory and this method then essentially writes
100
// the entire task back to the database. So, if the task in the database
101
// changes between loading the task and this method being called, this method
102
// will "revert" those changes. In practice this would only occur when multiple
103
// `task` invocatoins run at the same time and try to modify the same task.
104
//
105
// There is also the possibility that another task process has deleted the task
106
// from the database between the time this process loaded the tsak and called
107
// this method. In this case, this method throws an error that will make sense
108
// to the user. This is especially unlikely since tasks are only deleted when
109
// they have been unmodified for a long time.
110
void TDB2::modify(Task& task) {
583✔
111
  // All locally modified tasks are timestamped, implicitly overwriting any
112
  // changes the user or hooks tried to apply to the "modified" attribute.
113
  task.setAsNow("modified");
583✔
114
  task.validate(false);
583✔
115
  auto uuid = task.get("uuid");
583✔
116

117
  rust::Vec<tc::Operation> ops;
583✔
118
  maybe_add_undo_point(ops);
583✔
119

120
  changes[uuid] = task;
583✔
121

122
  // invoke the hook and allow it to modify the task before updating
123
  Task original;
583✔
124
  get(uuid, original);
583✔
125
  Context::getContext().hooks.onModify(original, task);
583✔
126

127
  tc::Uuid tcuuid = tc::uuid_from_string(uuid);
577✔
128
  auto maybe_tctask = replica()->get_task_data(tcuuid);
577✔
129
  if (maybe_tctask.is_none()) {
577✔
130
    throw std::string("task no longer exists");
×
131
  }
132
  auto tctask = maybe_tctask.take();
577✔
133

134
  // Perform the necessary `update` operations to set all keys in `tctask`
135
  // equal to those in `task`.
136
  std::unordered_set<std::string> seen;
577✔
137
  for (auto k : task.all()) {
5,004✔
138
    // ignore task keys that aren't stored
139
    if (k == "uuid") {
4,427✔
140
      continue;
577✔
141
    }
142
    seen.insert(k);
3,850✔
143
    bool update = false;
3,850✔
144
    auto v_new = task.get(k);
3,850✔
145
    std::string v_tctask;
3,850✔
146
    if (tctask->get(k, v_tctask)) {
3,850✔
147
      update = v_tctask != v_new;
3,285✔
148
    } else {
149
      // tctask does not contain k, so update it
150
      update = true;
565✔
151
    }
152
    if (update) {
3,850✔
153
      // An empty string indicates the value should be removed.
154
      if (v_new == "") {
982✔
155
        tctask->update_remove(k, ops);
4✔
156
      } else {
157
        tctask->update(k, v_new, ops);
978✔
158
      }
159
    }
160
  }
5,004✔
161

162
  // we've now added and updated properties; but must find any deleted properties
163
  for (auto k : tctask->properties()) {
4,487✔
164
    auto kstr = static_cast<std::string>(k);
3,910✔
165
    if (seen.find(kstr) == seen.end()) {
3,910✔
166
      tctask->update_remove(kstr, ops);
64✔
167
    }
168
  }
4,487✔
169

170
  replica()->commit_operations(std::move(ops));
577✔
171

172
  invalidate_cached_info();
577✔
173
}
595✔
174

175
////////////////////////////////////////////////////////////////////////////////
176
void TDB2::purge(Task& task) {
6✔
177
  auto uuid = tc::uuid_from_string(task.get("uuid"));
6✔
178
  rust::Vec<tc::Operation> ops;
6✔
179
  auto maybe_tctask = replica()->get_task_data(uuid);
6✔
180
  if (maybe_tctask.is_some()) {
6✔
181
    auto tctask = maybe_tctask.take();
6✔
182
    tctask->delete_task(ops);
6✔
183
    replica()->commit_operations(std::move(ops));
6✔
184
  }
6✔
185

186
  invalidate_cached_info();
6✔
187
}
6✔
188

189
////////////////////////////////////////////////////////////////////////////////
190
rust::Box<tc::Replica>& TDB2::replica() {
16,640✔
191
  // One of the open_replica_ methods must be called before this one.
192
  assert(_replica);
16,640✔
193
  return _replica.value();
16,640✔
194
}
195

196
////////////////////////////////////////////////////////////////////////////////
197
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
750,733✔
198
  if (!_working_set.has_value()) {
750,733✔
199
    _working_set = replica()->working_set();
5,098✔
200
  }
201
  return _working_set.value();
750,733✔
202
}
203

204
////////////////////////////////////////////////////////////////////////////////
205
void TDB2::maybe_add_undo_point(rust::Vec<tc::Operation>& ops) {
3,576✔
206
  // Only add an UndoPoint if there are not yet any changes.
207
  if (changes.size() == 0) {
3,576✔
208
    tc::add_undo_point(ops);
1,786✔
209
  }
210
}
3,576✔
211

212
////////////////////////////////////////////////////////////////////////////////
213
void TDB2::get_changes(std::vector<Task>& changes) {
5✔
214
  std::map<std::string, Task>& changes_map = this->changes;
5✔
215
  changes.clear();
5✔
216
  std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
5✔
217
                 [](const auto& kv) { return kv.second; });
1✔
218
}
5✔
219

220
////////////////////////////////////////////////////////////////////////////////
221
void TDB2::gc() {
925✔
222
  Timer timer;
925✔
223

224
  // Allowed as an override, but not recommended.
225
  if (Context::getContext().config.getBoolean("gc")) {
2,775✔
226
    replica()->rebuild_working_set(true);
920✔
227
  }
228

229
  Context::getContext().time_gc_us += timer.total_us();
925✔
230
}
925✔
231

232
////////////////////////////////////////////////////////////////////////////////
233
void TDB2::expire_tasks() { replica()->expire_tasks(); }
1✔
234

235
////////////////////////////////////////////////////////////////////////////////
236
// Latest ID is that of the last pending task.
237
int TDB2::latest_id() {
179✔
238
  auto& ws = working_set();
179✔
239
  return (int)ws->largest_index();
179✔
240
}
241

242
////////////////////////////////////////////////////////////////////////////////
243
const std::vector<Task> TDB2::all_tasks() {
441✔
244
  Timer timer;
441✔
245
  auto all_tctasks = replica()->all_task_data();
441✔
246
  std::vector<Task> all;
441✔
247
  for (auto& maybe_tctask : all_tctasks) {
1,633✔
248
    auto tctask = maybe_tctask.take();
1,192✔
249
    all.push_back(Task(std::move(tctask)));
1,192✔
250
  }
1,192✔
251

252
  dependency_scan(all);
441✔
253

254
  Context::getContext().time_load_us += timer.total_us();
441✔
255
  return all;
882✔
256
}
441✔
257

258
////////////////////////////////////////////////////////////////////////////////
259
const std::vector<Task> TDB2::pending_tasks() {
6,351✔
260
  if (!_pending_tasks) {
6,351✔
261
    Timer timer;
3,810✔
262

263
    auto pending_tctasks = replica()->pending_task_data();
3,810✔
264
    std::vector<Task> result;
3,810✔
265
    for (auto& maybe_tctask : pending_tctasks) {
749,149✔
266
      auto tctask = maybe_tctask.take();
745,339✔
267
      result.push_back(Task(std::move(tctask)));
745,339✔
268
    }
745,339✔
269

270
    dependency_scan(result);
3,810✔
271

272
    Context::getContext().time_load_us += timer.total_us();
3,810✔
273
    _pending_tasks = result;
3,810✔
274
  }
3,810✔
275

276
  return *_pending_tasks;
6,351✔
277
}
278

279
////////////////////////////////////////////////////////////////////////////////
280
const std::vector<Task> TDB2::completed_tasks() {
319✔
281
  if (!_completed_tasks) {
319✔
282
    auto all_tctasks = replica()->all_task_data();
319✔
283
    auto& ws = working_set();
319✔
284

285
    std::vector<Task> result;
319✔
286
    for (auto& maybe_tctask : all_tctasks) {
2,205✔
287
      auto tctask = maybe_tctask.take();
1,886✔
288
      // if this task is _not_ in the working set, return it.
289
      if (ws->by_uuid(tctask->get_uuid()) == 0) {
1,886✔
290
        result.push_back(Task(std::move(tctask)));
238✔
291
      }
292
    }
1,886✔
293
    _completed_tasks = result;
319✔
294
  }
319✔
295
  return *_completed_tasks;
319✔
296
}
297

298
////////////////////////////////////////////////////////////////////////////////
299
void TDB2::invalidate_cached_info() {
3,569✔
300
  _pending_tasks = std::nullopt;
3,569✔
301
  _completed_tasks = std::nullopt;
3,569✔
302
  _working_set = std::nullopt;
3,569✔
303
}
3,569✔
304

305
////////////////////////////////////////////////////////////////////////////////
306
// Locate task by ID, wherever it is.
307
bool TDB2::get(int id, Task& task) {
308✔
308
  auto& ws = working_set();
308✔
309
  const auto tcuuid = ws->by_index(id);
308✔
310
  if (!tcuuid.is_nil()) {
308✔
311
    std::string uuid = static_cast<std::string>(tcuuid.to_string());
296✔
312
    // Load all pending tasks in order to get dependency data, and in particular
313
    // `task.is_blocking` and `task.is_blocked`, set correctly.
314
    std::vector<Task> pending = pending_tasks();
296✔
315
    for (auto& pending_task : pending) {
1,537✔
316
      if (pending_task.get("uuid") == uuid) {
3,074✔
317
        task = pending_task;
296✔
318
        return true;
296✔
319
      }
320
    }
321
  }
592✔
322

323
  return false;
12✔
324
}
325

326
////////////////////////////////////////////////////////////////////////////////
327
// Locate task by UUID, including by partial ID, wherever it is.
328
bool TDB2::get(const std::string& uuid, Task& task) {
2,293✔
329
  // Load all pending tasks in order to get dependency data, and in particular
330
  // `task.is_blocking` and `task.is_blocked`, set correctly.
331
  std::vector<Task> pending = pending_tasks();
2,293✔
332

333
  // try by raw uuid, if the length is right
334
  for (auto& pending_task : pending) {
735,108✔
335
    if (closeEnough(pending_task.get("uuid"), uuid, uuid.length())) {
2,200,470✔
336
      task = pending_task;
675✔
337
      return true;
675✔
338
    }
339
  }
340

341
  // Nothing to do but iterate over all tasks and check whether it's closeEnough.
342
  for (auto& maybe_tctask : replica()->all_task_data()) {
733,515✔
343
    auto tctask = maybe_tctask.take();
731,925✔
344
    auto tctask_uuid = static_cast<std::string>(tctask->get_uuid().to_string());
731,925✔
345
    if (closeEnough(tctask_uuid, uuid, uuid.length())) {
731,925✔
346
      task = Task{std::move(tctask)};
28✔
347
      return true;
28✔
348
    }
349
  }
733,571✔
350

351
  return false;
1,590✔
352
}
2,293✔
353

354
////////////////////////////////////////////////////////////////////////////////
355
// Locate task by UUID, wherever it is.
356
bool TDB2::has(const std::string& uuid) {
×
NEW
357
  return replica()->get_task_data(tc::uuid_from_string(uuid)).is_some();
×
358
}
359

360
////////////////////////////////////////////////////////////////////////////////
361
const std::vector<Task> TDB2::siblings(Task& task) {
8✔
362
  std::vector<Task> results;
8✔
363
  if (task.has("parent")) {
16✔
364
    std::string parent = task.get("parent");
8✔
365

366
    for (auto& i : this->pending_tasks()) {
35✔
367
      // Do not include self in results.
368
      if (i.id != task.id) {
27✔
369
        // Do not include completed or deleted tasks.
370
        if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
19✔
371
          // If task has the same parent, it is a sibling.
372
          if (i.has("parent") && i.get("parent") == parent) {
77✔
373
            results.push_back(i);
10✔
374
          }
375
        }
376
      }
377
    }
8✔
378
  }
8✔
379

380
  return results;
8✔
381
}
×
382

383
////////////////////////////////////////////////////////////////////////////////
384
const std::vector<Task> TDB2::children(Task& parent) {
71✔
385
  // scan _pending_ tasks for those with `parent` equal to this task
386
  std::vector<Task> results;
71✔
387
  std::string this_uuid = parent.get("uuid");
71✔
388

389
  auto& ws = working_set();
71✔
390
  size_t end_idx = ws->largest_index();
71✔
391

392
  for (size_t i = 0; i <= end_idx; i++) {
387✔
393
    auto uuid = ws->by_index(i);
316✔
394
    if (uuid.is_nil()) {
316✔
395
      continue;
302✔
396
    }
397

398
    // skip self-references
399
    if (uuid.to_string() == this_uuid) {
245✔
400
      continue;
70✔
401
    }
402

403
    auto task_opt = replica()->get_task_data(uuid);
175✔
404
    if (task_opt.is_none()) {
175✔
405
      continue;
×
406
    }
407
    auto task = task_opt.take();
175✔
408

409
    std::string parent_uuid;
175✔
410
    if (!task->get("parent", parent_uuid)) {
525✔
411
      continue;
161✔
412
    }
413

414
    if (parent_uuid == this_uuid) {
14✔
415
      results.push_back(Task(std::move(task)));
14✔
416
    }
417
  }
336✔
418
  return results;
142✔
419
}
71✔
420

421
////////////////////////////////////////////////////////////////////////////////
422
std::string TDB2::uuid(int id) {
59✔
423
  auto& ws = working_set();
59✔
424
  auto uuid = ws->by_index(id);
59✔
425
  if (uuid.is_nil()) {
59✔
426
    return "";
2✔
427
  }
428
  return static_cast<std::string>(uuid.to_string());
58✔
429
}
430

431
////////////////////////////////////////////////////////////////////////////////
432
int TDB2::id(const std::string& uuid) {
746,811✔
433
  auto& ws = working_set();
746,811✔
434
  return ws->by_uuid(tc::uuid_from_string(uuid));
746,811✔
435
}
436

437
////////////////////////////////////////////////////////////////////////////////
438
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
5✔
439

440
////////////////////////////////////////////////////////////////////////////////
441
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
3✔
442

443
////////////////////////////////////////////////////////////////////////////////
444
// For any task that has depenencies, follow the chain of dependencies until the
445
// end.  Along the way, update the Task::is_blocked and Task::is_blocking data
446
// cache.
447
static void dependency_scan(std::vector<Task>& tasks) {
4,251✔
448
  for (auto& left : tasks) {
750,782✔
449
    for (auto& dep : left.getDependencyUUIDs()) {
746,890✔
450
      for (auto& right : tasks) {
1,471✔
451
        if (right.get("uuid") == dep) {
2,930✔
452
          // GC hasn't run yet, check both tasks for their current status
453
          Task::status lstatus = left.getStatus();
353✔
454
          Task::status rstatus = right.getStatus();
353✔
455
          if (lstatus != Task::completed && lstatus != Task::deleted &&
353✔
456
              rstatus != Task::completed && rstatus != Task::deleted) {
330✔
457
            left.is_blocked = true;
321✔
458
            right.is_blocking = true;
321✔
459
          }
460

461
          // Only want to break out of the "right" loop.
462
          break;
353✔
463
        }
464
      }
465
    }
746,531✔
466
  }
467
}
4,251✔
468

469
////////////////////////////////////////////////////////////////////////////////
470
// vim: ts=2 et sw=2
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc