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

GothenburgBitFactory / taskwarrior / 10152339701

29 Jul 2024 09:45PM UTC coverage: 84.437% (+0.07%) from 84.372%
10152339701

push

github

web-flow
Merge pull request #3566 from felixschurk/add-clang-format

Add clang-format to enforce style guide

12359 of 13760 new or added lines in 147 files covered. (89.82%)

123 existing lines in 42 files now uncovered.

19070 of 22585 relevant lines covered (84.44%)

19724.02 hits per line

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

88.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
#include "tc/Server.h"
50
#include "tc/util.h"
51

52
bool TDB2::debug_mode = false;
53
static void dependency_scan(std::vector<Task>&);
54

55
////////////////////////////////////////////////////////////////////////////////
56
TDB2::TDB2()
4,398✔
57
    : replica{tc::Replica()}  // in-memory Replica
4,398✔
58
      ,
59
      _working_set{} {}
4,398✔
60

61
////////////////////////////////////////////////////////////////////////////////
62
void TDB2::open_replica(const std::string& location, bool create_if_missing) {
4,391✔
63
  replica = tc::Replica(location, create_if_missing);
4,391✔
64
}
4,390✔
65

66
////////////////////////////////////////////////////////////////////////////////
67
// Add the new task to the replica.
68
void TDB2::add(Task& task) {
2,943✔
69
  // Ensure the task is consistent, and provide defaults if necessary.
70
  // bool argument to validate() is "applyDefault", to apply default values for
71
  // properties not otherwise given.
72
  task.validate(true);
2,943✔
73

74
  std::string uuid = task.get("uuid");
5,884✔
75
  changes[uuid] = task;
2,942✔
76

77
  // run hooks for this new task
78
  Context::getContext().hooks.onAdd(task);
2,942✔
79

80
  auto innertask = replica.import_task_with_uuid(uuid);
2,935✔
81

82
  {
83
    auto guard = replica.mutate_task(innertask);
2,935✔
84

85
    // add the task attributes
86
    for (auto& attr : task.all()) {
19,490✔
87
      // TaskChampion does not store uuid or id in the taskmap
88
      if (attr == "uuid" || attr == "id") {
16,555✔
89
        continue;
2,935✔
90
      }
91

92
      // Use `set_status` for the task status, to get expected behavior
93
      // with respect to the working set.
94
      else if (attr == "status") {
13,620✔
95
        innertask.set_status(Task::status2tc(Task::textToStatus(task.get(attr))));
2,935✔
96
      }
97

98
      // use `set_modified` to set the modified timestamp, avoiding automatic
99
      // updates to this field by TaskChampion.
100
      else if (attr == "modified") {
10,685✔
101
        auto mod = (time_t)std::stoi(task.get(attr));
2,935✔
102
        innertask.set_modified(mod);
2,935✔
103
      }
104

105
      // otherwise, just set the k/v map value
106
      else {
107
        innertask.set_value(attr, std::make_optional(task.get(attr)));
7,750✔
108
      }
109
    }
2,935✔
110
  }
2,935✔
111

112
  auto ws = replica.working_set();
2,935✔
113

114
  // get the ID that was assigned to this task
115
  auto id = ws.by_uuid(uuid);
2,935✔
116

117
  // update the cached working set with the new information
118
  _working_set = std::make_optional(std::move(ws));
2,935✔
119

120
  if (id.has_value()) {
2,935✔
121
    task.id = id.value();
2,817✔
122
  }
123
}
2,942✔
124

125
////////////////////////////////////////////////////////////////////////////////
126
// Modify the task in storage to match the given task.
127
//
128
// Note that there are a few race conditions to consider here.  Taskwarrior
129
// loads the enitre task into memory and this method then essentially writes
130
// the entire task back to the database. So, if the task in the database
131
// changes between loading the task and this method being called, this method
132
// will "revert" those changes. In practice this would only occur when multiple
133
// `task` invocatoins run at the same time and try to modify the same task.
134
//
135
// There is also the possibility that another task process has deleted the task
136
// from the database between the time this process loaded the tsak and called
137
// this method. In this case, this method throws an error that will make sense
138
// to the user. This is especially unlikely since tasks are only deleted when
139
// they have been unmodified for a long time.
140
void TDB2::modify(Task& task) {
559✔
141
  // All locally modified tasks are timestamped, implicitly overwriting any
142
  // changes the user or hooks tried to apply to the "modified" attribute.
143
  task.setAsNow("modified");
559✔
144
  task.validate(false);
559✔
145
  auto uuid = task.get("uuid");
1,118✔
146

147
  changes[uuid] = task;
559✔
148

149
  // invoke the hook and allow it to modify the task before updating
150
  Task original;
559✔
151
  get(uuid, original);
559✔
152
  Context::getContext().hooks.onModify(original, task);
559✔
153

154
  auto maybe_tctask = replica.get_task(uuid);
553✔
155
  if (!maybe_tctask.has_value()) {
553✔
NEW
156
    throw std::string("task no longer exists");
×
157
  }
158
  auto tctask = std::move(maybe_tctask.value());
553✔
159
  auto guard = replica.mutate_task(tctask);
553✔
160
  auto tctask_map = tctask.get_taskmap();
553✔
161

162
  std::unordered_set<std::string> seen;
553✔
163
  for (auto k : task.all()) {
4,821✔
164
    // ignore task keys that aren't stored
165
    if (k == "uuid") {
4,268✔
166
      continue;
553✔
167
    }
168
    seen.insert(k);
3,715✔
169
    bool update = false;
3,715✔
170
    auto v_new = task.get(k);
3,715✔
171
    try {
172
      auto v_tctask = tctask_map.at(k);
3,715✔
173
      update = v_tctask != v_new;
3,189✔
174
    } catch (const std::out_of_range& oor) {
3,715✔
175
      // tctask_map does not contain k, so update it
176
      update = true;
526✔
177
    }
526✔
178
    if (update) {
3,715✔
179
      // An empty string indicates the value should be removed.
180
      if (v_new == "") {
905✔
181
        tctask.set_value(k, {});
5✔
182
      } else {
183
        tctask.set_value(k, make_optional(v_new));
900✔
184
      }
185
    }
186
  }
4,821✔
187

188
  // we've now added and updated properties; but must find any deleted properties
189
  for (auto kv : tctask_map) {
3,801✔
190
    if (seen.find(kv.first) == seen.end()) {
3,248✔
191
      tctask.set_value(kv.first, {});
59✔
192
    }
193
  }
3,248✔
194
}
565✔
195

196
////////////////////////////////////////////////////////////////////////////////
197
void TDB2::purge(Task& task) {
6✔
198
  auto uuid = task.get("uuid");
12✔
199
  replica.delete_task(uuid);
6✔
200
}
6✔
201

202
////////////////////////////////////////////////////////////////////////////////
203
const tc::WorkingSet& TDB2::working_set() {
30,886✔
204
  if (!_working_set.has_value()) {
30,886✔
205
    _working_set = std::make_optional(replica.working_set());
1,775✔
206
  }
207
  return _working_set.value();
30,886✔
208
}
209

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

218
////////////////////////////////////////////////////////////////////////////////
219
void TDB2::revert() {
7✔
220
  auto undo_ops = replica.get_undo_ops();
7✔
221
  if (undo_ops.len == 0) {
7✔
222
    std::cout << "No operations to undo.";
×
223
    return;
×
224
  }
225
  if (confirm_revert(undo_ops)) {
7✔
226
    // Has the side-effect of freeing undo_ops.
227
    replica.commit_undo_ops(undo_ops, NULL);
4✔
228
  } else {
229
    replica.free_replica_ops(undo_ops);
3✔
230
  }
231
  replica.rebuild_working_set(false);
7✔
232
}
233

234
////////////////////////////////////////////////////////////////////////////////
235
bool TDB2::confirm_revert(struct tc::ffi::TCReplicaOpList undo_ops) {
7✔
236
  // TODO Use show_diff rather than this basic listing of operations, though
237
  // this might be a worthy undo.style itself.
238
  std::cout << "The following " << undo_ops.len << " operations would be reverted:\n";
7✔
239
  for (size_t i = 0; i < undo_ops.len; i++) {
33✔
240
    std::cout << "- ";
26✔
241
    tc::ffi::TCReplicaOp op = undo_ops.items[i];
26✔
242
    switch (op.operation_type) {
26✔
243
      case tc::ffi::TCReplicaOpType::Create:
2✔
244
        std::cout << "Create " << replica.get_op_uuid(op);
2✔
245
        break;
2✔
246
      case tc::ffi::TCReplicaOpType::Delete:
×
247
        std::cout << "Delete " << replica.get_op_old_task_description(op);
×
248
        break;
×
249
      case tc::ffi::TCReplicaOpType::Update:
24✔
250
        std::cout << "Update " << replica.get_op_uuid(op) << "\n";
24✔
NEW
251
        std::cout << "    " << replica.get_op_property(op) << ": "
×
252
                  << option_string(replica.get_op_old_value(op)) << " -> "
48✔
253
                  << option_string(replica.get_op_value(op));
48✔
254
        break;
24✔
255
      case tc::ffi::TCReplicaOpType::UndoPoint:
×
NEW
256
        throw std::string("Can't undo UndoPoint.");
×
257
        break;
258
      default:
×
NEW
259
        throw std::string("Can't undo non-operation.");
×
260
        break;
261
    }
262
    std::cout << "\n";
26✔
263
  }
264
  return !Context::getContext().config.getBoolean("confirmation") ||
20✔
265
         confirm(
13✔
266
             "The undo command is not reversible.  Are you sure you want to revert to the previous "
267
             "state?");
14✔
268
}
269

270
////////////////////////////////////////////////////////////////////////////////
271
std::string TDB2::option_string(std::string input) { return input == "" ? "<empty>" : input; }
48✔
272

273
////////////////////////////////////////////////////////////////////////////////
NEW
274
void TDB2::show_diff(const std::string& current, const std::string& prior,
×
275
                     const std::string& when) {
NEW
276
  Datetime lastChange(strtoll(when.c_str(), nullptr, 10));
×
277

278
  // Set the colors.
279
  Color color_red(
NEW
280
      Context::getContext().color() ? Context::getContext().config.get("color.undo.before") : "");
×
281
  Color color_green(
NEW
282
      Context::getContext().color() ? Context::getContext().config.get("color.undo.after") : "");
×
283

284
  auto before = prior == "" ? Task() : Task(prior);
×
285
  auto after = Task(current);
×
286

NEW
287
  if (Context::getContext().config.get("undo.style") == "side") {
×
UNCOV
288
    Table view = before.diffForUndoSide(after);
×
289

290
    std::cout << '\n'
NEW
291
              << format("The last modification was made {1}", lastChange.toString()) << '\n'
×
292
              << '\n'
NEW
293
              << view.render() << '\n';
×
294
  }
295

NEW
296
  else if (Context::getContext().config.get("undo.style") == "diff") {
×
UNCOV
297
    Table view = before.diffForUndoPatch(after, lastChange);
×
NEW
298
    std::cout << '\n' << view.render() << '\n';
×
299
  }
300
}
301

302
////////////////////////////////////////////////////////////////////////////////
303
void TDB2::gc() {
874✔
304
  Timer timer;
874✔
305

306
  // Allowed as an override, but not recommended.
307
  if (Context::getContext().config.getBoolean("gc")) {
874✔
308
    replica.rebuild_working_set(true);
871✔
309
  }
310

311
  Context::getContext().time_gc_us += timer.total_us();
874✔
312
}
874✔
313

314
////////////////////////////////////////////////////////////////////////////////
315
void TDB2::expire_tasks() { replica.expire_tasks(); }
1✔
316

317
////////////////////////////////////////////////////////////////////////////////
318
// Latest ID is that of the last pending task.
319
int TDB2::latest_id() {
166✔
320
  const tc::WorkingSet& ws = working_set();
166✔
321
  return (int)ws.largest_index();
166✔
322
}
323

324
////////////////////////////////////////////////////////////////////////////////
325
const std::vector<Task> TDB2::all_tasks() {
432✔
326
  auto all_tctasks = replica.all_tasks();
432✔
327
  std::vector<Task> all;
432✔
328
  for (auto& tctask : all_tctasks) all.push_back(Task(std::move(tctask)));
1,614✔
329

330
  return all;
864✔
331
}
432✔
332

333
////////////////////////////////////////////////////////////////////////////////
334
const std::vector<Task> TDB2::pending_tasks() {
3,649✔
335
  const tc::WorkingSet& ws = working_set();
3,649✔
336
  auto largest_index = ws.largest_index();
3,649✔
337

338
  std::vector<Task> result;
3,649✔
339
  for (size_t i = 0; i <= largest_index; i++) {
31,272✔
340
    auto maybe_uuid = ws.by_index(i);
27,623✔
341
    if (maybe_uuid.has_value()) {
27,623✔
342
      auto maybe_task = replica.get_task(maybe_uuid.value());
23,974✔
343
      if (maybe_task.has_value()) {
23,974✔
344
        result.push_back(Task(std::move(maybe_task.value())));
23,974✔
345
      }
346
    }
23,974✔
347
  }
27,623✔
348

349
  dependency_scan(result);
3,649✔
350

351
  return result;
3,649✔
352
}
353

354
////////////////////////////////////////////////////////////////////////////////
355
const std::vector<Task> TDB2::completed_tasks() {
280✔
356
  auto all_tctasks = replica.all_tasks();
280✔
357
  const tc::WorkingSet& ws = working_set();
280✔
358

359
  std::vector<Task> result;
280✔
360
  for (auto& tctask : all_tctasks) {
2,086✔
361
    // if this task is _not_ in the working set, return it.
362
    if (!ws.by_uuid(tctask.get_uuid())) {
1,806✔
363
      result.push_back(Task(std::move(tctask)));
225✔
364
    }
365
  }
366

367
  return result;
560✔
368
}
280✔
369

370
////////////////////////////////////////////////////////////////////////////////
371
// Locate task by ID, wherever it is.
372
bool TDB2::get(int id, Task& task) {
300✔
373
  const tc::WorkingSet& ws = working_set();
300✔
374
  const auto maybe_uuid = ws.by_index(id);
300✔
375
  if (maybe_uuid) {
300✔
376
    auto maybe_task = replica.get_task(*maybe_uuid);
288✔
377
    if (maybe_task) {
288✔
378
      task = Task{std::move(*maybe_task)};
288✔
379
      return true;
288✔
380
    }
381
  }
288✔
382

383
  return false;
12✔
384
}
300✔
385

386
////////////////////////////////////////////////////////////////////////////////
387
// Locate task by UUID, including by partial ID, wherever it is.
388
bool TDB2::get(const std::string& uuid, Task& task) {
2,266✔
389
  // try by raw uuid, if the length is right
390
  if (uuid.size() == 36) {
2,266✔
391
    try {
392
      auto maybe_task = replica.get_task(uuid);
2,262✔
393
      if (maybe_task) {
2,262✔
394
        task = Task{std::move(*maybe_task)};
675✔
395
        return true;
675✔
396
      }
397
    } catch (const std::string& err) {
2,262✔
398
      return false;
×
399
    }
400
  }
401

402
  // Nothing to do but iterate over all tasks and check whether it's closeEnough
403
  for (auto& tctask : replica.all_tasks()) {
733,452✔
404
    if (closeEnough(tctask.get_uuid(), uuid, uuid.length())) {
731,865✔
405
      task = Task{std::move(tctask)};
4✔
406
      return true;
4✔
407
    }
408
  }
1,591✔
409

410
  return false;
1,587✔
411
}
412

413
////////////////////////////////////////////////////////////////////////////////
414
// Locate task by UUID, wherever it is.
NEW
415
bool TDB2::has(const std::string& uuid) {
×
UNCOV
416
  Task task;
×
417
  return get(uuid, task);
×
418
}
419

420
////////////////////////////////////////////////////////////////////////////////
421
const std::vector<Task> TDB2::siblings(Task& task) {
8✔
422
  std::vector<Task> results;
8✔
423
  if (task.has("parent")) {
8✔
424
    std::string parent = task.get("parent");
16✔
425

426
    for (auto& i : this->pending_tasks()) {
35✔
427
      // Do not include self in results.
428
      if (i.id != task.id) {
27✔
429
        // Do not include completed or deleted tasks.
430
        if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) {
19✔
431
          // If task has the same parent, it is a sibling.
432
          if (i.has("parent") && i.get("parent") == parent) {
19✔
433
            results.push_back(i);
10✔
434
          }
435
        }
436
      }
437
    }
8✔
438
  }
8✔
439

440
  return results;
8✔
441
}
442

443
////////////////////////////////////////////////////////////////////////////////
444
const std::vector<Task> TDB2::children(Task& parent) {
71✔
445
  // scan _pending_ tasks for those with `parent` equal to this task
446
  std::vector<Task> results;
71✔
447
  std::string this_uuid = parent.get("uuid");
142✔
448

449
  const tc::WorkingSet& ws = working_set();
71✔
450
  size_t end_idx = ws.largest_index();
71✔
451

452
  for (size_t i = 0; i <= end_idx; i++) {
387✔
453
    auto uuid_opt = ws.by_index(i);
316✔
454
    if (!uuid_opt) {
316✔
455
      continue;
71✔
456
    }
457
    auto uuid = uuid_opt.value();
245✔
458

459
    // skip self-references
460
    if (uuid == this_uuid) {
245✔
461
      continue;
70✔
462
    }
463

464
    auto task_opt = replica.get_task(uuid_opt.value());
175✔
465
    if (!task_opt) {
175✔
466
      continue;
×
467
    }
468
    auto task = std::move(task_opt.value());
175✔
469

470
    auto parent_uuid_opt = task.get_value("parent");
350✔
471
    if (!parent_uuid_opt) {
175✔
472
      continue;
161✔
473
    }
474
    auto parent_uuid = parent_uuid_opt.value();
14✔
475

476
    if (parent_uuid == this_uuid) {
14✔
477
      results.push_back(Task(std::move(task)));
14✔
478
    }
479
  }
1,030✔
480

481
  return results;
142✔
482
}
71✔
483

484
////////////////////////////////////////////////////////////////////////////////
485
std::string TDB2::uuid(int id) {
58✔
486
  const tc::WorkingSet& ws = working_set();
58✔
487
  return ws.by_index((size_t)id).value_or("");
116✔
488
}
489

490
////////////////////////////////////////////////////////////////////////////////
491
int TDB2::id(const std::string& uuid) {
26,362✔
492
  const tc::WorkingSet& ws = working_set();
26,362✔
493
  return (int)ws.by_uuid(uuid).value_or(0);
26,362✔
494
}
495

496
////////////////////////////////////////////////////////////////////////////////
497
int TDB2::num_local_changes() { return (int)replica.num_local_operations(); }
5✔
498

499
////////////////////////////////////////////////////////////////////////////////
500
int TDB2::num_reverts_possible() { return (int)replica.num_undo_points(); }
3✔
501

502
////////////////////////////////////////////////////////////////////////////////
503
void TDB2::sync(tc::Server server, bool avoid_snapshots) {
2✔
504
  replica.sync(std::move(server), avoid_snapshots);
2✔
505
}
2✔
506

507
////////////////////////////////////////////////////////////////////////////////
NEW
508
void TDB2::dump() {
×
509
  // TODO
510
}
511

512
////////////////////////////////////////////////////////////////////////////////
513
// For any task that has depenencies, follow the chain of dependencies until the
514
// end.  Along the way, update the Task::is_blocked and Task::is_blocking data
515
// cache.
516
static void dependency_scan(std::vector<Task>& tasks) {
3,649✔
517
  for (auto& left : tasks) {
27,623✔
518
    for (auto& dep : left.getDependencyUUIDs()) {
24,679✔
519
      for (auto& right : tasks) {
1,270✔
520
        if (right.get("uuid") == dep) {
1,258✔
521
          // GC hasn't run yet, check both tasks for their current status
522
          Task::status lstatus = left.getStatus();
693✔
523
          Task::status rstatus = right.getStatus();
693✔
524
          if (lstatus != Task::completed && lstatus != Task::deleted &&
693✔
525
              rstatus != Task::completed && rstatus != Task::deleted) {
642✔
526
            left.is_blocked = true;
635✔
527
            right.is_blocking = true;
635✔
528
          }
529

530
          // Only want to break out of the "right" loop.
531
          break;
693✔
532
        }
533
      }
534
    }
23,974✔
535
  }
536
}
3,649✔
537

538
////////////////////////////////////////////////////////////////////////////////
539
// 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