Skip to content

Commit

Permalink
Use gitattributes to define per-path whitespace rule
Browse files Browse the repository at this point in the history
The `core.whitespace` configuration variable allows you to define what
`diff` and `apply` should consider whitespace errors for all paths in
the project (See gitlink:git-config[1]).  This attribute gives you finer
control per path.

For example, if you have these in the .gitattributes:

    frotz   whitespace
    nitfol  -whitespace
    xyzzy   whitespace=-trailing

all types of whitespace problems known to git are noticed in path 'frotz'
(i.e. diff shows them in diff.whitespace color, and apply warns about
them), no whitespace problem is noticed in path 'nitfol', and the
default types of whitespace problems except "trailing whitespace" are
noticed for path 'xyzzy'.  A project with mixed Python and C might want
to have:

    *.c    whitespace
    *.py   whitespace=-indent-with-non-tab

in its toplevel .gitattributes file.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
Junio C Hamano committed Dec 6, 2007
1 parent 91af7ae commit cf1b786
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 74 deletions.
31 changes: 31 additions & 0 deletions Documentation/gitattributes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,37 @@ When left unspecified, the driver itself is used for both
internal merge and the final merge.


Checking whitespace errors
~~~~~~~~~~~~~~~~~~~~~~~~~~

`whitespace`
^^^^^^^^^^^^

The `core.whitespace` configuration variable allows you to define what
`diff` and `apply` should consider whitespace errors for all paths in
the project (See gitlink:git-config[1]). This attribute gives you finer
control per path.

Set::

Notice all types of potential whitespace errors known to git.

Unset::

Do not notice anything as error.

Unspecified::

Use the value of `core.whitespace` configuration variable to
decide what to notice as error.

String::

Specify a comma separate list of common whitespace problems to
notice in the same format as `core.whitespace` configuration
variable.


EXAMPLE
-------

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ LIB_OBJS = \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
transport.o bundle.o walker.o parse-options.o
transport.o bundle.o walker.o parse-options.o ws.o

BUILTIN_OBJS = \
builtin-add.o \
Expand Down
36 changes: 22 additions & 14 deletions builtin-apply.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ struct patch {
unsigned int old_mode, new_mode;
int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */
int rejected;
unsigned ws_rule;
unsigned long deflate_origlen;
int lines_added, lines_deleted;
int score;
Expand Down Expand Up @@ -898,7 +899,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
return -1;
}

static void check_whitespace(const char *line, int len)
static void check_whitespace(const char *line, int len, unsigned ws_rule)
{
const char *err = "Adds trailing whitespace";
int seen_space = 0;
Expand All @@ -910,14 +911,14 @@ static void check_whitespace(const char *line, int len)
* this function. That is, an addition of an empty line would
* check the '+' here. Sneaky...
*/
if ((whitespace_rule & WS_TRAILING_SPACE) && isspace(line[len-2]))
if ((ws_rule & WS_TRAILING_SPACE) && isspace(line[len-2]))
goto error;

/*
* Make sure that there is no space followed by a tab in
* indentation.
*/
if (whitespace_rule & WS_SPACE_BEFORE_TAB) {
if (ws_rule & WS_SPACE_BEFORE_TAB) {
err = "Space in indent is followed by a tab";
for (i = 1; i < len; i++) {
if (line[i] == '\t') {
Expand All @@ -935,7 +936,7 @@ static void check_whitespace(const char *line, int len)
* Make sure that the indentation does not contain more than
* 8 spaces.
*/
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
(8 < len) && !strncmp("+ ", line, 9)) {
err = "Indent more than 8 places with spaces";
goto error;
Expand Down Expand Up @@ -1001,15 +1002,15 @@ static int parse_fragment(char *line, unsigned long size,
case '-':
if (apply_in_reverse &&
ws_error_action != nowarn_ws_error)
check_whitespace(line, len);
check_whitespace(line, len, patch->ws_rule);
deleted++;
oldlines--;
trailing = 0;
break;
case '+':
if (!apply_in_reverse &&
ws_error_action != nowarn_ws_error)
check_whitespace(line, len);
check_whitespace(line, len, patch->ws_rule);
added++;
newlines--;
trailing = 0;
Expand Down Expand Up @@ -1318,6 +1319,10 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
if (offset < 0)
return offset;

patch->ws_rule = whitespace_rule(patch->new_name
? patch->new_name
: patch->old_name);

patchsize = parse_single_patch(buffer + offset + hdrsize,
size - offset - hdrsize, patch);

Expand Down Expand Up @@ -1568,7 +1573,8 @@ static void remove_last_line(const char **rbuf, int *rsize)
*rsize = offset + 1;
}

static int apply_line(char *output, const char *patch, int plen)
static int apply_line(char *output, const char *patch, int plen,
unsigned ws_rule)
{
/*
* plen is number of bytes to be copied from patch,
Expand All @@ -1593,7 +1599,7 @@ static int apply_line(char *output, const char *patch, int plen)
/*
* Strip trailing whitespace
*/
if ((whitespace_rule & WS_TRAILING_SPACE) &&
if ((ws_rule & WS_TRAILING_SPACE) &&
(1 < plen && isspace(patch[plen-1]))) {
if (patch[plen] == '\n')
add_nl_to_tail = 1;
Expand All @@ -1610,12 +1616,12 @@ static int apply_line(char *output, const char *patch, int plen)
char ch = patch[i];
if (ch == '\t') {
last_tab_in_indent = i;
if ((whitespace_rule & WS_SPACE_BEFORE_TAB) &&
if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
0 <= last_space_in_indent)
need_fix_leading_space = 1;
} else if (ch == ' ') {
last_space_in_indent = i;
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
last_tab_in_indent < 0 &&
8 <= i)
need_fix_leading_space = 1;
Expand All @@ -1629,7 +1635,7 @@ static int apply_line(char *output, const char *patch, int plen)
int consecutive_spaces = 0;
int last = last_tab_in_indent + 1;

if (whitespace_rule & WS_INDENT_WITH_NON_TAB) {
if (ws_rule & WS_INDENT_WITH_NON_TAB) {
/* have "last" point at one past the indent */
if (last_tab_in_indent < last_space_in_indent)
last = last_space_in_indent + 1;
Expand Down Expand Up @@ -1671,7 +1677,7 @@ static int apply_line(char *output, const char *patch, int plen)
}

static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
int inaccurate_eof)
int inaccurate_eof, unsigned ws_rule)
{
int match_beginning, match_end;
const char *patch = frag->patch;
Expand Down Expand Up @@ -1730,7 +1736,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
case '+':
if (first != '+' || !no_add) {
int added = apply_line(new + newsize, patch,
plen);
plen, ws_rule);
newsize += added;
if (first == '+' &&
added == 1 && new[newsize-1] == '\n')
Expand Down Expand Up @@ -1953,12 +1959,14 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
{
struct fragment *frag = patch->fragments;
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned ws_rule = patch->ws_rule;
unsigned inaccurate_eof = patch->inaccurate_eof;

if (patch->is_binary)
return apply_binary(buf, patch);

while (frag) {
if (apply_one_fragment(buf, frag, patch->inaccurate_eof)) {
if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
error("patch failed: %s:%ld", name, frag->oldpos);
if (!apply_with_reject)
return -1;
Expand Down
4 changes: 3 additions & 1 deletion cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,8 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
#define WS_SPACE_BEFORE_TAB 02
#define WS_INDENT_WITH_NON_TAB 04
#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
extern unsigned whitespace_rule;
extern unsigned whitespace_rule_cfg;
extern unsigned whitespace_rule(const char *);
extern unsigned parse_whitespace_rule(const char *);

#endif /* CACHE_H */
50 changes: 1 addition & 49 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,54 +246,6 @@ static unsigned long get_unit_factor(const char *end)
die("unknown unit: '%s'", end);
}

static struct whitespace_rule {
const char *rule_name;
unsigned rule_bits;
} whitespace_rule_names[] = {
{ "trailing-space", WS_TRAILING_SPACE },
{ "space-before-tab", WS_SPACE_BEFORE_TAB },
{ "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
};

static unsigned parse_whitespace_rule(const char *string)
{
unsigned rule = WS_DEFAULT_RULE;

while (string) {
int i;
size_t len;
const char *ep;
int negated = 0;

string = string + strspn(string, ", \t\n\r");
ep = strchr(string, ',');
if (!ep)
len = strlen(string);
else
len = ep - string;

if (*string == '-') {
negated = 1;
string++;
len--;
}
if (!len)
break;
for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) {
if (strncmp(whitespace_rule_names[i].rule_name,
string, len))
continue;
if (negated)
rule &= ~whitespace_rule_names[i].rule_bits;
else
rule |= whitespace_rule_names[i].rule_bits;
break;
}
string = ep;
}
return rule;
}

int git_parse_long(const char *value, long *ret)
{
if (value && *value) {
Expand Down Expand Up @@ -480,7 +432,7 @@ int git_default_config(const char *var, const char *value)
}

if (!strcmp(var, "core.whitespace")) {
whitespace_rule = parse_whitespace_rule(value);
whitespace_rule_cfg = parse_whitespace_rule(value);
return 0;
}

Expand Down
19 changes: 12 additions & 7 deletions diff.c
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
struct emit_callback {
struct xdiff_emit_state xm;
int nparents, color_diff;
unsigned ws_rule;
const char **label_path;
struct diff_words_data *diff_words;
int *found_changesp;
Expand Down Expand Up @@ -493,8 +494,8 @@ static void emit_line(const char *set, const char *reset, const char *line, int
}

static void emit_line_with_ws(int nparents,
const char *set, const char *reset, const char *ws,
const char *line, int len)
const char *set, const char *reset, const char *ws,
const char *line, int len, unsigned ws_rule)
{
int col0 = nparents;
int last_tab_in_indent = -1;
Expand All @@ -511,7 +512,7 @@ static void emit_line_with_ws(int nparents,
for (i = col0; i < len; i++) {
if (line[i] == '\t') {
last_tab_in_indent = i;
if ((whitespace_rule & WS_SPACE_BEFORE_TAB) &&
if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
0 <= last_space_in_indent)
need_highlight_leading_space = 1;
}
Expand All @@ -520,7 +521,7 @@ static void emit_line_with_ws(int nparents,
else
break;
}
if ((whitespace_rule & WS_INDENT_WITH_NON_TAB) &&
if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
0 <= last_space_in_indent &&
last_tab_in_indent < 0 &&
8 <= (i - col0)) {
Expand Down Expand Up @@ -551,7 +552,7 @@ static void emit_line_with_ws(int nparents,
tail = len - 1;
if (line[tail] == '\n' && i < tail)
tail--;
if (whitespace_rule & WS_TRAILING_SPACE) {
if (ws_rule & WS_TRAILING_SPACE) {
while (i < tail) {
if (!isspace(line[tail]))
break;
Expand All @@ -578,7 +579,7 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons
emit_line(set, reset, line, len);
else
emit_line_with_ws(ecbdata->nparents, set, reset, ws,
line, len);
line, len, ecbdata->ws_rule);
}

static void fn_out_consume(void *priv, char *line, unsigned long len)
Expand Down Expand Up @@ -994,6 +995,7 @@ struct checkdiff_t {
struct xdiff_emit_state xm;
const char *filename;
int lineno, color_diff;
unsigned ws_rule;
};

static void checkdiff_consume(void *priv, char *line, unsigned long len)
Expand Down Expand Up @@ -1029,7 +1031,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
if (white_space_at_end)
printf("white space at end");
printf(":%s ", reset);
emit_line_with_ws(1, set, reset, ws, line, len);
emit_line_with_ws(1, set, reset, ws, line, len,
data->ws_rule);
}

data->lineno++;
Expand Down Expand Up @@ -1330,6 +1333,7 @@ static void builtin_diff(const char *name_a,
ecbdata.label_path = lbl;
ecbdata.color_diff = o->color_diff;
ecbdata.found_changesp = &o->found_changes;
ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a);
xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
xecfg.ctxlen = o->context;
xecfg.flags = XDL_EMIT_FUNCNAMES;
Expand Down Expand Up @@ -1423,6 +1427,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
data.color_diff = o->color_diff;
data.ws_rule = whitespace_rule(data.filename);

if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
Expand Down
2 changes: 1 addition & 1 deletion environment.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ int pager_in_use;
int pager_use_color = 1;
char *editor_program;
int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
unsigned whitespace_rule = WS_DEFAULT_RULE;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;

/* This is set by setup_git_dir_gently() and/or git_default_config() */
char *git_work_tree_cfg;
Expand Down
Loading

0 comments on commit cf1b786

Please sign in to comment.