summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile1
-rw-r--r--builtin-commit.c2
-rw-r--r--builtin-rm.c53
-rw-r--r--builtin-write-tree.c2
-rw-r--r--cache-tree.c10
-rw-r--r--cache.h59
-rwxr-xr-xgit-filter-branch.sh2
-rwxr-xr-xgit-lost-found.sh2
-rwxr-xr-xgit-pull.sh8
-rwxr-xr-xgit-rebase.sh4
-rwxr-xr-xgit-submodule.sh6
-rwxr-xr-xgitweb/gitweb.perl32
-rw-r--r--read-cache.c61
-rwxr-xr-xt/t2203-add-intent.sh28
-rwxr-xr-xt/t3600-rm.sh4
16 files changed, 216 insertions, 59 deletions
diff --git a/.gitignore b/.gitignore
index bbaf9de0bc..d9adce585a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -118,6 +118,7 @@ git-show
git-show-branch
git-show-index
git-show-ref
+git-stage
git-stash
git-status
git-stripspace
diff --git a/Makefile b/Makefile
index 9577d6fc73..51581973ab 100644
--- a/Makefile
+++ b/Makefile
@@ -320,6 +320,7 @@ BUILT_INS += git-merge-subtree$X
BUILT_INS += git-peek-remote$X
BUILT_INS += git-repo-config$X
BUILT_INS += git-show$X
+BUILT_INS += git-stage$X
BUILT_INS += git-status$X
BUILT_INS += git-whatchanged$X
diff --git a/builtin-commit.c b/builtin-commit.c
index 1677e6b45f..2b499fa543 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -639,7 +639,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix)
active_cache_tree = cache_tree();
if (cache_tree_update(active_cache_tree,
active_cache, active_nr, 0, 0) < 0) {
- error("Error building trees; the index is unmerged?");
+ error("Error building trees");
return 0;
}
diff --git a/builtin-rm.c b/builtin-rm.c
index b7126e3e25..c11f455858 100644
--- a/builtin-rm.c
+++ b/builtin-rm.c
@@ -31,7 +31,8 @@ static void add_list(const char *name)
static int check_local_mod(unsigned char *head, int index_only)
{
- /* items in list are already sorted in the cache order,
+ /*
+ * Items in list are already sorted in the cache order,
* so we could do this a lot more efficiently by using
* tree_desc based traversal if we wanted to, but I am
* lazy, and who cares if removal of files is a tad
@@ -71,25 +72,55 @@ static int check_local_mod(unsigned char *head, int index_only)
*/
continue;
}
+
+ /*
+ * "rm" of a path that has changes need to be treated
+ * carefully not to allow losing local changes
+ * accidentally. A local change could be (1) file in
+ * work tree is different since the index; and/or (2)
+ * the user staged a content that is different from
+ * the current commit in the index.
+ *
+ * In such a case, you would need to --force the
+ * removal. However, "rm --cached" (remove only from
+ * the index) is safe if the index matches the file in
+ * the work tree or the HEAD commit, as it means that
+ * the content being removed is available elsewhere.
+ */
+
+ /*
+ * Is the index different from the file in the work tree?
+ */
if (ce_match_stat(ce, &st, 0))
local_changes = 1;
+
+ /*
+ * Is the index different from the HEAD commit? By
+ * definition, before the very initial commit,
+ * anything staged in the index is treated by the same
+ * way as changed from the HEAD.
+ */
if (no_head
|| get_tree_entry(head, name, sha1, &mode)
|| ce->ce_mode != create_ce_mode(mode)
|| hashcmp(ce->sha1, sha1))
staged_changes = 1;
- if (local_changes && staged_changes &&
- !(index_only && is_empty_blob_sha1(ce->sha1)))
- errs = error("'%s' has staged content different "
- "from both the file and the HEAD\n"
- "(use -f to force removal)", name);
+ /*
+ * If the index does not match the file in the work
+ * tree and if it does not match the HEAD commit
+ * either, (1) "git rm" without --cached definitely
+ * will lose information; (2) "git rm --cached" will
+ * lose information unless it is about removing an
+ * "intent to add" entry.
+ */
+ if (local_changes && staged_changes) {
+ if (!index_only || !(ce->ce_flags & CE_INTENT_TO_ADD))
+ errs = error("'%s' has staged content different "
+ "from both the file and the HEAD\n"
+ "(use -f to force removal)", name);
+ }
else if (!index_only) {
- /* It's not dangerous to "git rm --cached" a
- * file if the index matches the file or the
- * HEAD, since it means the deleted content is
- * still available somewhere.
- */
if (staged_changes)
errs = error("'%s' has changes staged in the index\n"
"(use --cached to keep the file, "
diff --git a/builtin-write-tree.c b/builtin-write-tree.c
index 52a3c015ff..9d640508dd 100644
--- a/builtin-write-tree.c
+++ b/builtin-write-tree.c
@@ -42,7 +42,7 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix)
die("%s: error reading the index", me);
break;
case WRITE_TREE_UNMERGED_INDEX:
- die("%s: error building trees; the index is unmerged?", me);
+ die("%s: error building trees", me);
break;
case WRITE_TREE_PREFIX_ERROR:
die("%s: prefix %s not found", me, prefix);
diff --git a/cache-tree.c b/cache-tree.c
index 5f8ee87bb1..3d8f218a5f 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -155,13 +155,17 @@ static int verify_cache(struct cache_entry **cache,
funny = 0;
for (i = 0; i < entries; i++) {
struct cache_entry *ce = cache[i];
- if (ce_stage(ce)) {
+ if (ce_stage(ce) || (ce->ce_flags & CE_INTENT_TO_ADD)) {
if (10 < ++funny) {
fprintf(stderr, "...\n");
break;
}
- fprintf(stderr, "%s: unmerged (%s)\n",
- ce->name, sha1_to_hex(ce->sha1));
+ if (ce_stage(ce))
+ fprintf(stderr, "%s: unmerged (%s)\n",
+ ce->name, sha1_to_hex(ce->sha1));
+ else
+ fprintf(stderr, "%s: not added yet\n",
+ ce->name);
}
}
if (funny)
diff --git a/cache.h b/cache.h
index 487e5e1b1b..f15b3fc906 100644
--- a/cache.h
+++ b/cache.h
@@ -115,6 +115,26 @@ struct ondisk_cache_entry {
char name[FLEX_ARRAY]; /* more */
};
+/*
+ * This struct is used when CE_EXTENDED bit is 1
+ * The struct must match ondisk_cache_entry exactly from
+ * ctime till flags
+ */
+struct ondisk_cache_entry_extended {
+ struct cache_time ctime;
+ struct cache_time mtime;
+ unsigned int dev;
+ unsigned int ino;
+ unsigned int mode;
+ unsigned int uid;
+ unsigned int gid;
+ unsigned int size;
+ unsigned char sha1[20];
+ unsigned short flags;
+ unsigned short flags2;
+ char name[FLEX_ARRAY]; /* more */
+};
+
struct cache_entry {
unsigned int ce_ctime;
unsigned int ce_mtime;
@@ -136,7 +156,15 @@ struct cache_entry {
#define CE_VALID (0x8000)
#define CE_STAGESHIFT 12
-/* In-memory only */
+/*
+ * Range 0xFFFF0000 in ce_flags is divided into
+ * two parts: in-memory flags and on-disk ones.
+ * Flags in CE_EXTENDED_FLAGS will get saved on-disk
+ * if you want to save a new flag, add it in
+ * CE_EXTENDED_FLAGS
+ *
+ * In-memory only flags
+ */
#define CE_UPDATE (0x10000)
#define CE_REMOVE (0x20000)
#define CE_UPTODATE (0x40000)
@@ -146,6 +174,25 @@ struct cache_entry {
#define CE_UNHASHED (0x200000)
/*
+ * Extended on-disk flags
+ */
+#define CE_INTENT_TO_ADD 0x20000000
+/* CE_EXTENDED2 is for future extension */
+#define CE_EXTENDED2 0x80000000
+
+#define CE_EXTENDED_FLAGS (CE_INTENT_TO_ADD)
+
+/*
+ * Safeguard to avoid saving wrong flags:
+ * - CE_EXTENDED2 won't get saved until its semantic is known
+ * - Bits in 0x0000FFFF have been saved in ce_flags already
+ * - Bits in 0x003F0000 are currently in-memory flags
+ */
+#if CE_EXTENDED_FLAGS & 0x803FFFFF
+#error "CE_EXTENDED_FLAGS out of range"
+#endif
+
+/*
* Copy the sha1 and stat state of a cache entry from one to
* another. But we never change the name, or the hash state!
*/
@@ -177,7 +224,9 @@ static inline size_t ce_namelen(const struct cache_entry *ce)
}
#define ce_size(ce) cache_entry_size(ce_namelen(ce))
-#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce))
+#define ondisk_ce_size(ce) (((ce)->ce_flags & CE_EXTENDED) ? \
+ ondisk_cache_entry_extended_size(ce_namelen(ce)) : \
+ ondisk_cache_entry_size(ce_namelen(ce)))
#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT)
#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE)
#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE)
@@ -220,8 +269,10 @@ static inline int ce_to_dtype(const struct cache_entry *ce)
(S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
-#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
-#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7)
+#define flexible_size(STRUCT,len) ((offsetof(struct STRUCT,name) + (len) + 8) & ~7)
+#define cache_entry_size(len) flexible_size(cache_entry,len)
+#define ondisk_cache_entry_size(len) flexible_size(ondisk_cache_entry,len)
+#define ondisk_cache_entry_extended_size(len) flexible_size(ondisk_cache_entry_extended,len)
struct index_state {
struct cache_entry **cache;
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
index 81392add0b..c106f45af7 100755
--- a/git-filter-branch.sh
+++ b/git-filter-branch.sh
@@ -256,7 +256,7 @@ while read commit parents; do
*)
# The commit may not have the subdirectory at all
err=$(git read-tree -i -m $commit:"$filter_subdir" 2>&1) || {
- if ! git rev-parse --verify $commit:"$filter_subdir" 2>/dev/null
+ if ! git rev-parse -q --verify $commit:"$filter_subdir"
then
rm -f "$GIT_INDEX_FILE"
else
diff --git a/git-lost-found.sh b/git-lost-found.sh
index 9cedaf80ce..0b3e8c7a86 100755
--- a/git-lost-found.sh
+++ b/git-lost-found.sh
@@ -20,7 +20,7 @@ while read dangling type sha1
do
case "$dangling" in
dangling)
- if git rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
+ if git rev-parse -q --verify "$sha1^0" >/dev/null
then
dir="$laf/commit"
git show-branch "$sha1"
diff --git a/git-pull.sh b/git-pull.sh
index 1cac898a24..2c7f432dc0 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -121,13 +121,13 @@ test true = "$rebase" && {
test -z "$origin" && origin=$(get_default_remote)
reflist="$(get_remote_refs_for_fetch "$@" 2>/dev/null |
sed "s|refs/heads/\(.*\):|\1|")" &&
- oldremoteref="$(git rev-parse --verify \
- "refs/remotes/$origin/$reflist" 2>/dev/null)"
+ oldremoteref="$(git rev-parse -q --verify \
+ "refs/remotes/$origin/$reflist")"
}
-orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
+orig_head=$(git rev-parse -q --verify HEAD)
git fetch $verbosity --update-head-ok "$@" || exit 1
-curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
+curr_head=$(git rev-parse -q --verify HEAD)
if test -n "$orig_head" && test "$curr_head" != "$orig_head"
then
# The fetch involved updating the current branch.
diff --git a/git-rebase.sh b/git-rebase.sh
index 023a6dc94a..ea7720d3e2 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -365,10 +365,10 @@ case "$#" in
switch_to="$2"
if git show-ref --verify --quiet -- "refs/heads/$2" &&
- branch=$(git rev-parse --verify "refs/heads/$2" 2>/dev/null)
+ branch=$(git rev-parse -q --verify "refs/heads/$2")
then
head_name="refs/heads/$2"
- elif branch=$(git rev-parse --verify "$2" 2>/dev/null)
+ elif branch=$(git rev-parse -q --verify "$2")
then
head_name="detached HEAD"
else
diff --git a/git-submodule.sh b/git-submodule.sh
index 220d94ec0c..2f47e065fe 100755
--- a/git-submodule.sh
+++ b/git-submodule.sh
@@ -413,7 +413,7 @@ cmd_summary() {
test $summary_limit = 0 && return
- if rev=$(git rev-parse --verify "$1^0" 2>/dev/null)
+ if rev=$(git rev-parse -q --verify "$1^0")
then
head=$rev
shift
@@ -464,11 +464,11 @@ cmd_summary() {
missing_dst=
test $mod_src = 160000 &&
- ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_src^0 >/dev/null 2>&1 &&
+ ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_src^0 >/dev/null &&
missing_src=t
test $mod_dst = 160000 &&
- ! GIT_DIR="$name/.git" git-rev-parse --verify $sha1_dst^0 >/dev/null 2>&1 &&
+ ! GIT_DIR="$name/.git" git-rev-parse -q --verify $sha1_dst^0 >/dev/null &&
missing_dst=t
total_commits=
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 2738643950..95988fba4a 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -2758,6 +2758,15 @@ sub get_file_owner {
return to_utf8($owner);
}
+# assume that file exists
+sub insert_file {
+ my $filename = shift;
+
+ open my $fd, '<', $filename;
+ print map(to_utf8, <$fd>);
+ close $fd;
+}
+
## ......................................................................
## mimetype related functions
@@ -2946,9 +2955,7 @@ EOF
"<body>\n";
if (-f $site_header) {
- open (my $fd, $site_header);
- print <$fd>;
- close $fd;
+ insert_file($site_header);
}
print "<div class=\"page_header\">\n" .
@@ -3035,9 +3042,7 @@ sub git_footer_html {
print "</div>\n"; # class="page_footer"
if (-f $site_footer) {
- open (my $fd, $site_footer);
- print <$fd>;
- close $fd;
+ insert_file($site_footer);
}
print "</body>\n" .
@@ -4376,9 +4381,7 @@ sub git_project_list {
git_header_html();
if (-f $home_text) {
print "<div class=\"index_include\">\n";
- open (my $fd, $home_text);
- print <$fd>;
- close $fd;
+ insert_file($home_text);
print "</div>\n";
}
print $cgi->startform(-method => "get") .
@@ -4490,13 +4493,10 @@ sub git_summary {
print "</table>\n";
if (-s "$projectroot/$project/README.html") {
- if (open my $fd, "$projectroot/$project/README.html") {
- print "<div class=\"title\">readme</div>\n" .
- "<div class=\"readme\">\n";
- print $_ while (<$fd>);
- print "\n</div>\n"; # class="readme"
- close $fd;
- }
+ print "<div class=\"title\">readme</div>\n" .
+ "<div class=\"readme\">\n";
+ insert_file("$projectroot/$project/README.html");
+ print "\n</div>\n"; # class="readme"
}
# we need to request one more than 16 (0..15) to check if
diff --git a/read-cache.c b/read-cache.c
index 22a814311d..8579663ee0 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -257,6 +257,14 @@ int ie_match_stat(const struct index_state *istate,
if (!ignore_valid && (ce->ce_flags & CE_VALID))
return 0;
+ /*
+ * Intent-to-add entries have not been added, so the index entry
+ * by definition never matches what is in the work tree until it
+ * actually gets added.
+ */
+ if (ce->ce_flags & CE_INTENT_TO_ADD)
+ return DATA_CHANGED | TYPE_CHANGED | MODE_CHANGED;
+
changed = ce_match_stat_basic(ce, st);
/*
@@ -546,6 +554,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
ce->ce_flags = namelen;
if (!intent_only)
fill_stat_cache_info(ce, st);
+ else
+ ce->ce_flags |= CE_INTENT_TO_ADD;
if (trust_executable_bit && has_symlinks)
ce->ce_mode = create_ce_mode(st_mode);
@@ -1098,7 +1108,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
if (hdr->hdr_signature != htonl(CACHE_SIGNATURE))
return error("bad signature");
- if (hdr->hdr_version != htonl(2))
+ if (hdr->hdr_version != htonl(2) && hdr->hdr_version != htonl(3))
return error("bad index version");
git_SHA1_Init(&c);
git_SHA1_Update(&c, hdr, size - 20);
@@ -1133,6 +1143,7 @@ int read_index(struct index_state *istate)
static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_entry *ce)
{
size_t len;
+ const char *name;
ce->ce_ctime = ntohl(ondisk->ctime.sec);
ce->ce_mtime = ntohl(ondisk->mtime.sec);
@@ -1145,19 +1156,31 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
/* On-disk flags are just 16 bits */
ce->ce_flags = ntohs(ondisk->flags);
- /* For future extension: we do not understand this entry yet */
- if (ce->ce_flags & CE_EXTENDED)
- die("Unknown index entry format");
hashcpy(ce->sha1, ondisk->sha1);
len = ce->ce_flags & CE_NAMEMASK;
+
+ if (ce->ce_flags & CE_EXTENDED) {
+ struct ondisk_cache_entry_extended *ondisk2;
+ int extended_flags;
+ ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
+ extended_flags = ntohs(ondisk2->flags2) << 16;
+ /* We do not yet understand any bit out of CE_EXTENDED_FLAGS */
+ if (extended_flags & ~CE_EXTENDED_FLAGS)
+ die("Unknown index entry format %08x", extended_flags);
+ ce->ce_flags |= extended_flags;
+ name = ondisk2->name;
+ }
+ else
+ name = ondisk->name;
+
if (len == CE_NAMEMASK)
- len = strlen(ondisk->name);
+ len = strlen(name);
/*
* NEEDSWORK: If the original index is crafted, this copy could
* go unchecked.
*/
- memcpy(ce->name, ondisk->name, len + 1);
+ memcpy(ce->name, name, len + 1);
}
static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
@@ -1422,6 +1445,7 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
{
int size = ondisk_ce_size(ce);
struct ondisk_cache_entry *ondisk = xcalloc(1, size);
+ char *name;
ondisk->ctime.sec = htonl(ce->ce_ctime);
ondisk->ctime.nsec = 0;
@@ -1435,7 +1459,15 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
ondisk->size = htonl(ce->ce_size);
hashcpy(ondisk->sha1, ce->sha1);
ondisk->flags = htons(ce->ce_flags);
- memcpy(ondisk->name, ce->name, ce_namelen(ce));
+ if (ce->ce_flags & CE_EXTENDED) {
+ struct ondisk_cache_entry_extended *ondisk2;
+ ondisk2 = (struct ondisk_cache_entry_extended *)ondisk;
+ ondisk2->flags2 = htons((ce->ce_flags & CE_EXTENDED_FLAGS) >> 16);
+ name = ondisk2->name;
+ }
+ else
+ name = ondisk->name;
+ memcpy(name, ce->name, ce_namelen(ce));
return ce_write(c, fd, ondisk, size);
}
@@ -1444,16 +1476,25 @@ int write_index(const struct index_state *istate, int newfd)
{
git_SHA_CTX c;
struct cache_header hdr;
- int i, err, removed;
+ int i, err, removed, extended;
struct cache_entry **cache = istate->cache;
int entries = istate->cache_nr;
- for (i = removed = 0; i < entries; i++)
+ for (i = removed = extended = 0; i < entries; i++) {
if (cache[i]->ce_flags & CE_REMOVE)
removed++;
+ /* reduce extended entries if possible */
+ cache[i]->ce_flags &= ~CE_EXTENDED;
+ if (cache[i]->ce_flags & CE_EXTENDED_FLAGS) {
+ extended++;
+ cache[i]->ce_flags |= CE_EXTENDED;
+ }
+ }
+
hdr.hdr_signature = htonl(CACHE_SIGNATURE);
- hdr.hdr_version = htonl(2);
+ /* for extended format, increase version so older git won't try to read it */
+ hdr.hdr_version = htonl(extended ? 3 : 2);
hdr.hdr_entries = htonl(entries - removed);
git_SHA1_Init(&c);
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh
index d4de35ea06..58a329961e 100755
--- a/t/t2203-add-intent.sh
+++ b/t/t2203-add-intent.sh
@@ -32,5 +32,33 @@ test_expect_success 'intent to add does not clobber existing paths' '
! grep "$empty" actual
'
+test_expect_success 'cannot commit with i-t-a entry' '
+ test_tick &&
+ git commit -a -m initial &&
+ git reset --hard &&
+
+ echo xyzzy >rezrov &&
+ echo frotz >nitfol &&
+ git add rezrov &&
+ git add -N nitfol &&
+ test_must_fail git commit
+'
+
+test_expect_success 'can commit with an unrelated i-t-a entry in index' '
+ git reset --hard &&
+ echo xyzzy >rezrov &&
+ echo frotz >nitfol &&
+ git add rezrov &&
+ git add -N nitfol &&
+ git commit -m partial rezrov
+'
+
+test_expect_success 'can "commit -a" with an i-t-a entry' '
+ git reset --hard &&
+ : >nitfol &&
+ git add -N nitfol &&
+ git commit -a -m all
+'
+
test_done
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 5b4d6f7138..b7d46e50a8 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -187,8 +187,8 @@ test_expect_success 'but with -f it should work.' '
test_must_fail git ls-files --error-unmatch baz
'
-test_expect_failure 'refuse to remove cached empty file with modifications' '
- touch empty &&
+test_expect_success 'refuse to remove cached empty file with modifications' '
+ >empty &&
git add empty &&
echo content >empty &&
test_must_fail git rm --cached empty