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

GothenburgBitFactory / taskwarrior / 22980971839

12 Mar 2026 12:27AM UTC coverage: 85.221%. Remained the same
22980971839

push

github

web-flow
Add cooldown to dependabot (#4069)

* Add cooldown to dependabot

This prevents dependabot from proposing an update soon after it is
released. This helps avoid buggy updates, and also provides adequate
time for "supply chain attacks" to be discovered and yanked.

* Group updates into a single PR daily

19611 of 23012 relevant lines covered (85.22%)

23539.12 hits per line

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

76.69
/src/sort.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 <Context.h>
31
#include <Duration.h>
32
#include <Task.h>
33
#include <format.h>
34
#include <shared.h>
35
#include <stdlib.h>
36
#include <util.h>
37

38
#include <algorithm>
39
#include <functional>
40
#include <list>
41
#include <map>
42
#include <random>
43
#include <string>
44
#include <vector>
45

46
static std::vector<Task>* global_data = nullptr;
47
static std::vector<std::string> global_keys;
48
static unsigned int sort_random_seed = 0;
49
static bool sort_compare(int, int);
50

51
////////////////////////////////////////////////////////////////////////////////
52
void sort_tasks(std::vector<Task>& data, std::vector<int>& order, const std::string& keys) {
716✔
53
  Timer timer;
716✔
54
  global_data = &data;
716✔
55

56
  // Split the key defs.
57
  global_keys = split(keys, ',');
716✔
58

59
  // Generate a random seend for sorting by "random".
60
  if (sort_random_seed == 0) {
716✔
61
    // For testing purposes, allow the seed to be specified in an undocumented configuration
62
    // setting.
63
    std::string seed_str = Context::getContext().config.get("debug.random.seed");
1,432✔
64
    if (seed_str.empty()) {
716✔
65
      std::random_device rd;
716✔
66
      sort_random_seed = rd();
716✔
67
    } else {
716✔
68
      sort_random_seed = std::stoul(seed_str);
×
69
    }
70
  }
716✔
71

72
  // Only sort if necessary.
73
  if (order.size()) std::stable_sort(order.begin(), order.end(), sort_compare);
716✔
74

75
  Context::getContext().time_sort_us += timer.total_us();
716✔
76
}
716✔
77

78
void sort_projects(std::list<std::pair<std::string, int>>& sorted,
16✔
79
                   std::map<std::string, int>& allProjects) {
80
  for (auto& project : allProjects) {
46✔
81
    const std::vector<std::string> parents = extractParents(project.first);
30✔
82
    if (parents.size()) {
30✔
83
      // if parents exist: store iterator position of last parent
84
      std::list<std::pair<std::string, int>>::iterator parent_pos;
6✔
85
      for (auto& parent : parents) {
12✔
86
        parent_pos = std::find_if(
6✔
87
            sorted.begin(), sorted.end(),
88
            [&parent](const std::pair<std::string, int>& item) { return item.first == parent; });
17✔
89

90
        // if parent does not exist yet: insert into sorted view
91
        if (parent_pos == sorted.end()) sorted.emplace_back(parent, 1);
6✔
92
      }
93

94
      // insert new element below latest parent
95
      sorted.insert((parent_pos == sorted.end()) ? parent_pos : ++parent_pos, project);
6✔
96
    } else {
97
      // if has no parents: simply push to end of list
98
      sorted.push_back(project);
24✔
99
    }
100
  }
30✔
101
}
16✔
102

103
void sort_projects(std::list<std::pair<std::string, int>>& sorted,
6✔
104
                   std::map<std::string, bool>& allProjects) {
105
  std::map<std::string, int> allProjectsInt;
6✔
106
  for (auto& p : allProjects) allProjectsInt[p.first] = (int)p.second;
17✔
107

108
  sort_projects(sorted, allProjectsInt);
6✔
109
}
6✔
110

111
////////////////////////////////////////////////////////////////////////////////
112
// Re-implementation, using direct Task access instead of data copies that
113
// require re-parsing.
114
//
115
// Essentially a static implementation of a dynamic operator<.
116
static bool sort_compare(int left, int right) {
2,007✔
117
  std::string field;
2,007✔
118
  bool ascending;
119
  bool breakIndicator;
120
  Column* column;
121
  int left_number;
122
  int right_number;
123
  float left_real;
124
  float right_real;
125

126
  for (auto& k : global_keys) {
3,885✔
127
    Context::getContext().decomposeSortField(k, field, ascending, breakIndicator);
3,535✔
128

129
    // Random.
130
    if (field == "random") {
3,535✔
131
      // For "random" sort, we produce a stable number for each task based on a hash of its
132
      // UUID plus the random seed.
133
      std::string left_uuid = (*global_data)[left].get("uuid");
×
134
      std::string right_uuid = (*global_data)[right].get("uuid");
×
135

136
      std::string left_scrambled =
137
          std::to_string(std::hash<std::string>{}(left_uuid + std::to_string(sort_random_seed)));
×
138
      std::string right_scrambled =
139
          std::to_string(std::hash<std::string>{}(right_uuid + std::to_string(sort_random_seed)));
×
140

141
      if (left_scrambled == right_scrambled) continue;
×
142

143
      return ascending ? (left_scrambled < right_scrambled) : (left_scrambled > right_scrambled);
×
144
    }
×
145

146
    // Urgency.
147
    else if (field == "urgency") {
3,535✔
148
      left_real = (*global_data)[left].urgency();
143✔
149
      right_real = (*global_data)[right].urgency();
143✔
150

151
      if (left_real == right_real) continue;
143✔
152

153
      return ascending ? (left_real < right_real) : (left_real > right_real);
57✔
154
    }
155

156
    // Number.
157
    else if (field == "id") {
3,392✔
158
      left_number = (*global_data)[left].id;
304✔
159
      right_number = (*global_data)[right].id;
304✔
160

161
      if (left_number == right_number) continue;
304✔
162

163
      return ascending ? (left_number < right_number) : (left_number > right_number);
92✔
164
    }
165

166
    // String.
167
    else if (field == "description" || field == "project" || field == "status" || field == "tags" ||
5,520✔
168
             field == "uuid" || field == "parent" || field == "imask" || field == "mask") {
5,520✔
169
      auto left_string = (*global_data)[left].get_ref(field);
1,116✔
170
      auto right_string = (*global_data)[right].get_ref(field);
1,116✔
171

172
      if (left_string == right_string) continue;
1,116✔
173

174
      return ascending ? (left_string < right_string) : (left_string > right_string);
967✔
175
    }
2,232✔
176

177
    // Due Date.
178
    else if (field == "due" || field == "end" || field == "entry" || field == "start" ||
3,500✔
179
             field == "until" || field == "wait" || field == "modified" || field == "scheduled") {
3,500✔
180
      auto left_string = (*global_data)[left].get_ref(field);
1,920✔
181
      auto right_string = (*global_data)[right].get_ref(field);
1,920✔
182

183
      if (left_string != "" && right_string == "") return true;
1,920✔
184

185
      if (left_string == "" && right_string != "") return false;
1,899✔
186

187
      if (left_string == right_string) continue;
1,896✔
188

189
      return ascending ? (left_string < right_string) : (left_string > right_string);
466✔
190
    }
3,840✔
191

192
    // Depends string.
193
    else if (field == "depends") {
52✔
194
      // Raw data is an un-sorted list of UUIDs.  We just need a stable
195
      // sort, so we sort them lexically.
196
      auto left_deps = (*global_data)[left].getDependencyUUIDs();
×
197
      std::sort(left_deps.begin(), left_deps.end());
×
198
      auto right_deps = (*global_data)[right].getDependencyUUIDs();
×
199
      std::sort(right_deps.begin(), right_deps.end());
×
200

201
      if (left_deps == right_deps) continue;
×
202

203
      if (left_deps.size() == 0 && right_deps.size() > 0) return ascending;
×
204

205
      if (left_deps.size() > 0 && right_deps.size() == 0) return !ascending;
×
206

207
      // Sort on the first dependency.
208
      left_number = Context::getContext().tdb2.id(left_deps[0]);
×
209
      right_number = Context::getContext().tdb2.id(right_deps[0]);
×
210

211
      if (left_number == right_number) continue;
×
212

213
      return ascending ? (left_number < right_number) : (left_number > right_number);
×
214
    }
×
215

216
    // Duration.
217
    else if (field == "recur") {
52✔
218
      auto left_string = (*global_data)[left].get_ref(field);
7✔
219
      auto right_string = (*global_data)[right].get_ref(field);
7✔
220

221
      if (left_string == right_string) continue;
7✔
222

223
      Duration left_duration(left_string);
7✔
224
      Duration right_duration(right_string);
7✔
225
      return ascending ? (left_duration < right_duration) : (left_duration > right_duration);
7✔
226
    }
14✔
227

228
    // UDAs.
229
    else if ((column = Context::getContext().columns[field]) != nullptr) {
45✔
230
      std::string type = column->type();
45✔
231
      if (type == "numeric") {
45✔
232
        auto left_real = strtof(((*global_data)[left].get_ref(field)).c_str(), nullptr);
11✔
233
        auto right_real = strtof(((*global_data)[right].get_ref(field)).c_str(), nullptr);
11✔
234

235
        if (left_real == right_real) continue;
11✔
236

237
        return ascending ? (left_real < right_real) : (left_real > right_real);
11✔
238
      } else if (type == "string") {
34✔
239
        auto left_string = (*global_data)[left].get_ref(field);
33✔
240
        auto right_string = (*global_data)[right].get_ref(field);
33✔
241

242
        if (left_string == right_string) continue;
33✔
243

244
        // UDAs of the type string can have custom sort orders, which need to be considered.
245
        auto order = Task::customOrder.find(field);
32✔
246
        if (order != Task::customOrder.end()) {
32✔
247
          // Guaranteed to be found, because of ColUDA::validate ().
248
          auto posLeft = std::find(order->second.begin(), order->second.end(), left_string);
23✔
249
          auto posRight = std::find(order->second.begin(), order->second.end(), right_string);
23✔
250
          return ascending ? (posLeft < posRight) : (posLeft > posRight);
23✔
251
        } else {
252
          // Empty values are unconditionally last, if no custom order was specified.
253
          if (left_string == "")
9✔
254
            return false;
5✔
255
          else if (right_string == "")
4✔
256
            return true;
2✔
257

258
          return ascending ? (left_string < right_string) : (left_string > right_string);
2✔
259
        }
260
      }
66✔
261

262
      else if (type == "date") {
1✔
263
        auto left_string = (*global_data)[left].get_ref(field);
1✔
264
        auto right_string = (*global_data)[right].get_ref(field);
1✔
265

266
        if (left_string != "" && right_string == "") return true;
1✔
267

268
        if (left_string == "" && right_string != "") return false;
×
269

270
        if (left_string == right_string) continue;
×
271

272
        return ascending ? (left_string < right_string) : (left_string > right_string);
×
273
      } else if (type == "duration") {
2✔
274
        auto left_string = (*global_data)[left].get_ref(field);
×
275
        auto right_string = (*global_data)[right].get_ref(field);
×
276

277
        if (left_string == right_string) continue;
×
278

279
        Duration left_duration(left_string);
×
280
        Duration right_duration(right_string);
×
281
        return ascending ? (left_duration < right_duration) : (left_duration > right_duration);
×
282
      }
×
283
    } else
45✔
284
      throw format("The '{1}' column is not a valid sort field.", field);
×
285
  }
286

287
  return false;
350✔
288
}
2,007✔
289

290
////////////////////////////////////////////////////////////////////////////////
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