Skip to content

Commit

Permalink
Introduce "precious" file concept
Browse files Browse the repository at this point in the history
A new attribute "precious" is added to indicate that certain files
have valuable content and should not be easily discarded even if they
are ignored or untracked.

So far there is one part of Git that is made aware of precious files:
"git clean" will leave precious files alone if --keep-precious is
specified.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
pclouds authored and gitster committed Apr 9, 2019
1 parent 041f5ea commit b0c2d61
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 5 deletions.
6 changes: 5 additions & 1 deletion Documentation/git-clean.txt
Expand Up @@ -8,7 +8,7 @@ git-clean - Remove untracked files from the working tree
SYNOPSIS
--------
[verse]
'git clean' [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>...
'git clean' [<options>] [-d] [-f] [-i] [-n] [-x | -X] [--] <path>...

DESCRIPTION
-----------
Expand Down Expand Up @@ -71,6 +71,10 @@ OPTIONS
Remove only files ignored by Git. This may be useful to rebuild
everything from scratch, but keep manually created files.

--keep-precious::
Do not remove untracked or ignored files if they have
`precious` attribute.

Interactive mode
----------------
When the command enters the interactive mode, it shows the
Expand Down
11 changes: 11 additions & 0 deletions Documentation/gitattributes.txt
Expand Up @@ -1192,6 +1192,17 @@ If this attribute is not set or has an invalid value, the value of the
(See linkgit:git-config[1]).


Precious files
~~~~~~~~~~~~~~

`precious`
^^^^^^^^^^

This attribute is set on files to indicate that their content is
valuable. Some commands will behave slightly different on precious
files. linkgit:git-clean[1] may leave precious files alone.


USING MACRO ATTRIBUTES
----------------------

Expand Down
12 changes: 12 additions & 0 deletions attr.c
Expand Up @@ -1157,3 +1157,15 @@ void attr_start(void)
pthread_mutex_init(&g_attr_hashmap.mutex, NULL);
pthread_mutex_init(&check_vector.mutex, NULL);
}

int is_precious_file(struct index_state *istate, const char *path)
{
static struct attr_check *check;
if (!check)
check = attr_check_initl("precious", NULL);
if (!check)
return 0;

git_check_attr(istate, path, check);
return ATTR_TRUE(check->items[0].value);
}
2 changes: 2 additions & 0 deletions attr.h
Expand Up @@ -82,4 +82,6 @@ void git_attr_set_direction(enum git_attr_direction new_direction);

void attr_start(void);

int is_precious_file(struct index_state *istate, const char *path);

#endif /* ATTR_H */
30 changes: 26 additions & 4 deletions builtin/clean.c
Expand Up @@ -18,19 +18,23 @@
#include "color.h"
#include "pathspec.h"
#include "help.h"
#include "attr.h"

static int force = -1; /* unset */
static int interactive;
static int keep_precious;
static struct string_list del_list = STRING_LIST_INIT_DUP;
static unsigned int colopts;

static const char *const builtin_clean_usage[] = {
N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
N_("git clean [<options>] [-d] [-f] [-i] [-n] [-x | -X] [--] <paths>..."),
NULL
};

static const char *msg_remove = N_("Removing %s\n");
static const char *msg_would_remove = N_("Would remove %s\n");
static const char *msg_skip_precious = N_("Skipping precious file %s\n");
static const char *msg_would_skip_precious = N_("Would skip precious file %s\n");
static const char *msg_skip_git_dir = N_("Skipping repository %s\n");
static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
static const char *msg_warn_remove_failed = N_("failed to remove %s");
Expand Down Expand Up @@ -146,6 +150,11 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset)
return 0;
}

static int skip_precious_file(struct index_state *istate, const char *path)
{
return keep_precious && is_precious_file(istate, path);
}

static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
int dry_run, int quiet, int *dir_gone)
{
Expand All @@ -154,6 +163,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,
struct dirent *e;
int res = 0, ret = 0, gone = 1, original_len = path->len, len;
struct string_list dels = STRING_LIST_INIT_DUP;
const char *rel_path;

*dir_gone = 1;

Expand Down Expand Up @@ -193,9 +203,16 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag,

strbuf_setlen(path, len);
strbuf_addstr(path, e->d_name);
if (lstat(path->buf, &st))
if (lstat(path->buf, &st)) {
; /* fall thru */
else if (S_ISDIR(st.st_mode)) {
} else if ((!prefix && skip_precious_file(&the_index, path->buf)) ||
(prefix && skip_prefix(path->buf, prefix, &rel_path) &&
skip_precious_file(&the_index, rel_path))) {
quote_path_relative(path->buf, prefix, &quoted);
printf(dry_run ? _(msg_would_skip_precious) : _(msg_skip_precious), quoted.buf);
*dir_gone = 0;
continue;
} else if (S_ISDIR(st.st_mode)) {
if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
ret = 1;
if (gone) {
Expand Down Expand Up @@ -915,6 +932,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
OPT_BOOL('X', NULL, &ignored_only,
N_("remove only ignored files")),
OPT_BOOL(0, "keep-precious", &keep_precious,
N_("do not remove files with 'precious' attribute")),
OPT_END()
};

Expand Down Expand Up @@ -1019,7 +1038,10 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
if (lstat(abs_path.buf, &st))
continue;

if (S_ISDIR(st.st_mode)) {
if (skip_precious_file(&the_index, item->string)) {
qname = quote_path_relative(item->string, NULL, &buf);
printf(dry_run ? _(msg_would_skip_precious) : _(msg_skip_precious), qname);
} else if (S_ISDIR(st.st_mode)) {
if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
errors++;
if (gone && !quiet) {
Expand Down
40 changes: 40 additions & 0 deletions t/t7300-clean.sh
Expand Up @@ -669,4 +669,44 @@ test_expect_success 'git clean -d skips untracked dirs containing ignored files'
test_path_is_missing foo/b/bb
'

test_expect_success 'git clean -xd --keep-precious leaves precious files alone' '
git init precious &&
(
cd precious &&
test_commit one &&
cat >.gitignore <<-\EOF &&
*.o
*.mak
EOF
cat >.gitattributes <<-\EOF &&
*.mak precious
.gitattributes precious
*.precious precious
EOF
mkdir sub &&
touch one.o sub/two.o one.mak sub/two.mak &&
touch one.untracked two.precious sub/also.precious &&
git clean -fdx --keep-precious &&
test_path_is_missing one.o &&
test_path_is_missing sub/two.o &&
test_path_is_missing one.untracked &&
test_path_is_file .gitattributes &&
test_path_is_file one.mak &&
test_path_is_file sub/two.mak &&
test_path_is_file two.precious &&
test_path_is_file sub/also.precious
)
'

test_expect_success 'git clean -xd still deletes them all' '
test_path_is_file precious/one.mak &&
test_path_is_file precious/sub/two.mak &&
test_path_is_file precious/two.precious &&
test_path_is_file precious/sub/also.precious &&
git -C precious clean -fdx &&
test_path_is_missing precious/one.mak &&
test_path_is_missing precious/sub/two.mak &&
test_path_is_missing precious/two.precious &&
test_path_is_missing precious/sub/also.precious
'
test_done

0 comments on commit b0c2d61

Please sign in to comment.