rugged 0.23.3 → 0.24.0b0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/ext/rugged/rugged.c +24 -0
  4. data/ext/rugged/rugged_config.c +65 -0
  5. data/ext/rugged/rugged_remote.c +22 -2
  6. data/ext/rugged/rugged_repo.c +10 -5
  7. data/ext/rugged/rugged_tree.c +4 -1
  8. data/lib/rugged/version.rb +1 -1
  9. data/vendor/libgit2/CMakeLists.txt +47 -2
  10. data/vendor/libgit2/include/git2/config.h +18 -0
  11. data/vendor/libgit2/include/git2/diff.h +25 -2
  12. data/vendor/libgit2/include/git2/errors.h +0 -12
  13. data/vendor/libgit2/include/git2/index.h +11 -0
  14. data/vendor/libgit2/include/git2/remote.h +12 -1
  15. data/vendor/libgit2/include/git2/sys/config.h +14 -0
  16. data/vendor/libgit2/include/git2/sys/filter.h +4 -1
  17. data/vendor/libgit2/include/git2/sys/odb_backend.h +4 -0
  18. data/vendor/libgit2/include/git2/sys/refdb_backend.h +5 -4
  19. data/vendor/libgit2/include/git2/sys/transport.h +27 -0
  20. data/vendor/libgit2/include/git2/transport.h +25 -21
  21. data/vendor/libgit2/include/git2/version.h +2 -2
  22. data/vendor/libgit2/libgit2.pc.in +3 -2
  23. data/vendor/libgit2/src/branch.c +1 -12
  24. data/vendor/libgit2/src/checkout.c +29 -20
  25. data/vendor/libgit2/src/clone.c +2 -2
  26. data/vendor/libgit2/src/common.h +13 -4
  27. data/vendor/libgit2/src/config.c +36 -0
  28. data/vendor/libgit2/src/config.h +15 -0
  29. data/vendor/libgit2/src/config_file.c +124 -20
  30. data/vendor/libgit2/src/config_file.h +10 -0
  31. data/vendor/libgit2/src/curl_stream.c +7 -7
  32. data/vendor/libgit2/src/diff.c +89 -27
  33. data/vendor/libgit2/src/diff_print.c +1 -1
  34. data/vendor/libgit2/src/errors.c +75 -40
  35. data/vendor/libgit2/src/filebuf.c +81 -3
  36. data/vendor/libgit2/src/fileops.c +176 -75
  37. data/vendor/libgit2/src/fileops.h +7 -10
  38. data/vendor/libgit2/src/filter.c +5 -2
  39. data/vendor/libgit2/src/global.c +25 -9
  40. data/vendor/libgit2/src/global.h +1 -0
  41. data/vendor/libgit2/src/idxmap.h +92 -0
  42. data/vendor/libgit2/src/ignore.c +9 -7
  43. data/vendor/libgit2/src/index.c +246 -46
  44. data/vendor/libgit2/src/index.h +2 -0
  45. data/vendor/libgit2/src/iterator.c +377 -118
  46. data/vendor/libgit2/src/iterator.h +28 -20
  47. data/vendor/libgit2/src/merge.c +26 -13
  48. data/vendor/libgit2/src/notes.c +1 -1
  49. data/vendor/libgit2/src/odb.c +1 -2
  50. data/vendor/libgit2/src/odb_loose.c +2 -2
  51. data/vendor/libgit2/src/odb_mempack.c +6 -2
  52. data/vendor/libgit2/src/oidmap.h +2 -0
  53. data/vendor/libgit2/src/openssl_stream.c +9 -3
  54. data/vendor/libgit2/src/path.c +37 -2
  55. data/vendor/libgit2/src/path.h +12 -1
  56. data/vendor/libgit2/src/pathspec.c +12 -12
  57. data/vendor/libgit2/src/push.c +2 -1
  58. data/vendor/libgit2/src/push.h +1 -0
  59. data/vendor/libgit2/src/refdb.c +2 -6
  60. data/vendor/libgit2/src/refdb_fs.c +13 -3
  61. data/vendor/libgit2/src/remote.c +44 -15
  62. data/vendor/libgit2/src/repository.c +28 -12
  63. data/vendor/libgit2/src/stash.c +17 -12
  64. data/vendor/libgit2/src/stransport_stream.c +1 -1
  65. data/vendor/libgit2/src/submodule.c +234 -152
  66. data/vendor/libgit2/src/sysdir.c +22 -8
  67. data/vendor/libgit2/src/transaction.c +41 -0
  68. data/vendor/libgit2/src/transaction.h +14 -0
  69. data/vendor/libgit2/src/transports/cred.c +8 -0
  70. data/vendor/libgit2/src/transports/http.c +6 -0
  71. data/vendor/libgit2/src/transports/smart.c +95 -0
  72. data/vendor/libgit2/src/transports/smart.h +1 -0
  73. data/vendor/libgit2/src/transports/smart_pkt.c +9 -2
  74. data/vendor/libgit2/src/transports/ssh.c +5 -3
  75. data/vendor/libgit2/src/transports/winhttp.c +19 -1
  76. data/vendor/libgit2/src/unix/posix.h +14 -1
  77. data/vendor/libgit2/src/util.c +56 -13
  78. data/vendor/libgit2/src/util.h +13 -5
  79. data/vendor/libgit2/src/win32/path_w32.c +15 -8
  80. data/vendor/libgit2/src/win32/posix_w32.c +11 -2
  81. data/vendor/libgit2/src/win32/w32_crtdbg_stacktrace.c +343 -0
  82. data/vendor/libgit2/src/win32/w32_crtdbg_stacktrace.h +93 -0
  83. data/vendor/libgit2/src/win32/w32_stack.c +192 -0
  84. data/vendor/libgit2/src/win32/w32_stack.h +138 -0
  85. data/vendor/libgit2/src/win32/w32_util.c +29 -5
  86. data/vendor/libgit2/src/win32/w32_util.h +13 -3
  87. metadata +11 -5
@@ -10,6 +10,7 @@
10
10
  #include "fileops.h"
11
11
  #include "filebuf.h"
12
12
  #include "vector.h"
13
+ #include "idxmap.h"
13
14
  #include "tree-cache.h"
14
15
  #include "git2/odb.h"
15
16
  #include "git2/index.h"
@@ -25,6 +26,7 @@ struct git_index {
25
26
  git_oid checksum; /* checksum at the end of the file */
26
27
 
27
28
  git_vector entries;
29
+ git_idxmap *entries_map;
28
30
 
29
31
  git_mutex lock; /* lock held while entries is being changed */
30
32
  git_vector deleted; /* deleted entries if readers > 0 */
@@ -31,14 +31,22 @@
31
31
  (P)->base.cb = &(P)->cb; \
32
32
  ITERATOR_SET_CB(P,NAME_LC); \
33
33
  (P)->base.repo = (REPO); \
34
- (P)->base.start = start ? git__strdup(start) : NULL; \
35
- (P)->base.end = end ? git__strdup(end) : NULL; \
36
- if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \
34
+ (P)->base.start = options && options->start ? \
35
+ git__strdup(options->start) : NULL; \
36
+ (P)->base.end = options && options->end ? \
37
+ git__strdup(options->end) : NULL; \
38
+ if ((options && options->start && !(P)->base.start) || \
39
+ (options && options->end && !(P)->base.end)) { \
37
40
  git__free(P); return -1; } \
41
+ (P)->base.strcomp = git__strcmp; \
42
+ (P)->base.strncomp = git__strncmp; \
38
43
  (P)->base.prefixcomp = git__prefixcmp; \
39
- (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \
44
+ (P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \
40
45
  if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \
41
46
  (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \
47
+ if (options && options->pathlist.count && \
48
+ iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \
49
+ git__free(P); return -1; } \
42
50
  } while (0)
43
51
 
44
52
  #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0)
@@ -56,6 +64,139 @@
56
64
  (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0)
57
65
 
58
66
 
67
+ typedef enum {
68
+ ITERATOR_PATHLIST_NONE = 0,
69
+ ITERATOR_PATHLIST_MATCH = 1,
70
+ ITERATOR_PATHLIST_MATCH_DIRECTORY = 2,
71
+ ITERATOR_PATHLIST_MATCH_CHILD = 3,
72
+ } iterator_pathlist__match_t;
73
+
74
+ static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec)
75
+ {
76
+ size_t i;
77
+
78
+ if (git_vector_init(&iter->pathlist, pathspec->count,
79
+ (git_vector_cmp)iter->strcomp) < 0)
80
+ return -1;
81
+
82
+ for (i = 0; i < pathspec->count; i++) {
83
+ if (!pathspec->strings[i])
84
+ continue;
85
+
86
+ if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0)
87
+ return -1;
88
+ }
89
+
90
+ git_vector_sort(&iter->pathlist);
91
+
92
+ return 0;
93
+ }
94
+
95
+ static iterator_pathlist__match_t iterator_pathlist__match(
96
+ git_iterator *iter, const char *path, size_t path_len)
97
+ {
98
+ const char *p;
99
+ size_t idx;
100
+ int error;
101
+
102
+ error = git_vector_bsearch2(&idx, &iter->pathlist,
103
+ (git_vector_cmp)iter->strcomp, path);
104
+
105
+ if (error == 0)
106
+ return ITERATOR_PATHLIST_MATCH;
107
+
108
+ /* at this point, the path we're examining may be a directory (though we
109
+ * don't know that yet, since we're avoiding a stat unless it's necessary)
110
+ * so see if the pathlist contains a file beneath this directory.
111
+ */
112
+ while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) {
113
+ if (iter->prefixcomp(p, path) != 0)
114
+ break;
115
+
116
+ /* an exact match would have been matched by the bsearch above */
117
+ assert(p[path_len]);
118
+
119
+ /* is this a literal directory entry (eg `foo/`) or a file beneath */
120
+ if (p[path_len] == '/') {
121
+ return (p[path_len+1] == '\0') ?
122
+ ITERATOR_PATHLIST_MATCH_DIRECTORY :
123
+ ITERATOR_PATHLIST_MATCH_CHILD;
124
+ }
125
+
126
+ if (p[path_len] > '/')
127
+ break;
128
+
129
+ idx++;
130
+ }
131
+
132
+ return ITERATOR_PATHLIST_NONE;
133
+ }
134
+
135
+ static void iterator_pathlist_walk__reset(git_iterator *iter)
136
+ {
137
+ iter->pathlist_walk_idx = 0;
138
+ }
139
+
140
+ /* walker for the index iterator that allows it to walk the sorted pathlist
141
+ * entries alongside the sorted index entries. the `iter->pathlist_walk_idx`
142
+ * stores the starting position for subsequent calls, the position is advanced
143
+ * along with the index iterator, with a special case for handling directories
144
+ * in the pathlist that are specified without trailing '/'. (eg, `foo`).
145
+ * we do not advance over these entries until we're certain that the index
146
+ * iterator will not ask us for a file beneath that directory (eg, `foo/bar`).
147
+ */
148
+ static bool iterator_pathlist_walk__contains(git_iterator *iter, const char *path)
149
+ {
150
+ size_t i;
151
+ char *p;
152
+ size_t p_len;
153
+ int cmp;
154
+
155
+ for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) {
156
+ p = iter->pathlist.contents[i];
157
+ p_len = strlen(p);
158
+
159
+ /* see if the pathlist entry is a prefix of this path */
160
+ cmp = iter->strncomp(p, path, p_len);
161
+
162
+ /* this pathlist entry sorts before the given path, try the next */
163
+ if (!p_len || cmp < 0)
164
+ iter->pathlist_walk_idx++;
165
+
166
+ /* this pathlist sorts after the given path, no match. */
167
+ else if (cmp > 0)
168
+ return false;
169
+
170
+ /* match! an exact match (`foo` vs `foo`), the path is a child of an
171
+ * explicit directory in the pathlist (`foo/` vs `foo/bar`) or the path
172
+ * is a child of an entry in the pathlist (`foo` vs `foo/bar`)
173
+ */
174
+ else if (path[p_len] == '\0' || p[p_len - 1] == '/' || path[p_len] == '/')
175
+ return true;
176
+
177
+ /* only advance the start index for future callers if we know that we
178
+ * will not see a child of this path. eg, a pathlist entry `foo` is
179
+ * a prefix for `foo.txt` and `foo/bar`. don't advance the start
180
+ * pathlist index when we see `foo.txt` or we would miss a subsequent
181
+ * inspection of `foo/bar`. only advance when there are no more
182
+ * potential children.
183
+ */
184
+ else if (path[p_len] > '/')
185
+ iter->pathlist_walk_idx++;
186
+ }
187
+
188
+ return false;
189
+ }
190
+
191
+ static void iterator_pathlist__update_ignore_case(git_iterator *iter)
192
+ {
193
+ git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp);
194
+ git_vector_sort(&iter->pathlist);
195
+
196
+ iter->pathlist_walk_idx = 0;
197
+ }
198
+
199
+
59
200
  static int iterator__reset_range(
60
201
  git_iterator *iter, const char *start, const char *end)
61
202
  {
@@ -82,7 +223,8 @@ static int iterator__update_ignore_case(
82
223
  git_iterator *iter,
83
224
  git_iterator_flag_t flags)
84
225
  {
85
- int error = 0, ignore_case = -1;
226
+ bool ignore_case;
227
+ int error;
86
228
 
87
229
  if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
88
230
  ignore_case = true;
@@ -91,19 +233,29 @@ static int iterator__update_ignore_case(
91
233
  else {
92
234
  git_index *index;
93
235
 
94
- if (!(error = git_repository_index__weakptr(&index, iter->repo)))
95
- ignore_case = (index->ignore_case != false);
236
+ if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0)
237
+ return error;
238
+
239
+ ignore_case = (index->ignore_case == 1);
96
240
  }
97
241
 
98
- if (ignore_case > 0)
242
+ if (ignore_case) {
99
243
  iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE);
100
- else if (ignore_case == 0)
244
+
245
+ iter->strcomp = git__strcasecmp;
246
+ iter->strncomp = git__strncasecmp;
247
+ iter->prefixcomp = git__prefixcmp_icase;
248
+ } else {
101
249
  iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE);
102
250
 
103
- iter->prefixcomp = iterator__ignore_case(iter) ?
104
- git__prefixcmp_icase : git__prefixcmp;
251
+ iter->strcomp = git__strcmp;
252
+ iter->strncomp = git__strncmp;
253
+ iter->prefixcomp = git__prefixcmp;
254
+ }
255
+
256
+ iterator_pathlist__update_ignore_case(iter);
105
257
 
106
- return error;
258
+ return 0;
107
259
  }
108
260
 
109
261
  GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry)
@@ -149,9 +301,7 @@ typedef struct {
149
301
 
150
302
  int git_iterator_for_nothing(
151
303
  git_iterator **iter,
152
- git_iterator_flag_t flags,
153
- const char *start,
154
- const char *end)
304
+ git_iterator_options *options)
155
305
  {
156
306
  empty_iterator *i = git__calloc(1, sizeof(empty_iterator));
157
307
  GITERR_CHECK_ALLOC(i);
@@ -162,7 +312,7 @@ int git_iterator_for_nothing(
162
312
 
163
313
  ITERATOR_BASE_INIT(i, empty, EMPTY, NULL);
164
314
 
165
- if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
315
+ if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
166
316
  i->base.flags |= GIT_ITERATOR_IGNORE_CASE;
167
317
 
168
318
  *iter = (git_iterator *)i;
@@ -201,7 +351,6 @@ typedef struct {
201
351
  int path_ambiguities;
202
352
  bool path_has_filename;
203
353
  bool entry_is_current;
204
- int (*strncomp)(const char *a, const char *b, size_t sz);
205
354
  } tree_iterator;
206
355
 
207
356
  static char *tree_iterator__current_filename(
@@ -271,7 +420,7 @@ static int tree_iterator__search_cmp(const void *key, const void *val, void *p)
271
420
  return git_path_cmp(
272
421
  tf->start, tf->startlen, false,
273
422
  te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE,
274
- ((tree_iterator *)p)->strncomp);
423
+ ((git_iterator *)p)->strncomp);
275
424
  }
276
425
 
277
426
  static bool tree_iterator__move_to_next(
@@ -303,7 +452,7 @@ static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf)
303
452
  for (; tf->next < tf->n_entries; tf->next++, last = te) {
304
453
  te = tf->entries[tf->next]->te;
305
454
 
306
- if (last && tree_iterator__te_cmp(last, te, ti->strncomp))
455
+ if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp))
307
456
  break;
308
457
 
309
458
  /* try to load trees for items in [current,next) range */
@@ -468,7 +617,7 @@ static int tree_iterator__update_entry(tree_iterator *ti)
468
617
  return 0;
469
618
  }
470
619
 
471
- static int tree_iterator__current(
620
+ static int tree_iterator__current_internal(
472
621
  const git_index_entry **entry, git_iterator *self)
473
622
  {
474
623
  int error;
@@ -491,41 +640,32 @@ static int tree_iterator__current(
491
640
  return 0;
492
641
  }
493
642
 
494
- static int tree_iterator__advance_into(
495
- const git_index_entry **entry, git_iterator *self)
643
+ static int tree_iterator__advance_into_internal(git_iterator *self)
496
644
  {
497
645
  int error = 0;
498
646
  tree_iterator *ti = (tree_iterator *)self;
499
647
 
500
- iterator__clear_entry(entry);
501
-
502
648
  if (tree_iterator__at_tree(ti))
503
649
  error = tree_iterator__push_frame(ti);
504
650
 
505
- if (!error && entry)
506
- error = tree_iterator__current(entry, self);
507
-
508
651
  return error;
509
652
  }
510
653
 
511
- static int tree_iterator__advance(
512
- const git_index_entry **entry, git_iterator *self)
654
+ static int tree_iterator__advance_internal(git_iterator *self)
513
655
  {
514
656
  int error;
515
657
  tree_iterator *ti = (tree_iterator *)self;
516
658
  tree_iterator_frame *tf = ti->head;
517
659
 
518
- iterator__clear_entry(entry);
519
-
520
660
  if (tf->current >= tf->n_entries)
521
661
  return GIT_ITEROVER;
522
662
 
523
663
  if (!iterator__has_been_accessed(ti))
524
- return tree_iterator__current(entry, self);
664
+ return 0;
525
665
 
526
666
  if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) &&
527
667
  tree_iterator__at_tree(ti))
528
- return tree_iterator__advance_into(entry, self);
668
+ return tree_iterator__advance_into_internal(self);
529
669
 
530
670
  if (ti->path_has_filename) {
531
671
  git_buf_rtruncate_at_char(&ti->path, '/');
@@ -534,7 +674,7 @@ static int tree_iterator__advance(
534
674
 
535
675
  /* scan forward and up, advancing in frame or popping frame when done */
536
676
  while (!tree_iterator__move_to_next(ti, tf) &&
537
- tree_iterator__pop_frame(ti, false))
677
+ tree_iterator__pop_frame(ti, false))
538
678
  tf = ti->head;
539
679
 
540
680
  /* find next and load trees */
@@ -543,7 +683,63 @@ static int tree_iterator__advance(
543
683
 
544
684
  /* deal with include_trees / auto_expand as needed */
545
685
  if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti))
546
- return tree_iterator__advance_into(entry, self);
686
+ return tree_iterator__advance_into_internal(self);
687
+
688
+ return 0;
689
+ }
690
+
691
+ static int tree_iterator__current(
692
+ const git_index_entry **out, git_iterator *self)
693
+ {
694
+ const git_index_entry *entry = NULL;
695
+ iterator_pathlist__match_t m;
696
+ int error;
697
+
698
+ do {
699
+ if ((error = tree_iterator__current_internal(&entry, self)) < 0)
700
+ return error;
701
+
702
+ if (self->pathlist.length) {
703
+ m = iterator_pathlist__match(
704
+ self, entry->path, strlen(entry->path));
705
+
706
+ if (m != ITERATOR_PATHLIST_MATCH) {
707
+ if ((error = tree_iterator__advance_internal(self)) < 0)
708
+ return error;
709
+
710
+ entry = NULL;
711
+ }
712
+ }
713
+ } while (!entry);
714
+
715
+ if (out)
716
+ *out = entry;
717
+
718
+ return error;
719
+ }
720
+
721
+ static int tree_iterator__advance(
722
+ const git_index_entry **entry, git_iterator *self)
723
+ {
724
+ int error = tree_iterator__advance_internal(self);
725
+
726
+ iterator__clear_entry(entry);
727
+
728
+ if (error < 0)
729
+ return error;
730
+
731
+ return tree_iterator__current(entry, self);
732
+ }
733
+
734
+ static int tree_iterator__advance_into(
735
+ const git_index_entry **entry, git_iterator *self)
736
+ {
737
+ int error = tree_iterator__advance_into_internal(self);
738
+
739
+ iterator__clear_entry(entry);
740
+
741
+ if (error < 0)
742
+ return error;
547
743
 
548
744
  return tree_iterator__current(entry, self);
549
745
  }
@@ -607,15 +803,13 @@ static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree)
607
803
  int git_iterator_for_tree(
608
804
  git_iterator **iter,
609
805
  git_tree *tree,
610
- git_iterator_flag_t flags,
611
- const char *start,
612
- const char *end)
806
+ git_iterator_options *options)
613
807
  {
614
808
  int error;
615
809
  tree_iterator *ti;
616
810
 
617
811
  if (tree == NULL)
618
- return git_iterator_for_nothing(iter, flags, start, end);
812
+ return git_iterator_for_nothing(iter, options);
619
813
 
620
814
  if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0)
621
815
  return error;
@@ -625,9 +819,8 @@ int git_iterator_for_tree(
625
819
 
626
820
  ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree));
627
821
 
628
- if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0)
822
+ if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0)
629
823
  goto fail;
630
- ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp;
631
824
 
632
825
  if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 ||
633
826
  (error = tree_iterator__create_root_frame(ti, tree)) < 0 ||
@@ -650,6 +843,8 @@ typedef struct {
650
843
  git_vector entries;
651
844
  git_vector_cmp entry_srch;
652
845
  size_t current;
846
+ /* when limiting with a pathlist, this is the current index into it */
847
+ size_t pathlist_idx;
653
848
  /* when not in autoexpand mode, use these to represent "tree" state */
654
849
  git_buf partial;
655
850
  size_t partial_pos;
@@ -669,15 +864,35 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii)
669
864
  return ie;
670
865
  }
671
866
 
672
- static const git_index_entry *index_iterator__advance_over_conflicts(index_iterator *ii)
867
+ static const git_index_entry *index_iterator__advance_over_unwanted(
868
+ index_iterator *ii)
673
869
  {
674
870
  const git_index_entry *ie = index_iterator__index_entry(ii);
871
+ bool match;
675
872
 
676
- if (!iterator__include_conflicts(ii)) {
677
- while (ie && git_index_entry_is_conflict(ie)) {
873
+ while (ie) {
874
+ if (!iterator__include_conflicts(ii) &&
875
+ git_index_entry_is_conflict(ie)) {
678
876
  ii->current++;
679
877
  ie = index_iterator__index_entry(ii);
878
+ continue;
680
879
  }
880
+
881
+ /* if we have a pathlist, this entry's path must be in it to be
882
+ * returned. walk the pathlist in unison with the index to
883
+ * compare paths.
884
+ */
885
+ if (ii->base.pathlist.length) {
886
+ match = iterator_pathlist_walk__contains(&ii->base, ie->path);
887
+
888
+ if (!match) {
889
+ ii->current++;
890
+ ie = index_iterator__index_entry(ii);
891
+ continue;
892
+ }
893
+ }
894
+
895
+ break;
681
896
  }
682
897
 
683
898
  return ie;
@@ -706,7 +921,7 @@ static void index_iterator__next_prefix_tree(index_iterator *ii)
706
921
 
707
922
  static int index_iterator__first_prefix_tree(index_iterator *ii)
708
923
  {
709
- const git_index_entry *ie = index_iterator__advance_over_conflicts(ii);
924
+ const git_index_entry *ie = index_iterator__advance_over_unwanted(ii);
710
925
  const char *scan, *prior, *slash;
711
926
 
712
927
  if (!ie || !iterator__include_trees(ii))
@@ -825,11 +1040,16 @@ static int index_iterator__reset(
825
1040
 
826
1041
  ii->current = 0;
827
1042
 
1043
+ iterator_pathlist_walk__reset(self);
1044
+
1045
+ /* if we're given a start prefix, find it; if we're given a pathlist, find
1046
+ * the first of those. start at the later of the two.
1047
+ */
828
1048
  if (ii->base.start)
829
1049
  git_index_snapshot_find(
830
1050
  &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0);
831
1051
 
832
- if ((ie = index_iterator__advance_over_conflicts(ii)) == NULL)
1052
+ if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL)
833
1053
  return 0;
834
1054
 
835
1055
  if (git_buf_sets(&ii->partial, ie->path) < 0)
@@ -860,9 +1080,7 @@ static void index_iterator__free(git_iterator *self)
860
1080
  int git_iterator_for_index(
861
1081
  git_iterator **iter,
862
1082
  git_index *index,
863
- git_iterator_flag_t flags,
864
- const char *start,
865
- const char *end)
1083
+ git_iterator_options *options)
866
1084
  {
867
1085
  int error = 0;
868
1086
  index_iterator *ii = git__calloc(1, sizeof(index_iterator));
@@ -876,7 +1094,7 @@ int git_iterator_for_index(
876
1094
 
877
1095
  ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index));
878
1096
 
879
- if ((error = iterator__update_ignore_case((git_iterator *)ii, flags)) < 0) {
1097
+ if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) {
880
1098
  git_iterator_free((git_iterator *)ii);
881
1099
  return error;
882
1100
  }
@@ -916,6 +1134,7 @@ struct fs_iterator {
916
1134
  size_t root_len;
917
1135
  uint32_t dirload_flags;
918
1136
  int depth;
1137
+ iterator_pathlist__match_t pathlist_match;
919
1138
 
920
1139
  int (*enter_dir_cb)(fs_iterator *self);
921
1140
  int (*leave_dir_cb)(fs_iterator *self);
@@ -926,6 +1145,7 @@ struct fs_iterator {
926
1145
 
927
1146
  typedef struct {
928
1147
  struct stat st;
1148
+ iterator_pathlist__match_t pathlist_match;
929
1149
  size_t path_len;
930
1150
  char path[GIT_FLEX_ARRAY];
931
1151
  } fs_iterator_path_with_stat;
@@ -1007,28 +1227,20 @@ static void fs_iterator__seek_frame_start(
1007
1227
  ff->index = 0;
1008
1228
  }
1009
1229
 
1010
- static int dirload_with_stat(
1011
- const char *dirpath,
1012
- size_t prefix_len,
1013
- unsigned int flags,
1014
- const char *start_stat,
1015
- const char *end_stat,
1016
- git_vector *contents)
1230
+ static int dirload_with_stat(git_vector *contents, fs_iterator *fi)
1017
1231
  {
1018
1232
  git_path_diriter diriter = GIT_PATH_DIRITER_INIT;
1019
1233
  const char *path;
1020
- int (*strncomp)(const char *a, const char *b, size_t sz);
1021
- size_t start_len = start_stat ? strlen(start_stat) : 0;
1022
- size_t end_len = end_stat ? strlen(end_stat) : 0;
1234
+ size_t start_len = fi->base.start ? strlen(fi->base.start) : 0;
1235
+ size_t end_len = fi->base.end ? strlen(fi->base.end) : 0;
1023
1236
  fs_iterator_path_with_stat *ps;
1024
1237
  size_t path_len, cmp_len, ps_size;
1238
+ iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH;
1025
1239
  int error;
1026
1240
 
1027
- strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ?
1028
- git__strncasecmp : git__strncmp;
1029
-
1030
1241
  /* Any error here is equivalent to the dir not existing, skip over it */
1031
- if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0) {
1242
+ if ((error = git_path_diriter_init(
1243
+ &diriter, fi->path.ptr, fi->dirload_flags)) < 0) {
1032
1244
  error = GIT_ENOTFOUND;
1033
1245
  goto done;
1034
1246
  }
@@ -1037,18 +1249,31 @@ static int dirload_with_stat(
1037
1249
  if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0)
1038
1250
  goto done;
1039
1251
 
1040
- assert(path_len > prefix_len);
1252
+ assert(path_len > fi->root_len);
1041
1253
 
1042
1254
  /* remove the prefix if requested */
1043
- path += prefix_len;
1044
- path_len -= prefix_len;
1255
+ path += fi->root_len;
1256
+ path_len -= fi->root_len;
1045
1257
 
1046
1258
  /* skip if before start_stat or after end_stat */
1047
1259
  cmp_len = min(start_len, path_len);
1048
- if (cmp_len && strncomp(path, start_stat, cmp_len) < 0)
1260
+ if (cmp_len && fi->base.strncomp(path, fi->base.start, cmp_len) < 0)
1049
1261
  continue;
1262
+ /* skip if after end_stat */
1050
1263
  cmp_len = min(end_len, path_len);
1051
- if (cmp_len && strncomp(path, end_stat, cmp_len) > 0)
1264
+ if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0)
1265
+ continue;
1266
+
1267
+ /* if we have a pathlist that we're limiting to, examine this path.
1268
+ * if the frame has already deemed us inside the path (eg, we're in
1269
+ * `foo/bar` and the pathlist previously was detected to say `foo/`)
1270
+ * then simply continue. otherwise, examine the pathlist looking for
1271
+ * this path or children of this path.
1272
+ */
1273
+ if (fi->base.pathlist.length &&
1274
+ fi->pathlist_match != ITERATOR_PATHLIST_MATCH &&
1275
+ fi->pathlist_match != ITERATOR_PATHLIST_MATCH_DIRECTORY &&
1276
+ !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len)))
1052
1277
  continue;
1053
1278
 
1054
1279
  /* Make sure to append two bytes, one for the path's null
@@ -1062,6 +1287,8 @@ static int dirload_with_stat(
1062
1287
 
1063
1288
  memcpy(ps->path, path, path_len);
1064
1289
 
1290
+ /* TODO: don't stat if assume unchanged for this path */
1291
+
1065
1292
  if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) {
1066
1293
  if (error == GIT_ENOTFOUND) {
1067
1294
  /* file was removed between readdir and lstat */
@@ -1069,6 +1296,12 @@ static int dirload_with_stat(
1069
1296
  continue;
1070
1297
  }
1071
1298
 
1299
+ if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) {
1300
+ /* were looking for a directory, but this is a file */
1301
+ git__free(ps);
1302
+ continue;
1303
+ }
1304
+
1072
1305
  /* Treat the file as unreadable if we get any other error */
1073
1306
  memset(&ps->st, 0, sizeof(ps->st));
1074
1307
  ps->st.st_mode = GIT_FILEMODE_UNREADABLE;
@@ -1085,6 +1318,11 @@ static int dirload_with_stat(
1085
1318
  continue;
1086
1319
  }
1087
1320
 
1321
+ /* record whether this path was explicitly found in the path list
1322
+ * or whether we're only examining it because something beneath it
1323
+ * is in the path list.
1324
+ */
1325
+ ps->pathlist_match = pathlist_match;
1088
1326
  git_vector_insert(contents, ps);
1089
1327
  }
1090
1328
 
@@ -1114,13 +1352,11 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
1114
1352
  ff = fs_iterator__alloc_frame(fi);
1115
1353
  GITERR_CHECK_ALLOC(ff);
1116
1354
 
1117
- error = dirload_with_stat(
1118
- fi->path.ptr, fi->root_len, fi->dirload_flags,
1119
- fi->base.start, fi->base.end, &ff->entries);
1355
+ error = dirload_with_stat(&ff->entries, fi);
1120
1356
 
1121
1357
  if (error < 0) {
1122
1358
  git_error_state last_error = { 0 };
1123
- giterr_capture(&last_error, error);
1359
+ giterr_state_capture(&last_error, error);
1124
1360
 
1125
1361
  /* these callbacks may clear the error message */
1126
1362
  fs_iterator__free_frame(ff);
@@ -1128,7 +1364,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi)
1128
1364
  /* next time return value we skipped to */
1129
1365
  fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS;
1130
1366
 
1131
- return giterr_restore(&last_error);
1367
+ return giterr_state_restore(&last_error);
1132
1368
  }
1133
1369
 
1134
1370
  if (ff->entries.length == 0) {
@@ -1196,19 +1432,14 @@ static int fs_iterator__advance_into(
1196
1432
  return error;
1197
1433
  }
1198
1434
 
1199
- static int fs_iterator__advance_over(
1200
- const git_index_entry **entry, git_iterator *self)
1435
+ static void fs_iterator__advance_over_internal(git_iterator *self)
1201
1436
  {
1202
- int error = 0;
1203
1437
  fs_iterator *fi = (fs_iterator *)self;
1204
1438
  fs_iterator_frame *ff;
1205
1439
  fs_iterator_path_with_stat *next;
1206
1440
 
1207
- if (entry != NULL)
1208
- *entry = NULL;
1209
-
1210
1441
  while (fi->entry.path != NULL) {
1211
- ff = fi->stack;
1442
+ ff = fi->stack;
1212
1443
  next = git_vector_get(&ff->entries, ++ff->index);
1213
1444
 
1214
1445
  if (next != NULL)
@@ -1216,8 +1447,19 @@ static int fs_iterator__advance_over(
1216
1447
 
1217
1448
  fs_iterator__pop_frame(fi, ff, false);
1218
1449
  }
1450
+ }
1219
1451
 
1220
- error = fs_iterator__update_entry(fi);
1452
+ static int fs_iterator__advance_over(
1453
+ const git_index_entry **entry, git_iterator *self)
1454
+ {
1455
+ int error;
1456
+
1457
+ if (entry != NULL)
1458
+ *entry = NULL;
1459
+
1460
+ fs_iterator__advance_over_internal(self);
1461
+
1462
+ error = fs_iterator__update_entry((fs_iterator *)self);
1221
1463
 
1222
1464
  if (!error && entry != NULL)
1223
1465
  error = fs_iterator__current(entry, self);
@@ -1294,40 +1536,50 @@ static int fs_iterator__update_entry(fs_iterator *fi)
1294
1536
  {
1295
1537
  fs_iterator_path_with_stat *ps;
1296
1538
 
1297
- memset(&fi->entry, 0, sizeof(fi->entry));
1539
+ while (true) {
1540
+ memset(&fi->entry, 0, sizeof(fi->entry));
1298
1541
 
1299
- if (!fi->stack)
1300
- return GIT_ITEROVER;
1542
+ if (!fi->stack)
1543
+ return GIT_ITEROVER;
1301
1544
 
1302
- ps = git_vector_get(&fi->stack->entries, fi->stack->index);
1303
- if (!ps)
1304
- return GIT_ITEROVER;
1545
+ ps = git_vector_get(&fi->stack->entries, fi->stack->index);
1546
+ if (!ps)
1547
+ return GIT_ITEROVER;
1305
1548
 
1306
- git_buf_truncate(&fi->path, fi->root_len);
1307
- if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
1308
- return -1;
1549
+ git_buf_truncate(&fi->path, fi->root_len);
1550
+ if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0)
1551
+ return -1;
1309
1552
 
1310
- if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
1311
- return GIT_ITEROVER;
1553
+ if (iterator__past_end(fi, fi->path.ptr + fi->root_len))
1554
+ return GIT_ITEROVER;
1312
1555
 
1313
- fi->entry.path = ps->path;
1314
- git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
1556
+ fi->entry.path = ps->path;
1557
+ fi->pathlist_match = ps->pathlist_match;
1558
+ git_index_entry__init_from_stat(&fi->entry, &ps->st, true);
1315
1559
 
1316
- /* need different mode here to keep directories during iteration */
1317
- fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
1560
+ /* need different mode here to keep directories during iteration */
1561
+ fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode);
1318
1562
 
1319
- /* allow wrapper to check/update the entry (can force skip) */
1320
- if (fi->update_entry_cb &&
1321
- fi->update_entry_cb(fi) == GIT_ENOTFOUND)
1322
- return fs_iterator__advance_over(NULL, (git_iterator *)fi);
1563
+ /* allow wrapper to check/update the entry (can force skip) */
1564
+ if (fi->update_entry_cb &&
1565
+ fi->update_entry_cb(fi) == GIT_ENOTFOUND) {
1566
+ fs_iterator__advance_over_internal(&fi->base);
1567
+ continue;
1568
+ }
1323
1569
 
1324
- /* if this is a tree and trees aren't included, then skip */
1325
- if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
1326
- int error = fs_iterator__advance_into(NULL, (git_iterator *)fi);
1327
- if (error != GIT_ENOTFOUND)
1328
- return error;
1329
- giterr_clear();
1330
- return fs_iterator__advance_over(NULL, (git_iterator *)fi);
1570
+ /* if this is a tree and trees aren't included, then skip */
1571
+ if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) {
1572
+ int error = fs_iterator__advance_into(NULL, &fi->base);
1573
+
1574
+ if (error != GIT_ENOTFOUND)
1575
+ return error;
1576
+
1577
+ giterr_clear();
1578
+ fs_iterator__advance_over_internal(&fi->base);
1579
+ continue;
1580
+ }
1581
+
1582
+ break;
1331
1583
  }
1332
1584
 
1333
1585
  return 0;
@@ -1343,6 +1595,7 @@ static int fs_iterator__initialize(
1343
1595
  return -1;
1344
1596
  }
1345
1597
  fi->root_len = fi->path.size;
1598
+ fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD;
1346
1599
 
1347
1600
  fi->dirload_flags =
1348
1601
  (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) |
@@ -1366,16 +1619,14 @@ static int fs_iterator__initialize(
1366
1619
  int git_iterator_for_filesystem(
1367
1620
  git_iterator **out,
1368
1621
  const char *root,
1369
- git_iterator_flag_t flags,
1370
- const char *start,
1371
- const char *end)
1622
+ git_iterator_options *options)
1372
1623
  {
1373
1624
  fs_iterator *fi = git__calloc(1, sizeof(fs_iterator));
1374
1625
  GITERR_CHECK_ALLOC(fi);
1375
1626
 
1376
1627
  ITERATOR_BASE_INIT(fi, fs, FS, NULL);
1377
1628
 
1378
- if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0)
1629
+ if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0)
1379
1630
  fi->base.flags |= GIT_ITERATOR_IGNORE_CASE;
1380
1631
 
1381
1632
  return fs_iterator__initialize(out, fi, root);
@@ -1559,9 +1810,7 @@ int git_iterator_for_workdir_ext(
1559
1810
  const char *repo_workdir,
1560
1811
  git_index *index,
1561
1812
  git_tree *tree,
1562
- git_iterator_flag_t flags,
1563
- const char *start,
1564
- const char *end)
1813
+ git_iterator_options *options)
1565
1814
  {
1566
1815
  int error, precompose = 0;
1567
1816
  workdir_iterator *wi;
@@ -1583,7 +1832,7 @@ int git_iterator_for_workdir_ext(
1583
1832
  wi->fi.leave_dir_cb = workdir_iterator__leave_dir;
1584
1833
  wi->fi.update_entry_cb = workdir_iterator__update_entry;
1585
1834
 
1586
- if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 ||
1835
+ if ((error = iterator__update_ignore_case((git_iterator *)wi, options ? options->flags : 0)) < 0 ||
1587
1836
  (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0)
1588
1837
  {
1589
1838
  git_iterator_free((git_iterator *)wi);
@@ -1618,6 +1867,7 @@ void git_iterator_free(git_iterator *iter)
1618
1867
 
1619
1868
  iter->cb->free(iter);
1620
1869
 
1870
+ git_vector_free(&iter->pathlist);
1621
1871
  git__free(iter->start);
1622
1872
  git__free(iter->end);
1623
1873
 
@@ -1687,7 +1937,7 @@ int git_iterator_current_parent_tree(
1687
1937
  if (!(tf = tf->down) ||
1688
1938
  tf->current >= tf->n_entries ||
1689
1939
  !(te = tf->entries[tf->current]->te) ||
1690
- ti->strncomp(scan, te->filename, te->filename_len) != 0)
1940
+ ti->base.strncomp(scan, te->filename, te->filename_len) != 0)
1691
1941
  return 0;
1692
1942
 
1693
1943
  scan += te->filename_len;
@@ -1820,9 +2070,18 @@ int git_iterator_advance_over_with_status(
1820
2070
 
1821
2071
  if (!error)
1822
2072
  continue;
2073
+
1823
2074
  else if (error == GIT_ENOTFOUND) {
2075
+ /* we entered this directory only hoping to find child matches to
2076
+ * our pathlist (eg, this is `foo` and we had a pathlist entry for
2077
+ * `foo/bar`). it should not be ignored, it should be excluded.
2078
+ */
2079
+ if (wi->fi.pathlist_match == ITERATOR_PATHLIST_MATCH_CHILD)
2080
+ *status = GIT_ITERATOR_STATUS_FILTERED;
2081
+ else
2082
+ wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
2083
+
1824
2084
  error = 0;
1825
- wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */
1826
2085
  } else
1827
2086
  break; /* real error, stop here */
1828
2087
  } else {