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

GothenburgBitFactory / taskwarrior / 28061458970

23 Jun 2026 10:29PM UTC coverage: 84.886% (-0.2%) from 85.053%
28061458970

Pull #4127

github

web-flow
Merge e8ad67672 into 3b61e6ee7
Pull Request #4127: Caching foundation for a dependencyGraph which maps dependency relations.

78 of 101 new or added lines in 1 file covered. (77.23%)

27 existing lines in 4 files now uncovered.

19623 of 23117 relevant lines covered (84.89%)

24103.95 hits per line

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

90.11
/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 <optional>
42
#include <unordered_set>
43
#include <vector>
44

45
bool TDB2::debug_mode = false;
46
// This functions main job is to set Task::is_blocked / Task::is_blocking flags.
47
static void dependency_scan(std::vector<Task>&, const std::unordered_map<std::string, size_t>&);
48

49
// Build maps for dependency queries.
50
static DependencyGraph build_dependency_graph(const std::vector<Task>&,
51
                                              const std::unordered_map<std::string, size_t>&);
52

53
////////////////////////////////////////////////////////////////////////////////
54
void TDB2::open_replica(const std::string& location, bool create_if_missing, bool read_write) {
4,618✔
55
  _replica = tc::new_replica_on_disk(location, create_if_missing, read_write);
4,619✔
56
}
4,617✔
57

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

66
  rust::Vec<tc::Operation> ops;
3,012✔
67
  maybe_add_undo_point(ops);
3,012✔
68

69
  auto uuid = task.get_ref("uuid");
3,012✔
70
  changes[uuid] = task;
3,012✔
71
  tc::Uuid tcuuid = tc::uuid_from_string(uuid);
3,012✔
72

73
  // run hooks for this new task
74
  Context::getContext().hooks.onAdd(task);
3,012✔
75

76
  auto taskdata = tc::create_task(tcuuid, ops);
3,005✔
77

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

85
    taskdata->update(attr, task.get(attr), ops);
13,972✔
86
  }
3,005✔
87
  replica()->commit_operations(std::move(ops));
3,005✔
88

89
  invalidate_cached_info();
3,005✔
90

91
  // get the ID that was assigned to this task
92
  auto id = working_set()->by_uuid(tcuuid);
3,005✔
93
  if (id > 0) {
3,005✔
94
    task.id = id;
2,885✔
95
  }
96
}
3,019✔
97

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

120
  rust::Vec<tc::Operation> ops;
600✔
121
  maybe_add_undo_point(ops);
600✔
122

123
  changes[uuid] = task;
600✔
124

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

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

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

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

173
  replica()->commit_operations(std::move(ops));
594✔
174

175
  // If the task entered or left the pending set, we must invalidate the cache.
176
  bool was_pending = found_original && (original.getStatus() == Task::pending);
594✔
177
  bool now_pending = task.getStatus() == Task::pending;
594✔
178
  if (was_pending != now_pending) {
594✔
179
    invalidate_cached_info();
196✔
180
    return;
196✔
181
  }
182

183
  // If the task stayed in the set, we can edit the vector in-place.
184
  // This speeds up modifications a lot relative to reloading and parsing from rust.
185
  bool deps_changed = false;
398✔
186
  if (_pending_tasks) {
398✔
187
    auto* pt = find_pending(uuid);
398✔
188
    if (pt) {
398✔
189
      auto old_deps = pt->getDependencyUUIDs();
392✔
190
      *pt = task;
392✔
191
      auto new_deps = task.getDependencyUUIDs();
392✔
192
      if (old_deps != new_deps) {
392✔
193
        deps_changed = true;
35✔
194
        dependency_scan(*_pending_tasks, pending_index());
35✔
195
      }
196
    }
392✔
197
  }
198
  // We have to drop the dependency map, in case modifications were made to those.
199
  // We only drop it if they actually changed.
200
  if (deps_changed)
398✔
201
    _dependency_graph = std::nullopt;
35✔
202
  // We also have to drop _completed_tasks. This probably isn't strictly necessary
203
  // but it seems sensible for correctness reasons.
204
  _completed_tasks = std::nullopt;
398✔
205
}
1,396✔
206

207
////////////////////////////////////////////////////////////////////////////////
208
void TDB2::purge(Task& task) {
10✔
209
  auto uuid = tc::uuid_from_string(task.get_ref("uuid"));
10✔
210
  rust::Vec<tc::Operation> ops;
10✔
211
  auto maybe_tctask = replica()->get_task_data(uuid);
10✔
212
  if (maybe_tctask.is_some()) {
10✔
213
    auto tctask = maybe_tctask.take();
10✔
214
    tctask->delete_task(ops);
10✔
215
    replica()->commit_operations(std::move(ops));
10✔
216
  }
10✔
217

218
  invalidate_cached_info();
10✔
219
}
10✔
220

221
////////////////////////////////////////////////////////////////////////////////
222
rust::Box<tc::Replica>& TDB2::replica() {
16,334✔
223
  // One of the open_replica_ methods must be called before this one.
224
  assert(_replica);
16,334✔
225
  return _replica.value();
16,334✔
226
}
227

228
////////////////////////////////////////////////////////////////////////////////
229
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
750,238✔
230
  if (!_working_set.has_value()) {
750,238✔
231
    _working_set = replica()->working_set();
4,982✔
232
  }
233
  return _working_set.value();
750,238✔
234
}
235

236
////////////////////////////////////////////////////////////////////////////////
237
void TDB2::maybe_add_undo_point(rust::Vec<tc::Operation>& ops) {
3,612✔
238
  // Only add an UndoPoint if there are not yet any changes.
239
  if (changes.size() == 0) {
3,612✔
240
    tc::add_undo_point(ops);
1,811✔
241
  }
242
}
3,612✔
243

244
////////////////////////////////////////////////////////////////////////////////
245
void TDB2::get_changes(std::vector<Task>& changes) {
5✔
246
  std::map<std::string, Task>& changes_map = this->changes;
5✔
247
  changes.clear();
5✔
248
  std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
5✔
249
                 [](const auto& kv) { return kv.second; });
1✔
250
}
5✔
251

252
////////////////////////////////////////////////////////////////////////////////
253
void TDB2::gc() {
937✔
254
  Timer timer;
937✔
255

256
  // Allowed as an override, but not recommended.
257
  if (Context::getContext().config.getBoolean("gc")) {
2,811✔
258
    replica()->rebuild_working_set(true);
932✔
259
  }
260

261
  Context::getContext().time_gc_us += timer.total_us();
937✔
262
}
937✔
263

264
////////////////////////////////////////////////////////////////////////////////
265
void TDB2::expire_tasks() { replica()->expire_tasks(); }
1✔
266

267
////////////////////////////////////////////////////////////////////////////////
268
// Latest ID is that of the last pending task.
269
int TDB2::latest_id() {
180✔
270
  auto& ws = working_set();
180✔
271
  return (int)ws->largest_index();
180✔
272
}
273

274
////////////////////////////////////////////////////////////////////////////////
275
// Not cached: callers read this once per process, and it can be very large.
276
const std::vector<Task> TDB2::all_tasks() {
452✔
277
  Timer timer;
452✔
278
  auto all_tctasks = replica()->all_task_data();
452✔
279
  std::vector<Task> all;
452✔
280
  for (auto& maybe_tctask : all_tctasks) {
1,671✔
281
    auto tctask = maybe_tctask.take();
1,219✔
282
    all.push_back(Task(std::move(tctask)));
1,219✔
283
  }
1,219✔
284

285
  // Build a temporary map so that dependency_scan can resolve references
286
  // inside all_tasks.
287
  std::unordered_map<std::string, size_t> all_index;
452✔
288
  all_index.reserve(all.size());
452✔
289
  for (size_t i = 0; i < all.size(); ++i) all_index[all[i].get_ref("uuid")] = i;
2,890✔
290

291
  dependency_scan(all, all_index);
452✔
292

293
  Context::getContext().time_load_us += timer.total_us();
452✔
294
  return all;
904✔
295
}
452✔
296

297
////////////////////////////////////////////////////////////////////////////////
298
// Load and cache pending tasks. The first call will build the UUID index,
299
// after which it is reused.
300
const std::vector<Task>& TDB2::pending_tasks() {
6,502✔
301
  if (!_pending_tasks) {
6,502✔
302
    Timer timer;
3,705✔
303

304
    auto pending_tctasks = replica()->pending_task_data();
3,705✔
305
    std::vector<Task> result;
3,705✔
306

307
    result.reserve(pending_tctasks.size());
3,705✔
308

309
    for (auto& maybe_tctask : pending_tctasks) {
748,571✔
310
      auto tctask = maybe_tctask.take();
744,866✔
311
      result.push_back(Task(std::move(tctask)));
744,866✔
312
    }
744,866✔
313

314
    // Build a UUID map for use with get()/modify() and dependency_scan()
315
    // while the pending vector is already in memory.
316
    _pending_index.emplace();
3,705✔
317
    _pending_index->reserve(result.size());
3,705✔
318
    for (size_t i = 0, n = result.size(); i < n; ++i)
748,571✔
319
      _pending_index->emplace(result[i].get_ref("uuid"), i);
2,234,598✔
320

321
    dependency_scan(result, *_pending_index);
3,705✔
322

323
    Context::getContext().time_load_us += timer.total_us();
3,705✔
324
    _pending_tasks = std::move(result);
3,705✔
325
  }
3,705✔
326

327
  return *_pending_tasks;
6,502✔
328
}
329

330
////////////////////////////////////////////////////////////////////////////////
331
// Load and cache all completed tests by scanning all tasks,
332
// and excluding those in the working set. We cache it to speed up those reports
333
// which involve completed tasks.
334
const std::vector<Task>& TDB2::completed_tasks() {
323✔
335
  if (!_completed_tasks) {
323✔
336
    auto all_tctasks = replica()->all_task_data();
323✔
337
    auto& ws = working_set();
323✔
338

339
    std::vector<Task> result;
323✔
340

341
    result.reserve(all_tctasks.size());
323✔
342

343
    for (auto& maybe_tctask : all_tctasks) {
2,218✔
344
      auto tctask = maybe_tctask.take();
1,895✔
345
      // if this task is _not_ in the working set, return it.
346
      if (ws->by_uuid(tctask->get_uuid()) == 0) {
1,895✔
347
        result.push_back(Task(std::move(tctask)));
242✔
348
      }
349
    }
1,895✔
350
    _completed_tasks = std::move(result);
323✔
351
  }
323✔
352
  return *_completed_tasks;
323✔
353
}
354

355
/////////////////////////////////////////////////////////////////////////////////
356
// Build and return the dependency map for pending tasks.
357
// We invalidate it whenever pending_tasks may have changed.
NEW
358
const DependencyGraph& TDB2::dependency_graph() {
×
NEW
359
  if (!_dependency_graph) {
×
NEW
360
    pending_tasks();
×
361
    // reuse the UUID index
NEW
362
    _dependency_graph = build_dependency_graph(*_pending_tasks, pending_index());
×
363
  }
364

NEW
365
  return *_dependency_graph;
×
366
}
367

368
/////////////////////////////////////////////////////////////////////////////////
369
// This builds and returns the UUID map if it is missing.
370
const std::unordered_map<std::string, size_t>& TDB2::pending_index() {
3,050✔
371
  if (!_pending_index) {
3,050✔
NEW
372
    _pending_index.emplace();
×
NEW
373
    _pending_index->reserve(_pending_tasks->size());
×
NEW
374
    for (size_t i = 0, n = _pending_tasks->size(); i < n; ++i)
×
NEW
375
      _pending_index->emplace((*_pending_tasks)[i].get_ref("uuid"), i);
×
376
  }
377

378
  return *_pending_index;
3,050✔
379
}
380

381
// Finds the UUID in the index. Returns nullptr if the task is not in the pending
382
// set.
383
Task* TDB2::find_pending(const std::string& uuid) {
3,015✔
384
  auto& idx = pending_index();
3,015✔
385
  auto it = idx.find(uuid);
3,015✔
386
  if (it != idx.end()) return &(*_pending_tasks)[it->second];
3,015✔
387
  return nullptr;
1,628✔
388
}
389

390
////////////////////////////////////////////////////////////////////////////////
391
void TDB2::invalidate_cached_info() {
3,211✔
392
  _pending_tasks = std::nullopt;
3,211✔
393
  _completed_tasks = std::nullopt;
3,211✔
394
  _working_set = std::nullopt;
3,211✔
395
  _dependency_graph = std::nullopt;
3,211✔
396
  _pending_index = std::nullopt;
3,211✔
397
}
3,211✔
398

399
////////////////////////////////////////////////////////////////////////////////
400
// Locate task by ID, wherever it is.
401
bool TDB2::get(int id, Task& task) {
316✔
402
  auto& ws = working_set();
316✔
403
  const auto tcuuid = ws->by_index(id);
316✔
404
  if (!tcuuid.is_nil()) {
316✔
405
    std::string uuid = static_cast<std::string>(tcuuid.to_string());
304✔
406
    // Load index of pending tasks.
407
    pending_tasks();
304✔
408
    // Lookup the UUID in the index instead of scanning the vector.
409
    auto* pt = find_pending(uuid);
304✔
410
    if (pt) {
304✔
411
      task = *pt;
304✔
412
      return true;
304✔
413
    }
414
  }
304✔
415

416
  return false;
12✔
417
}
418

419
////////////////////////////////////////////////////////////////////////////////
420
// Locate task by UUID, including by partial ID, wherever it is.
421
bool TDB2::get(const std::string& uuid, Task& task) {
2,313✔
422
  pending_tasks();
2,313✔
423

424
  // Try to match exact UUID within the index.
425
  auto* pt = find_pending(uuid);
2,313✔
426
  if (pt) {
2,313✔
427
    task = *pt;
691✔
428
    return true;
691✔
429
  }
430

431
  // try a partial match
432
  if (uuid.length() < 36) {
1,622✔
433
    for (const auto& pending_task : *_pending_tasks) {
14✔
434
      if (closeEnough(pending_task.get_ref("uuid"), uuid, uuid.length())) {
42✔
435
        task = pending_task;
4✔
436
        return true;
4✔
437
      }
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,504✔
443
    auto tctask = maybe_tctask.take();
731,914✔
444
    auto tctask_uuid = static_cast<std::string>(tctask->get_uuid().to_string());
731,914✔
445
    if (closeEnough(tctask_uuid, uuid, uuid.length())) {
731,914✔
446
      task = Task{std::move(tctask)};
28✔
447
      return true;
28✔
448
    }
449
  }
733,560✔
450

451
  return false;
1,590✔
452
}
453

454
////////////////////////////////////////////////////////////////////////////////
455
// Locate task by UUID, wherever it is.
456
bool TDB2::has(const std::string& uuid) {
×
UNCOV
457
  return replica()->get_task_data(tc::uuid_from_string(uuid)).is_some();
×
458
}
459

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

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

480
  return results;
8✔
UNCOV
481
}
×
482

483
////////////////////////////////////////////////////////////////////////////////
484
// Return the child tasks of a parent. Uses the _pending_tasks cache, to avoid
485
// having to fetch this info from the Rust replica.
486
const std::vector<Task> TDB2::children(Task& parent) {
72✔
487
  std::vector<Task> results;
72✔
488
  const auto& this_uuid = parent.get_ref("uuid");
72✔
489

490
  for (const auto& i : pending_tasks()) {
321✔
491
    if (i.get_ref("uuid") == this_uuid) continue;
498✔
492
    if (i.has("parent") && i.get_ref("parent") == this_uuid) results.push_back(i);
568✔
493
  }
494
  return results;
72✔
UNCOV
495
}
×
496

497
////////////////////////////////////////////////////////////////////////////////
498
std::string TDB2::uuid(int id) {
59✔
499
  auto& ws = working_set();
59✔
500
  auto uuid = ws->by_index(id);
59✔
501
  if (uuid.is_nil()) {
59✔
502
    return "";
2✔
503
  }
504
  return static_cast<std::string>(uuid.to_string());
58✔
505
}
506

507
////////////////////////////////////////////////////////////////////////////////
508
int TDB2::id(const std::string& uuid) {
746,355✔
509
  auto& ws = working_set();
746,355✔
510
  return ws->by_uuid(tc::uuid_from_string(uuid));
746,355✔
511
}
512

513
////////////////////////////////////////////////////////////////////////////////
514
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
5✔
515

516
////////////////////////////////////////////////////////////////////////////////
517
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
3✔
518

519
////////////////////////////////////////////////////////////////////////////////
520
// Set Task::is_blocked / Task::is_blocking flags using the pre-built UUID map
521
static void dependency_scan(std::vector<Task>& tasks,
4,192✔
522
                            const std::unordered_map<std::string, size_t>& uuid_index) {
523
  // Reset all flags first. This is for safety reasons - if we don't do this
524
  // dependency_scan() only sets them to true, so it can stay true (within the cache)
525
  // even after we have changed a task's dependencies after a modify when
526
  // dependency_scan() runs the second time - the flag will still be set to true!
527
  // In many cases, this isn't user-visible - it's only visible in reports
528
  // or filters that explicitly filter for +BLOCKING.
529
  for (auto& task : tasks) {
750,384✔
530
    task.is_blocking = false;
746,192✔
531
    task.is_blocked = false;
746,192✔
532
  }
533
  for (size_t i = 0; i < tasks.size(); ++i) {
750,384✔
534
    auto lstatus = tasks[i].getStatus();
746,192✔
535
    for (const auto& dep : tasks[i].getDependencyUUIDs()) {
746,595✔
536
      auto it = uuid_index.find(dep);
403✔
537
      if (it == uuid_index.end()) continue;
403✔
538

539
      size_t j = it->second;
391✔
540
      auto rstatus = tasks[j].getStatus();
391✔
541
      if (lstatus != Task::completed && lstatus != Task::deleted && rstatus != Task::completed &&
391✔
542
          rstatus != Task::deleted) {
543
        tasks[i].is_blocked = true;
361✔
544
        tasks[j].is_blocking = true;
361✔
545
      }
546
    }
746,192✔
547
  }
548
}
4,192✔
549

550
/////////////////////////////////////////////////////////////////////////////////
551
// Build the full dependency map from the task vector.
NEW
552
static DependencyGraph build_dependency_graph(
×
553
    const std::vector<Task>& tasks, const std::unordered_map<std::string, size_t>& uuid_index) {
NEW
554
  DependencyGraph graph;
×
555

NEW
556
  for (size_t i = 0; i < tasks.size(); ++i) {
×
NEW
557
    const auto& deps = tasks[i].getDependencyUUIDs();
×
NEW
558
    if (deps.empty()) continue;
×
559

NEW
560
    const auto& uuid = tasks[i].get_ref("uuid");
×
561

NEW
562
    for (const auto& dep : deps) {
×
NEW
563
      auto it = uuid_index.find(dep);
×
NEW
564
      if (it == uuid_index.end()) continue;
×
565

NEW
566
      graph.dependencies[uuid].push_back(it->second);
×
NEW
567
      graph.dependents[dep].push_back(i);
×
568
    }
NEW
569
  }
×
570

NEW
571
  return graph;
×
NEW
572
}
×
573

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

© 2026 Coveralls, Inc