Skip to content

Commit cf441ce

Browse files
jochenhzPawel Czarnecki
authored and
Pawel Czarnecki
committed
patch in sprase-checkout from libgit2#6394
1 parent 1b2d0ef commit cf441ce

File tree

93 files changed

+3299
-177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+3299
-177
lines changed

include/git2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include "git2/revparse.h"
6161
#include "git2/revwalk.h"
6262
#include "git2/signature.h"
63+
#include "git2/sparse.h"
6364
#include "git2/stash.h"
6465
#include "git2/status.h"
6566
#include "git2/submodule.h"

include/git2/checkout.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,12 @@ typedef enum {
186186
/** Include common ancestor data in zdiff3 format for conflicts */
187187
GIT_CHECKOUT_CONFLICT_STYLE_ZDIFF3 = (1u << 25),
188188

189+
/**
190+
* Remove files that are excluded by the sparse-checkout ruleset.
191+
* Does nothing when GIT_CHECKOUT_SAFE is set.
192+
*/
193+
GIT_CHECKOUT_REMOVE_SPARSE_FILES = (1u << 26),
194+
189195
/**
190196
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
191197
*/

include/git2/diff.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,11 @@ typedef struct {
445445
* Defaults to "b".
446446
*/
447447
const char *new_prefix;
448+
449+
/** Skip files in the diff that are excluded by the `sparse-checkout` file.
450+
* Set to 1 to skip sparse files, 0 otherwise
451+
*/
452+
int skip_sparse_files;
448453
} git_diff_options;
449454

450455
/* The current version of the diff options structure */

include/git2/sparse.h

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright (C) the libgit2 contributors. All rights reserved.
3+
*
4+
* This file is part of libgit2, distributed under the GNU GPL v2 with
5+
* a Linking Exception. For full terms see the included COPYING file.
6+
*/
7+
#ifndef INCLUDE_git_sparse_h__
8+
#define INCLUDE_git_sparse_h__
9+
10+
#include "common.h"
11+
#include "types.h"
12+
13+
GIT_BEGIN_DECL
14+
15+
typedef struct {
16+
unsigned int version; /**< The version */
17+
18+
/**
19+
* Set to zero (false) to consider sparse-checkout patterns as
20+
* full patterns, or non-zero for cone patterns.
21+
*/
22+
/* int cone; */
23+
} git_sparse_checkout_init_options;
24+
25+
#define GIT_SPARSE_CHECKOUT_INIT_OPTIONS_VERSION 1
26+
#define GIT_SPARSE_CHECKOUT_INIT_OPTIONS_INIT {GIT_SPARSE_CHECKOUT_INIT_OPTIONS_VERSION};
27+
28+
/**
29+
* Enable the core.sparseCheckout setting. If the sparse-checkout
30+
* file does not exist, then populate it with patterns that match
31+
* every file in the root directory and no other directories.
32+
*
33+
* @param repo Repository where to find the sparse-checkout file
34+
* @param opts The `git_sparse_checkout_init_options` when
35+
* initializing the sparse-checkout file
36+
* @return 0 or an error code
37+
*/
38+
GIT_EXTERN(int) git_sparse_checkout_init(
39+
git_repository *repo,
40+
git_sparse_checkout_init_options *opts);
41+
42+
/**
43+
* Fill a list with all the patterns in the sparse-checkout file
44+
*
45+
* @param patterns Pointer to a git_strarray structure where
46+
* the patterns will be stored
47+
* @param repo Repository where to find the sparse-checkout file
48+
* @return 0 or an error code
49+
*/
50+
GIT_EXTERN(int) git_sparse_checkout_list(
51+
git_strarray *patterns,
52+
git_repository *repo);
53+
54+
/**
55+
* Write a set of patterns to the sparse-checkout file.
56+
* Update the working directory to match the new patterns.
57+
* Enable the core.sparseCheckout config setting if it is not
58+
* already enabled.
59+
*
60+
* @param repo Repository where to find the sparse-checkout file
61+
* @param patterns Pointer to a git_strarray structure where
62+
* the patterns to set can be found
63+
* @return 0 or an error code
64+
*/
65+
GIT_EXTERN(int) git_sparse_checkout_set(
66+
git_repository *repo,
67+
git_strarray *patterns);
68+
69+
/**
70+
* Update the sparse-checkout file to include additional patterns.
71+
*
72+
* @param repo Repository where to find the sparse-checkout file
73+
* @param patterns Pointer to a git_strarray structure where
74+
* the patterns to set can be found
75+
* @return 0 or an error code
76+
*/
77+
GIT_EXTERN(int) git_sparse_checkout_add(
78+
git_repository *repo,
79+
git_strarray *patterns);
80+
81+
GIT_EXTERN(int) git_sparse_checkout_reapply(git_repository *repo);
82+
83+
/**
84+
* Disable the core.sparseCheckout config setting, and restore the
85+
* working directory to include all files. Leaves the sparse-checkout
86+
* file intact so a later git sparse-checkout init command may return
87+
* the working directory to the same state.
88+
*
89+
* @param repo Repository where to find the sparse-checkout file
90+
* @return 0 or an error code
91+
*/
92+
GIT_EXTERN(int) git_sparse_checkout_disable(git_repository *repo);
93+
94+
/**
95+
* Test if the sparse-checkout rules apply to a given path.
96+
*
97+
* This function checks the sparse-checkout rules to see if they would apply
98+
* to the given path. This indicates if the path would be included on checkout.
99+
*
100+
* @param checkout boolean returning 1 if the sparse-checkout rules apply
101+
* (the file will be checked out), 0 if they do not
102+
* @param repo Repository where to find the sparse-checkout file
103+
* @param path the path to check sparse-checkout rules for, relative to the repo's workdir.
104+
* @return 0 if sparse-checkout rules could be processed for the path
105+
* (regardless of whether it exists or not), or an error < 0 if they could not.
106+
*/
107+
GIT_EXTERN(int) git_sparse_check_path(
108+
int *checkout,
109+
git_repository *repo,
110+
const char *path);
111+
112+
GIT_END_DECL
113+
114+
#endif

src/libgit2/attr_file.c

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,3 +1025,152 @@ void git_attr_session__free(git_attr_session *session)
10251025

10261026
memset(session, 0, sizeof(git_attr_session));
10271027
}
1028+
1029+
1030+
/**
1031+
* A negative ignore pattern can negate a positive one without
1032+
* wildcards if it is a basename only and equals the basename of
1033+
* the positive pattern. Thus
1034+
*
1035+
* foo/bar
1036+
* !bar
1037+
*
1038+
* would result in foo/bar being unignored again while
1039+
*
1040+
* moo/foo/bar
1041+
* !foo/bar
1042+
*
1043+
* would do nothing. The reverse also holds true: a positive
1044+
* basename pattern can be negated by unignoring the basename in
1045+
* subdirectories. Thus
1046+
*
1047+
* bar
1048+
* !foo/bar
1049+
*
1050+
* would result in foo/bar being unignored again. As with the
1051+
* first case,
1052+
*
1053+
* foo/bar
1054+
* !moo/foo/bar
1055+
*
1056+
* would do nothing, again.
1057+
*/
1058+
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
1059+
{
1060+
int (*cmp)(const char *, const char *, size_t);
1061+
git_attr_fnmatch *longer, *shorter;
1062+
char *p;
1063+
1064+
if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0
1065+
|| (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0)
1066+
return false;
1067+
1068+
if (neg->flags & GIT_ATTR_FNMATCH_ICASE)
1069+
cmp = git__strncasecmp;
1070+
else
1071+
cmp = git__strncmp;
1072+
1073+
/* If lengths match we need to have an exact match */
1074+
if (rule->length == neg->length) {
1075+
return cmp(rule->pattern, neg->pattern, rule->length) == 0;
1076+
} else if (rule->length < neg->length) {
1077+
shorter = rule;
1078+
longer = neg;
1079+
} else {
1080+
shorter = neg;
1081+
longer = rule;
1082+
}
1083+
1084+
/* Otherwise, we need to check if the shorter
1085+
* rule is a basename only (that is, it contains
1086+
* no path separator) and, if so, if it
1087+
* matches the tail of the longer rule */
1088+
p = longer->pattern + longer->length - shorter->length;
1089+
1090+
if (p[-1] != '/')
1091+
return false;
1092+
if (memchr(shorter->pattern, '/', shorter->length) != NULL)
1093+
return false;
1094+
1095+
return cmp(p, shorter->pattern, shorter->length) == 0;
1096+
}
1097+
1098+
/**
1099+
* A negative ignore can only unignore a file which is given explicitly before, thus
1100+
*
1101+
* foo
1102+
* !foo/bar
1103+
*
1104+
* does not unignore 'foo/bar' as it's not in the list. However
1105+
*
1106+
* foo/<star>
1107+
* !foo/bar
1108+
*
1109+
* does unignore 'foo/bar', as it is contained within the 'foo/<star>' rule.
1110+
*/
1111+
int git_attr__does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match)
1112+
{
1113+
int error = 0, wildmatch_flags, effective_flags;
1114+
size_t i;
1115+
git_attr_fnmatch *rule;
1116+
char *path;
1117+
git_str buf = GIT_STR_INIT;
1118+
1119+
*out = 0;
1120+
1121+
wildmatch_flags = WM_PATHNAME;
1122+
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
1123+
wildmatch_flags |= WM_CASEFOLD;
1124+
1125+
/* path of the file relative to the workdir, so we match the rules in subdirs */
1126+
if (match->containing_dir) {
1127+
git_str_puts(&buf, match->containing_dir);
1128+
}
1129+
if (git_str_puts(&buf, match->pattern) < 0)
1130+
return -1;
1131+
1132+
path = git_str_detach(&buf);
1133+
1134+
git_vector_foreach(rules, i, rule) {
1135+
if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
1136+
if (does_negate_pattern(rule, match)) {
1137+
error = 0;
1138+
*out = 1;
1139+
goto out;
1140+
}
1141+
else
1142+
continue;
1143+
}
1144+
1145+
git_str_clear(&buf);
1146+
if (rule->containing_dir)
1147+
git_str_puts(&buf, rule->containing_dir);
1148+
git_str_puts(&buf, rule->pattern);
1149+
1150+
if (git_str_oom(&buf))
1151+
goto out;
1152+
1153+
/*
1154+
* if rule isn't for full path we match without PATHNAME flag
1155+
* as lines like *.txt should match something like dir/test.txt
1156+
* requiring * to also match /
1157+
*/
1158+
effective_flags = wildmatch_flags;
1159+
if (!(rule->flags & GIT_ATTR_FNMATCH_FULLPATH))
1160+
effective_flags &= ~WM_PATHNAME;
1161+
1162+
/* if we found a match, we want to keep this rule */
1163+
if ((wildmatch(git_str_cstr(&buf), path, effective_flags)) == WM_MATCH) {
1164+
*out = 1;
1165+
error = 0;
1166+
goto out;
1167+
}
1168+
}
1169+
1170+
error = 0;
1171+
1172+
out:
1173+
git__free(path);
1174+
git_str_dispose(&buf);
1175+
return error;
1176+
}

src/libgit2/attr_file.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,6 @@ extern int git_attr_assignment__parse(
238238
git_vector *assigns,
239239
const char **scan);
240240

241+
extern int git_attr__does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match);
242+
241243
#endif

src/libgit2/checkout.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,15 @@ static int checkout_action_wd_only(
390390

391391
if (wd->mode != GIT_FILEMODE_TREE) {
392392
if (!error) { /* found by git_index__find_pos call */
393-
notify = GIT_CHECKOUT_NOTIFY_DIRTY;
394-
remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
393+
394+
/* Sparse checkout will set the SKIP_WORKTREE bit if a file should be skipped */
395+
const git_index_entry *e = git_index_get_byindex(data->index, pos);
396+
if (e == NULL ||
397+
(e->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) == 0 ||
398+
(data->strategy & GIT_CHECKOUT_REMOVE_SPARSE_FILES) != 0) {
399+
notify = GIT_CHECKOUT_NOTIFY_DIRTY;
400+
remove = ((data->strategy & GIT_CHECKOUT_FORCE) != 0);
401+
}
395402
} else if (error != GIT_ENOTFOUND)
396403
return error;
397404
else
@@ -2571,6 +2578,8 @@ int git_checkout_iterator(
25712578
GIT_DIFF_INCLUDE_TYPECHANGE_TREES |
25722579
GIT_DIFF_SKIP_BINARY_CHECK |
25732580
GIT_DIFF_INCLUDE_CASECHANGE;
2581+
diff_opts.skip_sparse_files = 1;
2582+
25742583
if (data.opts.checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)
25752584
diff_opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH;
25762585
if (data.opts.paths.count > 0)

src/libgit2/config_cache.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ static struct map_data _configmaps[] = {
8787
{"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
8888
{"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT },
8989
{"core.longpaths", NULL, 0, GIT_LONGPATHS_DEFAULT },
90+
{"core.sparsecheckout", NULL, 0, GIT_SPARSECHECKOUT_DEFAULT },
9091
};
9192

9293
int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item)

src/libgit2/diff_generate.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,12 @@ static int maybe_modified(
799799
if (!diff_pathspec_match(&matched_pathspec, diff, oitem))
800800
return 0;
801801

802+
if (diff->base.opts.skip_sparse_files &&
803+
git_iterator_current_skip_checkout(info->new_iter))
804+
return 0;
805+
806+
memset(&noid, 0, sizeof(noid));
807+
802808
/* on platforms with no symlinks, preserve mode of existing symlinks */
803809
if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
804810
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
@@ -1028,6 +1034,11 @@ static int handle_unmatched_new_item(
10281034
const git_index_entry *nitem = info->nitem;
10291035
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
10301036
bool contains_oitem;
1037+
1038+
/* check if this item should be skipped due to sparse checkout */
1039+
if (diff->base.opts.skip_sparse_files &&
1040+
git_iterator_current_skip_checkout(info->new_iter))
1041+
return iterator_advance(&info->nitem, info->new_iter);
10311042

10321043
/* check if this is a prefix of the other side */
10331044
contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
@@ -1188,7 +1199,12 @@ static int handle_unmatched_old_item(
11881199
if (git_index_entry_is_conflict(info->oitem))
11891200
delta_type = GIT_DELTA_CONFLICTED;
11901201

1191-
if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
1202+
if ((diff->base.opts.skip_sparse_files &&
1203+
git_iterator_current_skip_checkout(info->new_iter)) ||
1204+
(info->oitem->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) != 0)
1205+
delta_type = GIT_DELTA_UNMODIFIED;
1206+
1207+
else if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
11921208
return error;
11931209

11941210
/* if we are generating TYPECHANGE records then check for that

0 commit comments

Comments
 (0)