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

GothenburgBitFactory / taskwarrior / 27576113949

15 Jun 2026 09:00PM UTC coverage: 84.896%. First build
27576113949

Pull #4127

github

web-flow
Merge 2bd369b93 into 76537e107
Pull Request #4127: Caching foundation for a dependencyGraph which maps dependency relations.

65 of 88 new or added lines in 1 file covered. (73.86%)

19633 of 23126 relevant lines covered (84.9%)

23995.28 hits per line

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

89.73
/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>&, const std::unordered_map<std::string, size_t>&);
51

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

57
////////////////////////////////////////////////////////////////////////////////
58
// Add the new task to the replica.
59
void TDB2::add(Task& task) {
3,012✔
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);
3,012✔
64

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

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

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

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

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

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

88
  invalidate_cached_info();
3,005✔
89

90
  // get the ID that was assigned to this task
91
  auto id = working_set()->by_uuid(tcuuid);
3,005✔
92
  if (id > 0) {
3,005✔
93
    task.id = id;
2,885✔
94
  }
95
}
3,019✔
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) {
600✔
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");
600✔
116
  task.validate(false);
600✔
117
  auto uuid = task.get_ref("uuid");
600✔
118

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

122
  changes[uuid] = task;
600✔
123

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

129
  tc::Uuid tcuuid = tc::uuid_from_string(uuid);
594✔
130
  auto maybe_tctask = replica()->get_task_data(tcuuid);
594✔
131
  if (maybe_tctask.is_none()) {
594✔
132
    throw std::string("task no longer exists");
×
133
  }
134
  auto tctask = maybe_tctask.take();
594✔
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;
594✔
139
  for (auto k : task.all()) {
5,161✔
140
    // ignore task keys that aren't stored
141
    if (k == "uuid") {
4,567✔
142
      continue;
594✔
143
    }
144
    seen.insert(k);
3,973✔
145
    bool update = false;
3,973✔
146
    auto v_new = task.get(k);
3,973✔
147
    std::string v_tctask;
3,973✔
148
    if (tctask->get(k, v_tctask)) {
3,973✔
149
      update = v_tctask != v_new;
3,391✔
150
    } else {
151
      // tctask does not contain k, so update it
152
      update = true;
582✔
153
    }
154
    if (update) {
3,973✔
155
      // An empty string indicates the value should be removed.
156
      if (v_new == "") {
1,059✔
157
        tctask->update_remove(k, ops);
4✔
158
      } else {
159
        tctask->update(k, v_new, ops);
1,055✔
160
      }
161
    }
162
  }
5,161✔
163

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

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

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

182
  // If the task stayed in the set, we can edit the vector in-place.
183
  // This speeds up modifications a lot relative to reloading and parsing from rust.
184
  if (_pending_tasks) {
398✔
185
    auto idx = pending_index_of(uuid);
398✔
186
    if (idx != SIZE_MAX)
398✔
187
      (*_pending_tasks)[idx] = task;
392✔
188
  }
189
  // We have to drop the dependency map, in case modifications were made to those.
190
  _dependency_graph = std::nullopt;
398✔
191
  // We also have to drop _completed_tasks. This probably isn't strictly necessary
192
  // but it seems sensible for correctness reasons.
193
  _completed_tasks = std::nullopt;
398✔
194
}
1,396✔
195

196
////////////////////////////////////////////////////////////////////////////////
197
void TDB2::purge(Task& task) {
10✔
198
  auto uuid = tc::uuid_from_string(task.get_ref("uuid"));
10✔
199
  rust::Vec<tc::Operation> ops;
10✔
200
  auto maybe_tctask = replica()->get_task_data(uuid);
10✔
201
  if (maybe_tctask.is_some()) {
10✔
202
    auto tctask = maybe_tctask.take();
10✔
203
    tctask->delete_task(ops);
10✔
204
    replica()->commit_operations(std::move(ops));
10✔
205
  }
10✔
206

207
  invalidate_cached_info();
10✔
208
}
10✔
209

210
////////////////////////////////////////////////////////////////////////////////
211
rust::Box<tc::Replica>& TDB2::replica() {
16,506✔
212
  // One of the open_replica_ methods must be called before this one.
213
  assert(_replica);
16,506✔
214
  return _replica.value();
16,506✔
215
}
216

217
////////////////////////////////////////////////////////////////////////////////
218
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
750,319✔
219
  if (!_working_set.has_value()) {
750,319✔
220
    _working_set = replica()->working_set();
4,982✔
221
  }
222
  return _working_set.value();
750,319✔
223
}
224

225
////////////////////////////////////////////////////////////////////////////////
226
void TDB2::maybe_add_undo_point(rust::Vec<tc::Operation>& ops) {
3,612✔
227
  // Only add an UndoPoint if there are not yet any changes.
228
  if (changes.size() == 0) {
3,612✔
229
    tc::add_undo_point(ops);
1,811✔
230
  }
231
}
3,612✔
232

233
////////////////////////////////////////////////////////////////////////////////
234
void TDB2::get_changes(std::vector<Task>& changes) {
5✔
235
  std::map<std::string, Task>& changes_map = this->changes;
5✔
236
  changes.clear();
5✔
237
  std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes),
5✔
238
                 [](const auto& kv) { return kv.second; });
1✔
239
}
5✔
240

241
////////////////////////////////////////////////////////////////////////////////
242
void TDB2::gc() {
937✔
243
  Timer timer;
937✔
244

245
  // Allowed as an override, but not recommended.
246
  if (Context::getContext().config.getBoolean("gc")) {
2,811✔
247
    replica()->rebuild_working_set(true);
932✔
248
  }
249

250
  Context::getContext().time_gc_us += timer.total_us();
937✔
251
}
937✔
252

253
////////////////////////////////////////////////////////////////////////////////
254
void TDB2::expire_tasks() { replica()->expire_tasks(); }
1✔
255

256
////////////////////////////////////////////////////////////////////////////////
257
// Latest ID is that of the last pending task.
258
int TDB2::latest_id() {
180✔
259
  auto& ws = working_set();
180✔
260
  return (int)ws->largest_index();
180✔
261
}
262

263
////////////////////////////////////////////////////////////////////////////////
264
// Not cached: callers read this once per process, and it can be very large.
265
const std::vector<Task> TDB2::all_tasks() {
452✔
266
  Timer timer;
452✔
267
  auto all_tctasks = replica()->all_task_data();
452✔
268
  std::vector<Task> all;
452✔
269
  for (auto& maybe_tctask : all_tctasks) {
1,671✔
270
    auto tctask = maybe_tctask.take();
1,219✔
271
    all.push_back(Task(std::move(tctask)));
1,219✔
272
  }
1,219✔
273

274
  // Build a temporary map so that dependency_scan can resolve references
275
  // inside all_tasks.
276
  std::unordered_map<std::string, size_t> all_index;
452✔
277
  all_index.reserve(all.size());
452✔
278
  for (size_t i = 0; i < all.size(); ++i)
1,671✔
279
    all_index[all[i].get_ref("uuid")] = i;
3,657✔
280

281
  dependency_scan(all, all_index);
452✔
282

283
  Context::getContext().time_load_us += timer.total_us();
452✔
284
  return all;
904✔
285
}
452✔
286

287
////////////////////////////////////////////////////////////////////////////////
288
// Load and cache pending tasks. The first call will build the UUID index,
289
// after which it is reused.
290
const std::vector<Task>& TDB2::pending_tasks() {
6,430✔
291
  if (!_pending_tasks) {
6,430✔
292
    Timer timer;
3,699✔
293

294
    auto pending_tctasks = replica()->pending_task_data();
3,699✔
295
    std::vector<Task> result;
3,699✔
296

297
    result.reserve(pending_tctasks.size());
3,699✔
298

299
    for (auto& maybe_tctask : pending_tctasks) {
748,557✔
300
      auto tctask = maybe_tctask.take();
744,858✔
301
      result.push_back(Task(std::move(tctask)));
744,858✔
302
    }
744,858✔
303

304
    // Build a UUID map for use with get()/modify() and dependency_scan()
305
    // while the pending vector is already in memory.
306
    _pending_index.emplace();
3,699✔
307
    _pending_index->reserve(result.size());
3,699✔
308
    for (size_t i = 0, n = result.size(); i < n; ++i)
748,557✔
309
      _pending_index->emplace(result[i].get_ref("uuid"), i);
2,234,574✔
310

311
    dependency_scan(result, *_pending_index);
3,699✔
312

313
    Context::getContext().time_load_us += timer.total_us();
3,699✔
314
    _pending_tasks = std::move(result);
3,699✔
315
  }
3,699✔
316

317
  return *_pending_tasks;
6,430✔
318
}
319

320
////////////////////////////////////////////////////////////////////////////////
321
// Load and cache all completed tests by scanning all tasks,
322
// and excluding those in the working set. We cache it to speed up those reports
323
// which involve completed tasks.
324
const std::vector<Task>& TDB2::completed_tasks() {
323✔
325
  if (!_completed_tasks) {
323✔
326
    auto all_tctasks = replica()->all_task_data();
323✔
327
    auto& ws = working_set();
323✔
328

329
    std::vector<Task> result;
323✔
330

331
    result.reserve(all_tctasks.size());
323✔
332

333
    for (auto& maybe_tctask : all_tctasks) {
2,218✔
334
      auto tctask = maybe_tctask.take();
1,895✔
335
      // if this task is _not_ in the working set, return it.
336
      if (ws->by_uuid(tctask->get_uuid()) == 0) {
1,895✔
337
        result.push_back(Task(std::move(tctask)));
242✔
338
      }
339
    }
1,895✔
340
    _completed_tasks = std::move(result);
323✔
341
  }
323✔
342
  return *_completed_tasks;
323✔
343
}
344

345
/////////////////////////////////////////////////////////////////////////////////
346
// Build and return the dependency map for pending tasks.
347
// We invalidate it whenever pending_tasks may have changed.
NEW
348
const DependencyGraph& TDB2::dependency_graph() {
×
NEW
349
  if (!_dependency_graph) {
×
NEW
350
    pending_tasks();
×
351
    // reuse the UUID index
NEW
352
    _dependency_graph = build_dependency_graph(*_pending_tasks, pending_index());
×
353
  }
354

NEW
355
  return *_dependency_graph;
×
356
}
357

358
/////////////////////////////////////////////////////////////////////////////////
359
// This builds and returns the UUID map if it is missing.
360
const std::unordered_map<std::string, size_t>& TDB2::pending_index() {
3,015✔
361
  if (!_pending_index) {
3,015✔
NEW
362
    _pending_index.emplace();
×
NEW
363
    _pending_index->reserve(_pending_tasks->size());
×
NEW
364
    for (size_t i = 0, n = _pending_tasks->size(); i < n; ++i)
×
NEW
365
      _pending_index->emplace((*_pending_tasks)[i].get_ref("uuid"), i);
×
366
  }
367

368
  return *_pending_index;
3,015✔
369
}
370

371
// Finds the UUID in the index. Returns SIZE_MAX if it isn't present.
372
size_t TDB2::pending_index_of(const std::string& uuid) {
3,015✔
373
  auto& idx = pending_index();
3,015✔
374
  auto it = idx.find(uuid);
3,015✔
375
  return it != idx.end() ? it->second : SIZE_MAX;
3,015✔
376
}
377

378
////////////////////////////////////////////////////////////////////////////////
379
void TDB2::invalidate_cached_info() {
3,211✔
380
  _pending_tasks = std::nullopt;
3,211✔
381
  _completed_tasks = std::nullopt;
3,211✔
382
  _working_set = std::nullopt;
3,211✔
383
  _dependency_graph = std::nullopt;
3,211✔
384
  _pending_index = std::nullopt;
3,211✔
385
}
3,211✔
386

387
////////////////////////////////////////////////////////////////////////////////
388
// Locate task by ID, wherever it is.
389
bool TDB2::get(int id, Task& task) {
316✔
390
  auto& ws = working_set();
316✔
391
  const auto tcuuid = ws->by_index(id);
316✔
392
  if (!tcuuid.is_nil()) {
316✔
393
    std::string uuid = static_cast<std::string>(tcuuid.to_string());
304✔
394
    // Load index of pending tasks.
395
    pending_tasks();
304✔
396
    // Lookup the UUID in the index instead of scanning the vector.
397
    auto idx = pending_index_of(uuid);
304✔
398
    if (idx != SIZE_MAX) {
304✔
399
      task = (*_pending_tasks)[idx];
304✔
400
      return true;
304✔
401
    }
402
  }
304✔
403

404
  return false;
12✔
405
}
406

407
////////////////////////////////////////////////////////////////////////////////
408
// Locate task by UUID, including by partial ID, wherever it is.
409
bool TDB2::get(const std::string& uuid, Task& task) {
2,313✔
410
  pending_tasks();
2,313✔
411

412
  // Try to match exact UUID within the index.
413
  auto idx = pending_index_of(uuid);
2,313✔
414
  if (idx != SIZE_MAX) {
2,313✔
415
    task = (*_pending_tasks)[idx];
691✔
416
    return true;
691✔
417
  }
418

419
  // try a partial match
420
  if (uuid.length() < 36) {
1,622✔
421
    for (const auto& pending_task : *_pending_tasks) {
14✔
422
      if (closeEnough(pending_task.get_ref("uuid"), uuid, uuid.length())) {
42✔
423
        task = pending_task;
4✔
424
        return true;
4✔
425
      }
426
    }
427
  }
428

429
  // Nothing to do but iterate over all tasks and check whether it's closeEnough.
430
  for (auto& maybe_tctask : replica()->all_task_data()) {
733,511✔
431
    auto tctask = maybe_tctask.take();
731,921✔
432
    auto tctask_uuid = static_cast<std::string>(tctask->get_uuid().to_string());
731,921✔
433
    if (closeEnough(tctask_uuid, uuid, uuid.length())) {
731,921✔
434
      task = Task{std::move(tctask)};
28✔
435
      return true;
28✔
436
    }
437
  }
733,567✔
438

439
  return false;
1,590✔
440
}
441

442
////////////////////////////////////////////////////////////////////////////////
443
// Locate task by UUID, wherever it is.
444
bool TDB2::has(const std::string& uuid) {
×
445
  return replica()->get_task_data(tc::uuid_from_string(uuid)).is_some();
×
446
}
447

448
////////////////////////////////////////////////////////////////////////////////
449
const std::vector<Task> TDB2::siblings(Task& task) {
8✔
450
  std::vector<Task> results;
8✔
451
  if (task.has("parent")) {
16✔
452
    std::string parent = task.get("parent");
8✔
453

454
    for (auto& i : this->pending_tasks()) {
35✔
455
      // Do not include self in results.
456
      if (i.id != task.id) {
27✔
457
        // Do not include completed or deleted tasks.
458
        if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
19✔
459
          // If task has the same parent, it is a sibling.
460
          if (i.has("parent") && i.get("parent") == parent) {
77✔
461
            results.push_back(i);
10✔
462
          }
463
        }
464
      }
465
    }
466
  }
8✔
467

468
  return results;
8✔
469
}
×
470

471
////////////////////////////////////////////////////////////////////////////////
472
const std::vector<Task> TDB2::children(Task& parent) {
72✔
473
  // scan _pending_ tasks for those with `parent` equal to this task
474
  std::vector<Task> results;
72✔
475
  std::string this_uuid = parent.get("uuid");
72✔
476

477
  auto& ws = working_set();
72✔
478
  size_t end_idx = ws->largest_index();
72✔
479

480
  for (size_t i = 0; i <= end_idx; i++) {
393✔
481
    auto uuid = ws->by_index(i);
321✔
482
    if (uuid.is_nil()) {
321✔
483
      continue;
304✔
484
    }
485

486
    // skip self-references
487
    if (uuid.to_string() == this_uuid) {
249✔
488
      continue;
71✔
489
    }
490

491
    auto task_opt = replica()->get_task_data(uuid);
178✔
492
    if (task_opt.is_none()) {
178✔
493
      continue;
×
494
    }
495
    auto task = task_opt.take();
178✔
496

497
    std::string parent_uuid;
178✔
498
    if (!task->get("parent", parent_uuid)) {
534✔
499
      continue;
161✔
500
    }
501

502
    if (parent_uuid == this_uuid) {
17✔
503
      results.push_back(Task(std::move(task)));
17✔
504
    }
505
  }
339✔
506
  return results;
144✔
507
}
72✔
508

509
////////////////////////////////////////////////////////////////////////////////
510
std::string TDB2::uuid(int id) {
59✔
511
  auto& ws = working_set();
59✔
512
  auto uuid = ws->by_index(id);
59✔
513
  if (uuid.is_nil()) {
59✔
514
    return "";
2✔
515
  }
516
  return static_cast<std::string>(uuid.to_string());
58✔
517
}
518

519
////////////////////////////////////////////////////////////////////////////////
520
int TDB2::id(const std::string& uuid) {
746,364✔
521
  auto& ws = working_set();
746,364✔
522
  return ws->by_uuid(tc::uuid_from_string(uuid));
746,364✔
523
}
524

525
////////////////////////////////////////////////////////////////////////////////
526
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
5✔
527

528
////////////////////////////////////////////////////////////////////////////////
529
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
3✔
530

531
////////////////////////////////////////////////////////////////////////////////
532
// Set Task::is_blocked / Task::is_blocking flags using the pre-built UUID map
533
static void dependency_scan(std::vector<Task>& tasks, const std::unordered_map<std::string, size_t>& uuid_index) {
4,151✔
534
  for (size_t i = 0; i< tasks.size(); ++i) {
750,228✔
535
    auto lstatus = tasks[i].getStatus();
746,077✔
536
    for (const auto& dep : tasks[i].getDependencyUUIDs()) {
746,423✔
537
      auto it = uuid_index.find(dep);
346✔
538
      if (it == uuid_index.end())
346✔
539
        continue;
6✔
540

541
      size_t j = it->second;
340✔
542
      auto rstatus = tasks[j].getStatus();
340✔
543
      if (lstatus != Task::completed && lstatus != Task::deleted &&
340✔
544
          rstatus != Task::completed && rstatus != Task::deleted) {
319✔
545
        tasks[i].is_blocked = true;
310✔
546
        tasks[j].is_blocking = true;
310✔
547
      }
548
    }
746,077✔
549
  }
550
}
4,151✔
551

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

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

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

NEW
565
    for (const auto& dep : deps) {
×
NEW
566
      auto it = uuid_index.find(dep);
×
NEW
567
      if (it == uuid_index.end())
×
NEW
568
        continue;
×
569

NEW
570
      graph.dependencies[uuid].push_back(it->second);
×
NEW
571
      graph.dependents[dep].push_back(i);
×
572
    }
NEW
573
  }
×
574

575
  return graph;
×
576
}
×
577

578
////////////////////////////////////////////////////////////////////////////////
579
// 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