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

GothenburgBitFactory / taskwarrior / 9829041530

07 Jul 2024 05:19PM UTC coverage: 84.053% (-0.03%) from 84.083%
9829041530

push

github

web-flow
Remove duplicate check from task diag (#3545)

1 of 1 new or added line in 1 file covered. (100.0%)

7 existing lines in 3 files now uncovered.

19202 of 22845 relevant lines covered (84.05%)

20027.35 hits per line

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

87.31
/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
#include <TDB2.h>
29
#include <iostream>
30
#include <sstream>
31
#include <algorithm>
32
#include <vector>
33
#include <list>
34
#include <unordered_set>
35
#include <stdlib.h>
36
#include <signal.h>
37
#include <Context.h>
38
#include <Color.h>
39
#include <Datetime.h>
40
#include <Table.h>
41
#include <shared.h>
42
#include <format.h>
43
#include <main.h>
44
#include <util.h>
45
#include "tc/Server.h"
46
#include "tc/util.h"
47

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

51
////////////////////////////////////////////////////////////////////////////////
52
TDB2::TDB2 ()
4,294✔
53
: replica {tc::Replica()} // in-memory Replica
4,294✔
54
, _working_set {}
4,294✔
55
{
56
}
4,294✔
57

58
////////////////////////////////////////////////////////////////////////////////
59
void TDB2::open_replica (const std::string& location, bool create_if_missing)
4,287✔
60
{
61
  replica = tc::Replica(location, create_if_missing);
4,287✔
62
}
4,286✔
63

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

73
  std::string uuid = task.get ("uuid");
5,778✔
74
  changes[uuid] = task;
2,889✔
75

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

79
  auto innertask = replica.import_task_with_uuid (uuid);
2,882✔
80

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

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

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

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

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

111
  auto ws = replica.working_set ();
2,882✔
112

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

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

119
  if (id.has_value ()) {
2,882✔
120
      task.id = id.value();
2,767✔
121
  }
122

123
}
2,889✔
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)
507✔
141
{
142
  // All locally modified tasks are timestamped, implicitly overwriting any
143
  // changes the user or hooks tried to apply to the "modified" attribute.
144
  task.setAsNow ("modified");
507✔
145
  task.validate (false);
507✔
146
  auto uuid = task.get ("uuid");
1,014✔
147

148
  changes[uuid] = task;
507✔
149

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

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

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

189
  // we've now added and updated properties; but must find any deleted properties
190
  for (auto kv : tctask_map) {
3,390✔
191
    if (seen.find (kv.first) == seen.end ()) {
2,889✔
192
      tctask.set_value (kv.first, {});
55✔
193
    }
194
  }
2,889✔
195
}
513✔
196

197
////////////////////////////////////////////////////////////////////////////////
198
const tc::WorkingSet &TDB2::working_set ()
29,339✔
199
{
200
  if (!_working_set.has_value ()) {
29,339✔
201
    _working_set = std::make_optional (replica.working_set ());
1,709✔
202
  }
203
  return _working_set.value ();
29,339✔
204
}
205

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

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

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

265
////////////////////////////////////////////////////////////////////////////////
266
std::string TDB2::option_string(std::string input) {
48✔
267
  return input == "" ? "<empty>" : input;
48✔
268
}
269

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

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

282
  auto before = prior == "" ? Task() : Task(prior);
×
283
  auto after = Task(current);
×
284

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

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

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

306
void TDB2::gc ()
857✔
307
{
308
  Timer timer;
857✔
309

310
  // Allowed as an override, but not recommended.
311
  if (Context::getContext ().config.getBoolean ("gc"))
857✔
312
  {
313
    replica.rebuild_working_set (true);
854✔
314
  }
315

316
  Context::getContext ().time_gc_us += timer.total_us ();
857✔
317
}
857✔
318

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

327
////////////////////////////////////////////////////////////////////////////////
328
const std::vector <Task> TDB2::all_tasks ()
415✔
329
{
330
  auto all_tctasks = replica.all_tasks();
415✔
331
  std::vector <Task> all;
415✔
332
  for (auto& tctask : all_tctasks)
1,491✔
333
    all.push_back (Task (std::move (tctask)));
1,076✔
334

335
  return all;
830✔
336
}
415✔
337

338
////////////////////////////////////////////////////////////////////////////////
339
const std::vector <Task> TDB2::pending_tasks ()
3,517✔
340
{
341
  const tc::WorkingSet &ws = working_set ();
3,517✔
342
  auto largest_index = ws.largest_index ();
3,517✔
343

344
  std::vector <Task> result;
3,517✔
345
  for (size_t i = 0; i <= largest_index; i++) {
29,841✔
346
    auto maybe_uuid = ws.by_index (i);
26,324✔
347
    if (maybe_uuid.has_value ()) {
26,324✔
348
      auto maybe_task = replica.get_task (maybe_uuid.value ());
22,807✔
349
      if (maybe_task.has_value ()) {
22,807✔
350
        result.push_back (Task (std::move (maybe_task.value ())));
22,807✔
351
      }
352
    }
22,807✔
353
  }
26,324✔
354

355
  dependency_scan(result);
3,517✔
356

357
  return result;
3,517✔
358
}
359

360
////////////////////////////////////////////////////////////////////////////////
361
const std::vector <Task> TDB2::completed_tasks ()
249✔
362
{
363
  auto all_tctasks = replica.all_tasks();
249✔
364
  const tc::WorkingSet &ws = working_set ();
249✔
365

366
  std::vector <Task> result;
249✔
367
  for (auto& tctask : all_tctasks) {
1,688✔
368
    // if this task is _not_ in the working set, return it.
369
    if (!ws.by_uuid (tctask.get_uuid ())) {
1,439✔
370
      result.push_back (Task (std::move (tctask)));
214✔
371
    }
372
  }
373

374
  return result;
498✔
375
}
249✔
376

377
////////////////////////////////////////////////////////////////////////////////
378
// Locate task by ID, wherever it is.
379
bool TDB2::get (int id, Task& task)
291✔
380
{
381
  const tc::WorkingSet &ws = working_set ();
291✔
382
  const auto maybe_uuid = ws.by_index (id);
291✔
383
  if (maybe_uuid) {
291✔
384
    auto maybe_task = replica.get_task(*maybe_uuid);
279✔
385
    if (maybe_task) {
279✔
386
      task = Task{std::move(*maybe_task)};
279✔
387
      return true;
279✔
388
    }
389
  }
279✔
390

391
  return false;
12✔
392
}
291✔
393

394
////////////////////////////////////////////////////////////////////////////////
395
// Locate task by UUID, including by partial ID, wherever it is.
396
bool TDB2::get (const std::string& uuid, Task& task)
2,194✔
397
{
398
  // try by raw uuid, if the length is right
399
  if (uuid.size () == 36) {
2,194✔
400
    try {
401
      auto maybe_task = replica.get_task (uuid);
2,190✔
402
      if (maybe_task) {
2,190✔
403
        task = Task{std::move (*maybe_task)};
609✔
404
        return true;
609✔
405
      }
406
    } catch (const std::string &err) {
2,190✔
407
      return false;
×
408
    }
409
  }
410

411
  // Nothing to do but iterate over all tasks and check whether it's closeEnough
412
  for (auto& tctask : replica.all_tasks ()) {
733,432✔
413
    if (closeEnough (tctask.get_uuid (), uuid, uuid.length ())) {
731,851✔
414
      task = Task{std::move (tctask)};
4✔
415
      return true;
4✔
416
    }
417
  }
1,585✔
418

419
  return false;
1,581✔
420
}
421

422
////////////////////////////////////////////////////////////////////////////////
423
// Locate task by UUID, wherever it is.
UNCOV
424
bool TDB2::has (const std::string& uuid)
×
425
{
UNCOV
426
  Task task;
×
UNCOV
427
  return get(uuid, task);
×
428
}
429

430
////////////////////////////////////////////////////////////////////////////////
431
const std::vector <Task> TDB2::siblings (Task& task)
8✔
432
{
433
  std::vector <Task> results;
8✔
434
  if (task.has ("parent"))
8✔
435
  {
436
    std::string parent = task.get ("parent");
16✔
437

438
    for (auto& i : this->pending_tasks())
35✔
439
    {
440
      // Do not include self in results.
441
      if (i.id != task.id)
27✔
442
      {
443
        // Do not include completed or deleted tasks.
444
        if (i.getStatus () != Task::completed &&
38✔
445
            i.getStatus () != Task::deleted)
19✔
446
        {
447
          // If task has the same parent, it is a sibling.
448
          if (i.has ("parent") &&
48✔
449
              i.get ("parent") == parent)
29✔
450
          {
451
            results.push_back (i);
10✔
452
          }
453
        }
454
      }
455
    }
8✔
456
  }
8✔
457

458
  return results;
8✔
459
}
460

461
////////////////////////////////////////////////////////////////////////////////
462
const std::vector <Task> TDB2::children (Task& parent)
66✔
463
{
464
  // scan _pending_ tasks for those with `parent` equal to this task
465
        std::vector <Task> results;
66✔
466
  std::string this_uuid = parent.get ("uuid");
132✔
467

468
  const tc::WorkingSet &ws = working_set ();
66✔
469
  size_t end_idx = ws.largest_index ();
66✔
470

471
  for (size_t i = 0; i <= end_idx; i++) {
361✔
472
    auto uuid_opt = ws.by_index (i);
295✔
473
    if (!uuid_opt) {
295✔
474
      continue;
66✔
475
    }
476
    auto uuid = uuid_opt.value ();
229✔
477

478
    // skip self-references
479
    if (uuid == this_uuid) {
229✔
480
      continue;
65✔
481
    }
482

483
    auto task_opt = replica.get_task (uuid_opt.value ());
164✔
484
    if (!task_opt) {
164✔
485
      continue;
×
486
    }
487
    auto task = std::move (task_opt.value ());
164✔
488

489
    auto parent_uuid_opt = task.get_value ("parent");
328✔
490
    if (!parent_uuid_opt) {
164✔
491
      continue;
159✔
492
    }
493
    auto parent_uuid = parent_uuid_opt.value ();
5✔
494

495
    if (parent_uuid == this_uuid) {
5✔
496
      results.push_back (Task (std::move (task)));
5✔
497
    }
498
  }
996✔
499

500
  return results;
132✔
501
}
66✔
502

503
////////////////////////////////////////////////////////////////////////////////
504
std::string TDB2::uuid (int id)
56✔
505
{
506
  const tc::WorkingSet &ws = working_set ();
56✔
507
  return ws.by_index ((size_t)id).value_or ("");
112✔
508
}
509

510
////////////////////////////////////////////////////////////////////////////////
511
int TDB2::id (const std::string& uuid)
24,994✔
512
{
513
  const tc::WorkingSet &ws = working_set ();
24,994✔
514
  return (int)ws.by_uuid (uuid).value_or (0);
24,994✔
515
}
516

517
////////////////////////////////////////////////////////////////////////////////
518
int TDB2::num_local_changes ()
5✔
519
{
520
  return (int)replica.num_local_operations ();
5✔
521
}
522

523
////////////////////////////////////////////////////////////////////////////////
524
int TDB2::num_reverts_possible ()
3✔
525
{
526
  return (int)replica.num_undo_points ();
3✔
527
}
528

529
////////////////////////////////////////////////////////////////////////////////
530
void TDB2::sync (tc::Server server, bool avoid_snapshots)
×
531
{
532
  replica.sync(std::move(server), avoid_snapshots);
×
533
}
534

535
////////////////////////////////////////////////////////////////////////////////
536
void TDB2::dump ()
×
537
{
538
  // TODO
539
}
540

541
////////////////////////////////////////////////////////////////////////////////
542
// For any task that has depenencies, follow the chain of dependencies until the
543
// end.  Along the way, update the Task::is_blocked and Task::is_blocking data
544
// cache.
545
static void dependency_scan (std::vector<Task> &tasks)
3,517✔
546
{
547
  for (auto& left : tasks)
26,324✔
548
  {
549
    for (auto& dep : left.getDependencyUUIDs ())
23,501✔
550
    {
551
      for (auto& right : tasks)
1,239✔
552
      {
553
        if (right.get ("uuid") == dep)
1,229✔
554
        {
555
          // GC hasn't run yet, check both tasks for their current status
556
          Task::status lstatus = left.getStatus ();
684✔
557
          Task::status rstatus = right.getStatus ();
684✔
558
          if (lstatus != Task::completed &&
684✔
559
              lstatus != Task::deleted &&
662✔
560
              rstatus != Task::completed &&
633✔
561
              rstatus != Task::deleted)
562
          {
563
            left.is_blocked = true;
630✔
564
            right.is_blocking = true;
630✔
565
          }
566

567
          // Only want to break out of the "right" loop.
568
          break;
684✔
569
        }
570
      }
571
    }
22,807✔
572
  }
573
}
3,517✔
574

575
////////////////////////////////////////////////////////////////////////////////
576
// vim: ts=2 et sw=2
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc