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

GothenburgBitFactory / taskwarrior / 27993474965

23 Jun 2026 12:26AM UTC coverage: 84.968% (-0.09%) from 85.053%
27993474965

Pull #4127

github

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

70 of 93 new or added lines in 1 file covered. (75.27%)

4 existing lines in 3 files now uncovered.

19631 of 23104 relevant lines covered (84.97%)

24018.32 hits per line

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

89.63
/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,032✔
158
        tctask->update_remove(k, ops);
4✔
159
      } else {
160
        tctask->update(k, v_new, ops);
1,028✔
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
  if (_pending_tasks) {
398✔
186
    auto idx = pending_index_of(uuid);
398✔
187
    if (idx != SIZE_MAX) (*_pending_tasks)[idx] = task;
398✔
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,335✔
212
  // One of the open_replica_ methods must be called before this one.
213
  assert(_replica);
16,335✔
214
  return _replica.value();
16,335✔
215
}
216

217
////////////////////////////////////////////////////////////////////////////////
218
const rust::Box<tc::WorkingSet>& TDB2::working_set() {
750,239✔
219
  if (!_working_set.has_value()) {
750,239✔
220
    _working_set = replica()->working_set();
4,983✔
221
  }
222
  return _working_set.value();
750,239✔
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) all_index[all[i].get_ref("uuid")] = i;
2,890✔
279

280
  dependency_scan(all, all_index);
452✔
281

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

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

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

296
    result.reserve(pending_tctasks.size());
3,705✔
297

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

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

310
    dependency_scan(result, *_pending_index);
3,705✔
311

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

316
  return *_pending_tasks;
6,502✔
317
}
318

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

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

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

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

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

NEW
354
  return *_dependency_graph;
×
355
}
356

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

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

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

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

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

403
  return false;
12✔
404
}
405

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

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

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

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

438
  return false;
1,590✔
439
}
440

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

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

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

467
  return results;
8✔
468
}
×
469

470
////////////////////////////////////////////////////////////////////////////////
471
// Return the child tasks of a parent. Uses the _pending_tasks cache, to avoid
472
// having to fetch this info from the Rust replica.
473
const std::vector<Task> TDB2::children(Task& parent) {
72✔
474
  std::vector<Task> results;
72✔
475
  const auto& this_uuid = parent.get_ref("uuid");
72✔
476

477
  for (const auto& i : pending_tasks()) {
321✔
478
    if (i.get_ref("uuid") == this_uuid) continue;
498✔
479
    if (i.has("parent") && i.get("parent") == this_uuid) results.push_back(i);
568✔
480
  }
481
  return results;
72✔
UNCOV
482
}
×
483

484
////////////////////////////////////////////////////////////////////////////////
485
std::string TDB2::uuid(int id) {
59✔
486
  auto& ws = working_set();
59✔
487
  auto uuid = ws->by_index(id);
59✔
488
  if (uuid.is_nil()) {
59✔
489
    return "";
2✔
490
  }
491
  return static_cast<std::string>(uuid.to_string());
58✔
492
}
493

494
////////////////////////////////////////////////////////////////////////////////
495
int TDB2::id(const std::string& uuid) {
746,356✔
496
  auto& ws = working_set();
746,356✔
497
  return ws->by_uuid(tc::uuid_from_string(uuid));
746,356✔
498
}
499

500
////////////////////////////////////////////////////////////////////////////////
501
int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); }
5✔
502

503
////////////////////////////////////////////////////////////////////////////////
504
int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); }
3✔
505

506
////////////////////////////////////////////////////////////////////////////////
507
// Set Task::is_blocked / Task::is_blocking flags using the pre-built UUID map
508
static void dependency_scan(std::vector<Task>& tasks,
4,157✔
509
                            const std::unordered_map<std::string, size_t>& uuid_index) {
510
  for (size_t i = 0; i < tasks.size(); ++i) {
750,243✔
511
    auto lstatus = tasks[i].getStatus();
746,086✔
512
    for (const auto& dep : tasks[i].getDependencyUUIDs()) {
746,432✔
513
      auto it = uuid_index.find(dep);
346✔
514
      if (it == uuid_index.end()) continue;
346✔
515

516
      size_t j = it->second;
340✔
517
      auto rstatus = tasks[j].getStatus();
340✔
518
      if (lstatus != Task::completed && lstatus != Task::deleted && rstatus != Task::completed &&
340✔
519
          rstatus != Task::deleted) {
520
        tasks[i].is_blocked = true;
311✔
521
        tasks[j].is_blocking = true;
311✔
522
      }
523
    }
746,086✔
524
  }
525
}
4,157✔
526

527
/////////////////////////////////////////////////////////////////////////////////
528
// Build the full dependency map from the task vector.
NEW
529
static DependencyGraph build_dependency_graph(
×
530
    const std::vector<Task>& tasks, const std::unordered_map<std::string, size_t>& uuid_index) {
NEW
531
  DependencyGraph graph;
×
532

NEW
533
  for (size_t i = 0; i < tasks.size(); ++i) {
×
NEW
534
    const auto& deps = tasks[i].getDependencyUUIDs();
×
NEW
535
    if (deps.empty()) continue;
×
536

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

NEW
539
    for (const auto& dep : deps) {
×
NEW
540
      auto it = uuid_index.find(dep);
×
NEW
541
      if (it == uuid_index.end()) continue;
×
542

NEW
543
      graph.dependencies[uuid].push_back(it->second);
×
NEW
544
      graph.dependents[dep].push_back(i);
×
545
    }
NEW
546
  }
×
547

NEW
548
  return graph;
×
NEW
549
}
×
550

551
////////////////////////////////////////////////////////////////////////////////
552
// 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