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

GothenburgBitFactory / taskwarrior / 10336191094

11 Aug 2024 02:06AM UTC coverage: 84.973% (+0.2%) from 84.733%
10336191094

push

github

web-flow
Use TaskChampion 0.7.0, now via cxx instead of hand-rolled FFI (#3588)

TC 0.7.0 introduces a new `TaskData` type that maps to Taskwarrior's
`Task` type more cleanly. It also introduces the idea of gathering lists
of operations and "committing" them to a replica.

A consequence of this change is that TaskChampion no longer
automatically maintains dependency information, so Taskwarrior must do
so, with its `TDB2::dependency_sync` method. This method does a very
similar thing to what TaskChampion had been doing, so this is a shift of
responsibility but not a major performance difference.

Cxx is .. not great. It is missing a lot of useful things that make a
general-purpose bridge impractical:

 - no support for trait objects
 - no support for `Option<T>` (https://github.com/dtolnay/cxx/issues/87)
 - no support for `Vec<Box<..>>`

As a result, some creativity is required in writing the bridge, for
example returning a `Vec<OptionTaskData>` from `all_task_data` to allow
individual `TaskData` values to be "taken" from the vector.

That said, Cxx is the current state-of-the-art, and does a good job of
ensuring memory safety, at the cost of some slightly awkward APIs.

Subsequent work can remove the "TDB2" layer and allow commands and other
parts of Taskwarrior to interface directly with the `Replica`.

255 of 279 new or added lines in 7 files covered. (91.4%)

7 existing lines in 4 files now uncovered.

19011 of 22373 relevant lines covered (84.97%)

23094.9 hits per line

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

89.42
/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 <main.h>
37
#include <shared.h>
38
#include <signal.h>
39
#include <stdlib.h>
40
#include <util.h>
41

42
#include <algorithm>
43
#include <iostream>
44
#include <list>
45
#include <sstream>
46
#include <unordered_set>
47
#include <vector>
48

49
bool TDB2::debug_mode = false;
50
static void dependency_scan(std::vector<Task>&);
51

52
////////////////////////////////////////////////////////////////////////////////
53
void TDB2::open_replica(const std::string& location, bool create_if_missing) {
4,497✔
54
  _replica = tc::new_replica_on_disk(location, create_if_missing);
4,497✔
55
}
4,496✔
56

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

65
  rust::Vec<tc::Operation> ops;
2,984✔
66
  maybe_add_undo_point(ops);
2,984✔
67

68
  auto uuid = task.get("uuid");
5,968✔
69
  changes[uuid] = task;
2,984✔
70
  tc::Uuid tcuuid = tc::uuid_from_string(uuid);
2,984✔
71

72
  // run hooks for this new task
73
  Context::getContext().hooks.onAdd(task);
2,984✔
74

75
  auto taskdata = tc::create_task(tcuuid, ops);
2,977✔
76

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

84
    taskdata->update(attr, task.get(attr), ops);
13,823✔
85
  }
2,977✔
86
  replica()->commit_operations(std::move(ops));
2,977✔
87

88
  invalidate_cached_info();
2,977✔
89

90
  // get the ID that was assigned to this task
91
  auto id = working_set()->by_uuid(tcuuid);
2,977✔
92
  if (id > 0) {
2,977✔
93
    task.id = id;
2,857✔
94
  }
95
}
2,991✔
96

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

119
  rust::Vec<tc::Operation> ops;
576✔
120
  maybe_add_undo_point(ops);
576✔
121

122
  changes[uuid] = task;
576✔
123

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

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

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

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

172
  replica()->commit_operations(std::move(ops));
570✔
173

174
  invalidate_cached_info();
570✔
175
}
588✔
176

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

188
  invalidate_cached_info();
6✔
189
}
6✔
190

191
////////////////////////////////////////////////////////////////////////////////
192
rust::Box<tc::Replica>& TDB2::replica() {
758,075✔
193
  // Create a replica in-memory if `open_replica` has not been called. This
194
  // occurs in tests.
195
  if (!_replica) {
758,075✔
196
    _replica = tc::new_replica_in_memory();
1✔
197
  }
198
  return _replica.value();
758,075✔
199
}
200

201
////////////////////////////////////////////////////////////////////////////////
202
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
754,403✔
203
  if (!_working_set.has_value()) {
754,403✔
204
    _working_set = replica()->working_set();
5,202✔
205
  }
206
  return _working_set.value();
754,403✔
207
}
208

209
////////////////////////////////////////////////////////////////////////////////
210
void TDB2::maybe_add_undo_point(rust::Vec<tc::Operation>& ops) {
3,560✔
211
  // Only add an UndoPoint if there are not yet any changes.
212
  if (changes.size() == 0) {
3,560✔
213
    tc::add_undo_point(ops);
1,774✔
214
  }
215
}
3,560✔
216

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

225
////////////////////////////////////////////////////////////////////////////////
226
void TDB2::revert() {
7✔
227
  rust::Vec<tc::Operation> undo_ops = replica()->get_undo_operations();
7✔
228
  if (undo_ops.size() == 0) {
7✔
229
    std::cout << "No operations to undo.";
×
230
    return;
×
231
  }
232
  if (confirm_revert(undo_ops)) {
7✔
233
    // Note that commit_reversed_operations rebuilds the working set, so that
234
    // need not be done here.
235
    if (!replica()->commit_reversed_operations(std::move(undo_ops))) {
4✔
NEW
236
      std::cout << "Could not undo: other operations have occurred.";
×
237
    }
238
  }
239
}
7✔
240

241
////////////////////////////////////////////////////////////////////////////////
242
bool TDB2::confirm_revert(rust::Vec<tc::Operation>& undo_ops) {
7✔
243
  // TODO Use show_diff rather than this basic listing of operations, though
244
  // this might be a worthy undo.style itself.
245
  std::cout << "The following " << undo_ops.size() << " operations would be reverted:\n";
7✔
246
  for (auto& op : undo_ops) {
40✔
247
    if (op.is_undo_point()) {
33✔
248
      continue;
7✔
249
    }
250

251
    std::cout << "- ";
26✔
252
    std::string uuid = static_cast<std::string>(op.get_uuid().to_string());
26✔
253
    if (op.is_create()) {
26✔
254
      std::cout << "Create " << uuid;
2✔
255
    } else if (op.is_delete()) {
24✔
NEW
256
      std::cout << "Delete ";
×
NEW
257
      auto old_task = op.get_old_task();
×
NEW
258
      bool found_description = false;
×
NEW
259
      for (auto& pv : old_task) {
×
NEW
260
        if (static_cast<std::string>(pv.prop) == "description") {
×
NEW
261
          std::cout << static_cast<std::string>(pv.value) << " (" << uuid << ")";
×
NEW
262
          found_description = true;
×
263
        }
264
      }
NEW
265
      if (!found_description) {
×
NEW
266
        std::cout << uuid;
×
267
      }
268
    } else if (op.is_update()) {
24✔
269
      std::cout << "Update " << uuid << "\n";
24✔
270
      std::string property;
24✔
271
      op.get_property(property);
24✔
272
      std::string value;
24✔
273
      bool have_value = op.get_value(value);
24✔
274
      std::string old_value;
24✔
275
      bool have_old_value = op.get_old_value(old_value);
24✔
276
      std::cout << "    " << property << ": ";
24✔
277
      std::cout << (have_old_value ? old_value : "<empty>") << " -> ";
24✔
278
      std::cout << (have_value ? value : "<empty>");
24✔
279
    }
24✔
280
    std::cout << "\n";
26✔
281
  }
26✔
282
  return !Context::getContext().config.getBoolean("confirmation") ||
20✔
283
         confirm(
13✔
284
             "The undo command is not reversible.  Are you sure you want to revert to the previous "
285
             "state?");
7✔
286
  return true;
287
}
288

289
////////////////////////////////////////////////////////////////////////////////
UNCOV
290
void TDB2::show_diff(const std::string& current, const std::string& prior,
×
291
                     const std::string& when) {
292
  Datetime lastChange(strtoll(when.c_str(), nullptr, 10));
×
293

294
  // Set the colors.
295
  Color color_red(
296
      Context::getContext().color() ? Context::getContext().config.get("color.undo.before") : "");
×
297
  Color color_green(
298
      Context::getContext().color() ? Context::getContext().config.get("color.undo.after") : "");
×
299

300
  auto before = prior == "" ? Task() : Task(prior);
×
301
  auto after = Task(current);
×
302

303
  if (Context::getContext().config.get("undo.style") == "side") {
×
304
    Table view = before.diffForUndoSide(after);
×
305

306
    std::cout << '\n'
307
              << format("The last modification was made {1}", lastChange.toString()) << '\n'
×
308
              << '\n'
309
              << view.render() << '\n';
×
310
  }
311

312
  else if (Context::getContext().config.get("undo.style") == "diff") {
×
313
    Table view = before.diffForUndoPatch(after, lastChange);
×
314
    std::cout << '\n' << view.render() << '\n';
×
315
  }
316
}
317

318
////////////////////////////////////////////////////////////////////////////////
319
void TDB2::gc() {
913✔
320
  Timer timer;
913✔
321

322
  // Allowed as an override, but not recommended.
323
  if (Context::getContext().config.getBoolean("gc")) {
913✔
324
    replica()->rebuild_working_set(true);
910✔
325
  }
326

327
  Context::getContext().time_gc_us += timer.total_us();
913✔
328
}
913✔
329

330
////////////////////////////////////////////////////////////////////////////////
331
void TDB2::expire_tasks() { replica()->expire_tasks(); }
1✔
332

333
////////////////////////////////////////////////////////////////////////////////
334
// Latest ID is that of the last pending task.
335
int TDB2::latest_id() {
167✔
336
  auto& ws = working_set();
167✔
337
  return (int)ws->largest_index();
167✔
338
}
339

340
////////////////////////////////////////////////////////////////////////////////
341
const std::vector<Task> TDB2::all_tasks() {
443✔
342
  auto all_tctasks = replica()->all_task_data();
443✔
343
  std::vector<Task> all;
443✔
344
  for (auto& maybe_tctask : all_tctasks) {
1,637✔
345
    auto tctask = maybe_tctask.take();
1,194✔
346
    all.push_back(Task(std::move(tctask)));
1,194✔
347
  }
1,194✔
348

349
  dependency_scan(all);
443✔
350

351
  return all;
886✔
352
}
443✔
353

354
////////////////////////////////////////////////////////////////////////////////
355
const std::vector<Task> TDB2::pending_tasks() {
6,375✔
356
  if (!_pending_tasks) {
6,375✔
357
    auto& ws = working_set();
3,783✔
358
    auto largest_index = ws->largest_index();
3,783✔
359

360
    std::vector<Task> result;
3,783✔
361
    for (size_t i = 0; i <= largest_index; i++) {
752,890✔
362
      auto uuid = ws->by_index(i);
749,107✔
363
      if (!uuid.is_nil()) {
749,107✔
364
        auto maybe_task = replica()->get_task_data(uuid);
745,280✔
365
        if (maybe_task.is_some()) {
745,280✔
366
          result.push_back(Task(maybe_task.take()));
745,280✔
367
        }
368
      }
369
    }
370

371
    dependency_scan(result);
3,783✔
372

373
    _pending_tasks = result;
3,783✔
374
  }
3,783✔
375

376
  return *_pending_tasks;
6,375✔
377
}
378

379
////////////////////////////////////////////////////////////////////////////////
380
const std::vector<Task> TDB2::completed_tasks() {
299✔
381
  if (!_completed_tasks) {
299✔
382
    auto all_tctasks = replica()->all_task_data();
299✔
383
    auto& ws = working_set();
299✔
384

385
    std::vector<Task> result;
299✔
386
    for (auto& maybe_tctask : all_tctasks) {
2,158✔
387
      auto tctask = maybe_tctask.take();
1,859✔
388
      // if this task is _not_ in the working set, return it.
389
      if (ws->by_uuid(tctask->get_uuid()) == 0) {
1,859✔
390
        result.push_back(Task(std::move(tctask)));
226✔
391
      }
392
    }
1,859✔
393
    _completed_tasks = result;
299✔
394
  }
299✔
395
  return *_completed_tasks;
299✔
396
}
397

398
////////////////////////////////////////////////////////////////////////////////
399
void TDB2::invalidate_cached_info() {
3,553✔
400
  _pending_tasks = std::nullopt;
3,553✔
401
  _completed_tasks = std::nullopt;
3,553✔
402
  _working_set = std::nullopt;
3,553✔
403
}
3,553✔
404

405
////////////////////////////////////////////////////////////////////////////////
406
// Locate task by ID, wherever it is.
407
bool TDB2::get(int id, Task& task) {
308✔
408
  auto& ws = working_set();
308✔
409
  const auto tcuuid = ws->by_index(id);
308✔
410
  if (!tcuuid.is_nil()) {
308✔
411
    std::string uuid = static_cast<std::string>(tcuuid.to_string());
296✔
412
    // Load all pending tasks in order to get dependency data, and in particular
413
    // `task.is_blocking` and `task.is_blocked`, set correctly.
414
    std::vector<Task> pending = pending_tasks();
296✔
415
    for (auto& pending_task : pending) {
1,537✔
416
      if (pending_task.get("uuid") == uuid) {
1,537✔
417
        task = pending_task;
296✔
418
        return true;
296✔
419
      }
420
    }
421
  }
592✔
422

423
  return false;
12✔
424
}
425

426
////////////////////////////////////////////////////////////////////////////////
427
// Locate task by UUID, including by partial ID, wherever it is.
428
bool TDB2::get(const std::string& uuid, Task& task) {
2,286✔
429
  // Load all pending tasks in order to get dependency data, and in particular
430
  // `task.is_blocking` and `task.is_blocked`, set correctly.
431
  std::vector<Task> pending = pending_tasks();
2,286✔
432

433
  // try by raw uuid, if the length is right
434
  for (auto& pending_task : pending) {
735,069✔
435
    if (closeEnough(pending_task.get("uuid"), uuid, uuid.length())) {
733,454✔
436
      task = pending_task;
671✔
437
      return true;
671✔
438
    }
439
  }
440

441
  // Nothing to do but iterate over all tasks and check whether it's closeEnough.
442
  for (auto& maybe_tctask : replica()->all_task_data()) {
733,506✔
443
    auto tctask = maybe_tctask.take();
731,916✔
444
    auto tctask_uuid = static_cast<std::string>(tctask->get_uuid().to_string());
731,916✔
445
    if (closeEnough(tctask_uuid, uuid, uuid.length())) {
731,916✔
446
      task = Task{std::move(tctask)};
25✔
447
      return true;
25✔
448
    }
449
  }
733,556✔
450

451
  return false;
1,590✔
452
}
2,286✔
453

454
////////////////////////////////////////////////////////////////////////////////
455
// Locate task by UUID, wherever it is.
456
bool TDB2::has(const std::string& uuid) {
×
457
  Task task;
×
458
  return get(uuid, task);
×
459
}
460

461
////////////////////////////////////////////////////////////////////////////////
462
const std::vector<Task> TDB2::siblings(Task& task) {
8✔
463
  std::vector<Task> results;
8✔
464
  if (task.has("parent")) {
8✔
465
    std::string parent = task.get("parent");
16✔
466

467
    for (auto& i : this->pending_tasks()) {
35✔
468
      // Do not include self in results.
469
      if (i.id != task.id) {
27✔
470
        // Do not include completed or deleted tasks.
471
        if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
19✔
472
          // If task has the same parent, it is a sibling.
473
          if (i.has("parent") && i.get("parent") == parent) {
19✔
474
            results.push_back(i);
10✔
475
          }
476
        }
477
      }
478
    }
8✔
479
  }
8✔
480

481
  return results;
8✔
482
}
483

484
////////////////////////////////////////////////////////////////////////////////
485
const std::vector<Task> TDB2::children(Task& parent) {
71✔
486
  // scan _pending_ tasks for those with `parent` equal to this task
487
  std::vector<Task> results;
71✔
488
  std::string this_uuid = parent.get("uuid");
142✔
489

490
  auto& ws = working_set();
71✔
491
  size_t end_idx = ws->largest_index();
71✔
492

493
  for (size_t i = 0; i <= end_idx; i++) {
387✔
494
    auto uuid = ws->by_index(i);
316✔
495
    if (uuid.is_nil()) {
316✔
496
      continue;
302✔
497
    }
498

499
    // skip self-references
500
    if (uuid.to_string() == this_uuid) {
245✔
501
      continue;
70✔
502
    }
503

504
    auto task_opt = replica()->get_task_data(uuid);
175✔
505
    if (task_opt.is_none()) {
175✔
UNCOV
506
      continue;
×
507
    }
508
    auto task = task_opt.take();
175✔
509

510
    std::string parent_uuid;
175✔
511
    if (!task->get("parent", parent_uuid)) {
175✔
512
      continue;
161✔
513
    }
514

515
    if (parent_uuid == this_uuid) {
14✔
516
      results.push_back(Task(std::move(task)));
14✔
517
    }
518
  }
336✔
519
  return results;
142✔
520
}
71✔
521

522
////////////////////////////////////////////////////////////////////////////////
523
std::string TDB2::uuid(int id) {
59✔
524
  auto& ws = working_set();
59✔
525
  auto uuid = ws->by_index(id);
59✔
526
  if (uuid.is_nil()) {
59✔
527
    return "";
1✔
528
  }
529
  return static_cast<std::string>(uuid.to_string());
116✔
530
}
531

532
////////////////////////////////////////////////////////////////////////////////
533
int TDB2::id(const std::string& uuid) {
746,739✔
534
  auto& ws = working_set();
746,739✔
535
  return ws->by_uuid(tc::uuid_from_string(uuid));
746,739✔
536
}
537

538
////////////////////////////////////////////////////////////////////////////////
539
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
5✔
540

541
////////////////////////////////////////////////////////////////////////////////
542
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
3✔
543

544
////////////////////////////////////////////////////////////////////////////////
545
void TDB2::dump() {
×
546
  // TODO
547
}
548

549
////////////////////////////////////////////////////////////////////////////////
550
// For any task that has depenencies, follow the chain of dependencies until the
551
// end.  Along the way, update the Task::is_blocked and Task::is_blocking data
552
// cache.
553
static void dependency_scan(std::vector<Task>& tasks) {
4,226✔
554
  for (auto& left : tasks) {
750,700✔
555
    for (auto& dep : left.getDependencyUUIDs()) {
746,833✔
556
      for (auto& right : tasks) {
1,516✔
557
        if (right.get("uuid") == dep) {
1,510✔
558
          // GC hasn't run yet, check both tasks for their current status
559
          Task::status lstatus = left.getStatus();
353✔
560
          Task::status rstatus = right.getStatus();
353✔
561
          if (lstatus != Task::completed && lstatus != Task::deleted &&
353✔
562
              rstatus != Task::completed && rstatus != Task::deleted) {
330✔
563
            left.is_blocked = true;
321✔
564
            right.is_blocking = true;
321✔
565
          }
566

567
          // Only want to break out of the "right" loop.
568
          break;
353✔
569
        }
570
      }
571
    }
746,474✔
572
  }
573
}
4,226✔
574

575
////////////////////////////////////////////////////////////////////////////////
576
// 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