Imported Upstream version 2.1.2 upstream/2.1.2
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:14:41 +0000 (15:14 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 3 Mar 2021 06:14:41 +0000 (15:14 +0900)
24 files changed:
Documentation/RelNotes/2.1.2.txt [new file with mode: 0644]
Documentation/git-rebase.txt
Documentation/git-send-pack.txt
Documentation/git.txt
GIT-VERSION-GEN
RelNotes
builtin/config.c
builtin/fsck.c
builtin/index-pack.c
builtin/send-pack.c
cache.h
config.c
fast-import.c
po/TEAMS
po/de.po
reachable.c
remote-curl.c
t/t1303-wacky-config.sh
t/t1450-fsck.sh
t/t4212-log-corrupt.sh
t/t5304-prune.sh
t/t5408-send-pack-stdin.sh [new file with mode: 0755]
t/t5541-http-push-smart.sh
t/t9300-fast-import.sh

diff --git a/Documentation/RelNotes/2.1.2.txt b/Documentation/RelNotes/2.1.2.txt
new file mode 100644 (file)
index 0000000..abc3b89
--- /dev/null
@@ -0,0 +1,20 @@
+Git v2.1.2 Release Notes
+========================
+
+ * "git push" over HTTP transport had an artificial limit on number of
+   refs that can be pushed imposed by the command line length.
+
+ * When receiving an invalid pack stream that records the same object
+   twice, multiple threads got confused due to a race.
+
+ * An attempt to remove the entire tree in the "git fast-import" input
+   stream caused it to misbehave.
+
+ * Reachability check (used in "git prune" and friends) did not add a
+   detached HEAD as a starting point to traverse objects still in use.
+
+ * "git config --add section.var val" used to lose existing
+   section.var whose value was an empty string.
+
+ * "git fsck" failed to report that it found corrupt objects via its
+   exit status in some cases.
index 2a93c64..4138554 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
-       [<upstream>] [<branch>]
+       [<upstream> [<branch>]]
 'git rebase' [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>]
        --root [<branch>]
 'git rebase' --continue | --skip | --abort | --edit-todo
@@ -316,11 +316,8 @@ which makes little sense.
 
 -f::
 --force-rebase::
-       Force the rebase even if the current branch is a descendant
-       of the commit you are rebasing onto.  Normally non-interactive rebase will
-       exit with the message "Current branch is up to date" in such a
-       situation.
-       Incompatible with the --interactive option.
+       Force a rebase even if the current branch is up-to-date and
+       the command without `--force` would return without doing anything.
 +
 You may find this (or --no-ff with an interactive rebase) helpful after
 reverting a topic branch merge, as this option recreates the topic branch with
index dc3a568..2a0de42 100644 (file)
@@ -35,6 +35,16 @@ OPTIONS
        Instead of explicitly specifying which refs to update,
        update all heads that locally exist.
 
+--stdin::
+       Take the list of refs from stdin, one per line. If there
+       are refs specified on the command line in addition to this
+       option, then the refs from stdin are processed after those
+       on the command line.
++
+If '--stateless-rpc' is specified together with this option then
+the list of refs must be in packet format (pkt-line). Each ref must
+be in a separate packet, and the list must end with a flush packet.
+
 --dry-run::
        Do everything except actually send the updates.
 
@@ -77,7 +87,8 @@ this flag.
 Without '--all' and without any '<ref>', the heads that exist
 both on the local side and on the remote side are updated.
 
-When one or more '<ref>' are specified explicitly, it can be either a
+When one or more '<ref>' are specified explicitly (whether on the
+command line or via `--stdin`), it can be either a
 single pattern, or a pair of such pattern separated by a colon
 ":" (this means that a ref name cannot have a colon in it).  A
 single pattern '<name>' is just a shorthand for '<name>:<name>'.
index 8b2c542..c6175d4 100644 (file)
@@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v2.1.1/git.html[documentation for release 2.1.1]
+* link:v2.1.2/git.html[documentation for release 2.1.2]
 
 * release notes for
+  link:RelNotes/2.1.2.txt[2.1.2],
   link:RelNotes/2.1.1.txt[2.1.1],
   link:RelNotes/2.1.0.txt[2.1].
 
index c76c8d6..14b866e 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v2.1.1
+DEF_VER=v2.1.2
 
 LF='
 '
index 12cca32..f2706be 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/2.1.1.txt
\ No newline at end of file
+Documentation/RelNotes/2.1.2.txt
\ No newline at end of file
index fcd8474..7bba516 100644 (file)
@@ -586,7 +586,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
                return git_config_set_multivar_in_file(given_config_source.file,
-                                                      argv[0], value, "^$", 0);
+                                                      argv[0], value,
+                                                      CONFIG_REGEX_NONE, 0);
        }
        else if (actions == ACTION_REPLACE_ALL) {
                check_write();
index d42a27d..0928a98 100644 (file)
@@ -388,7 +388,8 @@ static void fsck_sha1_list(void)
                unsigned char *sha1 = entry->sha1;
 
                sha1_list.entry[i] = NULL;
-               fsck_sha1(sha1);
+               if (fsck_sha1(sha1))
+                       errors_found |= ERROR_OBJECT;
                free(entry);
        }
        sha1_list.nr = 0;
@@ -488,6 +489,7 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
        obj = parse_object(sha1);
        if (!obj) {
                error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+               errors_found |= ERROR_REACHABLE;
                /* We'll continue with the rest despite the error.. */
                return 0;
        }
@@ -504,7 +506,7 @@ static void get_default_heads(void)
 {
        if (head_points_at && !is_null_sha1(head_sha1))
                fsck_handle_ref("HEAD", head_sha1, 0, NULL);
-       for_each_ref(fsck_handle_ref, NULL);
+       for_each_rawref(fsck_handle_ref, NULL);
        if (include_reflogs)
                for_each_reflog(fsck_handle_reflog, NULL);
 
index 5568a5b..eebf1a8 100644 (file)
@@ -112,6 +112,10 @@ static pthread_mutex_t deepest_delta_mutex;
 #define deepest_delta_lock()   lock_mutex(&deepest_delta_mutex)
 #define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex)
 
+static pthread_mutex_t type_cas_mutex;
+#define type_cas_lock()                lock_mutex(&type_cas_mutex)
+#define type_cas_unlock()      unlock_mutex(&type_cas_mutex)
+
 static pthread_key_t key;
 
 static inline void lock_mutex(pthread_mutex_t *mutex)
@@ -135,6 +139,7 @@ static void init_thread(void)
        init_recursive_mutex(&read_mutex);
        pthread_mutex_init(&counter_mutex, NULL);
        pthread_mutex_init(&work_mutex, NULL);
+       pthread_mutex_init(&type_cas_mutex, NULL);
        if (show_stat)
                pthread_mutex_init(&deepest_delta_mutex, NULL);
        pthread_key_create(&key, NULL);
@@ -157,6 +162,7 @@ static void cleanup_thread(void)
        pthread_mutex_destroy(&read_mutex);
        pthread_mutex_destroy(&counter_mutex);
        pthread_mutex_destroy(&work_mutex);
+       pthread_mutex_destroy(&type_cas_mutex);
        if (show_stat)
                pthread_mutex_destroy(&deepest_delta_mutex);
        for (i = 0; i < nr_threads; i++)
@@ -862,7 +868,6 @@ static void resolve_delta(struct object_entry *delta_obj,
 {
        void *base_data, *delta_data;
 
-       delta_obj->real_type = base->obj->real_type;
        if (show_stat) {
                delta_obj->delta_depth = base->obj->delta_depth + 1;
                deepest_delta_lock();
@@ -888,6 +893,26 @@ static void resolve_delta(struct object_entry *delta_obj,
        counter_unlock();
 }
 
+/*
+ * Standard boolean compare-and-swap: atomically check whether "*type" is
+ * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched
+ * and return false.
+ */
+static int compare_and_swap_type(enum object_type *type,
+                                enum object_type want,
+                                enum object_type set)
+{
+       enum object_type old;
+
+       type_cas_lock();
+       old = *type;
+       if (old == want)
+               *type = set;
+       type_cas_unlock();
+
+       return old == want;
+}
+
 static struct base_data *find_unresolved_deltas_1(struct base_data *base,
                                                  struct base_data *prev_base)
 {
@@ -915,7 +940,10 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
                struct object_entry *child = objects + deltas[base->ref_first].obj_no;
                struct base_data *result = alloc_base_data();
 
-               assert(child->real_type == OBJ_REF_DELTA);
+               if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA,
+                                          base->obj->real_type))
+                       die("BUG: child->real_type != OBJ_REF_DELTA");
+
                resolve_delta(child, base, result);
                if (base->ref_first == base->ref_last && base->ofs_last == -1)
                        free_base_data(base);
@@ -929,6 +957,7 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base,
                struct base_data *result = alloc_base_data();
 
                assert(child->real_type == OBJ_OFS_DELTA);
+               child->real_type = base->obj->real_type;
                resolve_delta(child, base, result);
                if (base->ofs_first == base->ofs_last)
                        free_base_data(base);
index f420b74..4b1bc0f 100644 (file)
@@ -110,6 +110,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        int flags;
        unsigned int reject_reasons;
        int progress = -1;
+       int from_stdin = 0;
        struct push_cas_option cas = {0};
 
        argv++;
@@ -169,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.stateless_rpc = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--stdin")) {
+                               from_stdin = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--helper-status")) {
                                helper_status = 1;
                                continue;
@@ -201,6 +206,28 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        }
        if (!dest)
                usage(send_pack_usage);
+
+       if (from_stdin) {
+               struct argv_array all_refspecs = ARGV_ARRAY_INIT;
+
+               for (i = 0; i < nr_refspecs; i++)
+                       argv_array_push(&all_refspecs, refspecs[i]);
+
+               if (args.stateless_rpc) {
+                       const char *buf;
+                       while ((buf = packet_read_line(0, NULL)))
+                               argv_array_push(&all_refspecs, buf);
+               } else {
+                       struct strbuf line = STRBUF_INIT;
+                       while (strbuf_getline(&line, stdin, '\n') != EOF)
+                               argv_array_push(&all_refspecs, line.buf);
+                       strbuf_release(&line);
+               }
+
+               refspecs = all_refspecs.argv;
+               nr_refspecs = all_refspecs.argc;
+       }
+
        /*
         * --all and --mirror are incompatible; neither makes sense
         * with any refspecs.
diff --git a/cache.h b/cache.h
index fcb511d..dcf3a2a 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -1281,6 +1281,8 @@ extern int update_server_info(int);
 #define CONFIG_INVALID_PATTERN 6
 #define CONFIG_GENERIC_ERROR 7
 
+#define CONFIG_REGEX_NONE ((void *)1)
+
 struct git_config_source {
        unsigned int use_stdin:1;
        const char *file;
index 6cbf701..9e42d38 100644 (file)
--- a/config.c
+++ b/config.c
@@ -1236,10 +1236,15 @@ static struct {
 
 static int matches(const char *key, const char *value)
 {
-       return !strcmp(key, store.key) &&
-               (store.value_regex == NULL ||
-                (store.do_not_match ^
-                 !regexec(store.value_regex, value, 0, NULL, 0)));
+       if (strcmp(key, store.key))
+               return 0; /* not ours */
+       if (!store.value_regex)
+               return 1; /* always matches */
+       if (store.value_regex == CONFIG_REGEX_NONE)
+               return 0; /* never matches */
+
+       return store.do_not_match ^
+               (value && !regexec(store.value_regex, value, 0, NULL, 0));
 }
 
 static int store_aux(const char *key, const char *value, void *cb)
@@ -1501,6 +1506,8 @@ out_free_ret_1:
 /*
  * If value==NULL, unset in (remove from) config,
  * if value_regex!=NULL, disregard key/value pairs where value does not match.
+ * if value_regex==CONFIG_REGEX_NONE, do not match any existing values
+ *     (only add a new one)
  * if multi_replace==0, nothing, or only one matching key/value is replaced,
  *     else all matching key/values (regardless how many) are removed,
  *     before the new pair is written.
@@ -1584,6 +1591,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
 
                if (value_regex == NULL)
                        store.value_regex = NULL;
+               else if (value_regex == CONFIG_REGEX_NONE)
+                       store.value_regex = CONFIG_REGEX_NONE;
                else {
                        if (value_regex[0] == '!') {
                                store.do_not_match = 1;
@@ -1615,7 +1624,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
                if (git_config_from_file(store_aux, config_filename, NULL)) {
                        error("invalid config file %s", config_filename);
                        free(store.key);
-                       if (store.value_regex != NULL) {
+                       if (store.value_regex != NULL &&
+                           store.value_regex != CONFIG_REGEX_NONE) {
                                regfree(store.value_regex);
                                free(store.value_regex);
                        }
@@ -1624,7 +1634,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
                }
 
                free(store.key);
-               if (store.value_regex != NULL) {
+               if (store.value_regex != NULL &&
+                   store.value_regex != CONFIG_REGEX_NONE) {
                        regfree(store.value_regex);
                        free(store.value_regex);
                }
index a1479e9..a40b4ea 100644 (file)
@@ -1422,7 +1422,7 @@ static void mktree(struct tree_content *t, int v, struct strbuf *b)
 
 static void store_tree(struct tree_entry *root)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        unsigned int i, j, del;
        struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 };
        struct object_entry *le = NULL;
@@ -1430,6 +1430,10 @@ static void store_tree(struct tree_entry *root)
        if (!is_null_sha1(root->versions[1].sha1))
                return;
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
+
        for (i = 0; i < t->entry_count; i++) {
                if (t->entries[i]->tree)
                        store_tree(t->entries[i]);
index 461cc14..a33a38e 100644 (file)
--- a/po/TEAMS
+++ b/po/TEAMS
@@ -17,6 +17,7 @@ Members:      Thomas Rast <tr@thomasrast.ch>
                Christian Stimming <stimming@tuhh.de>
                Phillip Szelat <phillip.szelat@gmail.com>
                Matthias Rüster <matthias.ruester@gmail.com>
+               Magnus Görlitz <magnus.goerlitz@googlemail.com>
 
 Language:      fr (French)
 Repository:    https://github.com/jnavila/git
index e5d2b25..c807967 100644 (file)
--- a/po/de.po
+++ b/po/de.po
@@ -29,7 +29,7 @@ msgid ""
 "'git commit -a'."
 msgstr ""
 "Korrigieren Sie dies im Arbeitsverzeichnis, und benutzen Sie\n"
-"dann 'git add/rm <Datei>' um die Auflösung entsprechend zu markieren\n"
+"dann 'git add/rm <Datei>', um die Auflösung entsprechend zu markieren\n"
 "und zu committen, oder benutzen Sie 'git commit -a'."
 
 #: archive.c:10
@@ -619,7 +619,7 @@ msgstr "Fehler beim Erstellen des Pfades '%s'%s"
 #: merge-recursive.c:703
 #, c-format
 msgid "Removing %s to make room for subdirectory\n"
-msgstr "Entferne %s um Platz für Unterverzeichnis zu schaffen\n"
+msgstr "Entferne %s, um Platz für Unterverzeichnis zu schaffen\n"
 
 #: merge-recursive.c:717 merge-recursive.c:738
 msgid ": perhaps a D/F conflict?"
@@ -1037,7 +1037,7 @@ msgstr[1] "Ihr Branch ist vor '%s' um %d Commits.\n"
 
 #: remote.c:1960
 msgid "  (use \"git push\" to publish your local commits)\n"
-msgstr "  (benutzen Sie \"git push\" um lokale Commits zu publizieren)\n"
+msgstr "  (benutzen Sie \"git push\", um lokale Commits zu publizieren)\n"
 
 #: remote.c:1963
 #, c-format
@@ -1052,7 +1052,7 @@ msgstr[1] ""
 #: remote.c:1971
 msgid "  (use \"git pull\" to update your local branch)\n"
 msgstr ""
-"  (benutzen Sie \"git pull\" um Ihren lokalen Branch zu aktualisieren)\n"
+"  (benutzen Sie \"git pull\", um Ihren lokalen Branch zu aktualisieren)\n"
 
 #: remote.c:1974
 #, c-format
@@ -1072,7 +1072,7 @@ msgstr[1] ""
 #: remote.c:1984
 msgid "  (use \"git pull\" to merge the remote branch into yours)\n"
 msgstr ""
-"  (benutzen Sie \"git pull\" um Ihren Branch mit dem Remote-Branch "
+"  (benutzen Sie \"git pull\", um Ihren Branch mit dem Remote-Branch "
 "zusammenzuführen)\n"
 
 #: run-command.c:80
@@ -1136,7 +1136,7 @@ msgstr "Ihre lokalen Änderungen würden von \"revert\" überschrieben werden."
 #: sequencer.c:233
 msgid "Commit your changes or stash them to proceed."
 msgstr ""
-"Tragen Sie Ihre Änderungen ein oder benutzen Sie \"stash\" um fortzufahren."
+"Tragen Sie Ihre Änderungen ein oder benutzen Sie \"stash\", um fortzufahren."
 
 #: sequencer.c:250
 msgid "Failed to lock HEAD during fast_forward_to"
@@ -1488,18 +1488,18 @@ msgstr ""
 #: wt-status.c:183
 msgid "  (use \"git add <file>...\" to mark resolution)"
 msgstr ""
-"  (benutzen Sie \"git add/rm <Datei>...\" um die Auflösung zu markieren)"
+"  (benutzen Sie \"git add/rm <Datei>...\", um die Auflösung zu markieren)"
 
 #: wt-status.c:185 wt-status.c:189
 msgid "  (use \"git add/rm <file>...\" as appropriate to mark resolution)"
 msgstr ""
-"  (benutzen Sie \"git add/rm <Datei>...\" um die Auflösung entsprechend zu "
+"  (benutzen Sie \"git add/rm <Datei>...\", um die Auflösung entsprechend zu "
 "markieren)"
 
 #: wt-status.c:187
 msgid "  (use \"git rm <file>...\" to mark resolution)"
 msgstr ""
-"  (benutzen Sie \"git add/rm <Datei>...\" um die Auflösung zu markieren)"
+"  (benutzen Sie \"git add/rm <Datei>...\", um die Auflösung zu markieren)"
 
 #: wt-status.c:198
 msgid "Changes to be committed:"
@@ -1512,20 +1512,20 @@ msgstr "Änderungen, die nicht zum Commit vorgemerkt sind:"
 #: wt-status.c:220
 msgid "  (use \"git add <file>...\" to update what will be committed)"
 msgstr ""
-"  (benutzen Sie \"git add <Datei>...\" um die Änderungen zum Commit "
+"  (benutzen Sie \"git add <Datei>...\", um die Änderungen zum Commit "
 "vorzumerken)"
 
 #: wt-status.c:222
 msgid "  (use \"git add/rm <file>...\" to update what will be committed)"
 msgstr ""
-"  (benutzen Sie \"git add/rm <Datei>...\" um die Änderungen zum Commit "
+"  (benutzen Sie \"git add/rm <Datei>...\", um die Änderungen zum Commit "
 "vorzumerken)"
 
 #: wt-status.c:223
 msgid ""
 "  (use \"git checkout -- <file>...\" to discard changes in working directory)"
 msgstr ""
-"  (benutzen Sie \"git checkout -- <Datei>...\" um die Änderungen im "
+"  (benutzen Sie \"git checkout -- <Datei>...\", um die Änderungen im "
 "Arbeitsverzeichnis zu verwerfen)"
 
 #: wt-status.c:225
@@ -1538,7 +1538,7 @@ msgstr ""
 #, c-format
 msgid "  (use \"git %s <file>...\" to include in what will be committed)"
 msgstr ""
-"  (benutzen Sie \"git %s <Datei>...\" um die Änderungen zum Commit "
+"  (benutzen Sie \"git %s <Datei>...\", um die Änderungen zum Commit "
 "vorzumerken)"
 
 #: wt-status.c:252
@@ -1653,7 +1653,7 @@ msgstr "Alle Konflikte sind behoben, aber Sie sind immer noch beim Merge."
 
 #: wt-status.c:945
 msgid "  (use \"git commit\" to conclude merge)"
-msgstr "  (benutzen Sie \"git commit\" um den Merge abzuschließen)"
+msgstr "  (benutzen Sie \"git commit\", um den Merge abzuschließen)"
 
 #: wt-status.c:955
 msgid "You are in the middle of an am session."
@@ -1670,12 +1670,12 @@ msgstr ""
 
 #: wt-status.c:964
 msgid "  (use \"git am --skip\" to skip this patch)"
-msgstr "  (benutzen Sie \"git am --skip\" um diesen Patch auszulassen)"
+msgstr "  (benutzen Sie \"git am --skip\", um diesen Patch auszulassen)"
 
 #: wt-status.c:966
 msgid "  (use \"git am --abort\" to restore the original branch)"
 msgstr ""
-"  (benutzen Sie \"git am --abort\" um den ursprünglichen Branch "
+"  (benutzen Sie \"git am --abort\", um den ursprünglichen Branch "
 "wiederherzustellen)"
 
 #: wt-status.c:1026 wt-status.c:1043
@@ -1695,12 +1695,12 @@ msgstr ""
 
 #: wt-status.c:1036
 msgid "  (use \"git rebase --skip\" to skip this patch)"
-msgstr "  (benutzen Sie \"git rebase --skip\" um diesen Patch auszulassen)"
+msgstr "  (benutzen Sie \"git rebase --skip\", um diesen Patch auszulassen)"
 
 #: wt-status.c:1038
 msgid "  (use \"git rebase --abort\" to check out the original branch)"
 msgstr ""
-"  (benutzen Sie \"git rebase --abort\" um den ursprünglichen Branch "
+"  (benutzen Sie \"git rebase --abort\", um den ursprünglichen Branch "
 "auszuchecken)"
 
 #: wt-status.c:1051
@@ -1739,7 +1739,7 @@ msgstr "Sie editieren gerade einen Commit während eines Rebase."
 #: wt-status.c:1075
 msgid "  (use \"git commit --amend\" to amend the current commit)"
 msgstr ""
-"  (benutzen Sie \"git commit --amend\" um den aktuellen Commit nachzubessern)"
+"  (benutzen Sie \"git commit --amend\", um den aktuellen Commit nachzubessern)"
 
 #: wt-status.c:1077
 msgid ""
@@ -1767,7 +1767,7 @@ msgstr ""
 #: wt-status.c:1097
 msgid "  (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)"
 msgstr ""
-"  (benutzen Sie \"git cherry-pick --abort\" um die Cherry-Pick-Operation "
+"  (benutzen Sie \"git cherry-pick --abort\", um die Cherry-Pick-Operation "
 "abzubrechen)"
 
 #: wt-status.c:1106
@@ -1788,7 +1788,7 @@ msgstr "  (alle Konflikte behoben: führen Sie \"git revert --continue\" aus)"
 #: wt-status.c:1116
 msgid "  (use \"git revert --abort\" to cancel the revert operation)"
 msgstr ""
-"  (benutzen Sie \"git revert --abort\" um die Revert-Operation abzubrechen)"
+"  (benutzen Sie \"git revert --abort\", um die Revert-Operation abzubrechen)"
 
 #: wt-status.c:1127
 #, c-format
@@ -1802,7 +1802,7 @@ msgstr "Sie sind gerade bei einer binären Suche."
 #: wt-status.c:1134
 msgid "  (use \"git bisect reset\" to get back to the original branch)"
 msgstr ""
-"  (benutzen Sie \"git bisect reset\" um zum ursprünglichen Branch "
+"  (benutzen Sie \"git bisect reset\", um zum ursprünglichen Branch "
 "zurückzukehren)"
 
 #: wt-status.c:1309
@@ -1855,7 +1855,7 @@ msgstr "Unbeobachtete Dateien nicht aufgelistet%s"
 
 #: wt-status.c:1373
 msgid " (use -u option to show untracked files)"
-msgstr " (benutzen Sie die Option -u um unbeobachteten Dateien anzuzeigen)"
+msgstr " (benutzen Sie die Option -u, um unbeobachteten Dateien anzuzeigen)"
 
 #: wt-status.c:1379
 msgid "No changes"
@@ -2235,7 +2235,7 @@ msgstr[1] ""
 #: builtin/apply.c:2818
 #, c-format
 msgid "Context reduced to (%ld/%ld) to apply fragment at %d"
-msgstr "Kontext reduziert zu (%ld/%ld) um Patch-Bereich bei %d anzuwenden"
+msgstr "Kontext reduziert zu (%ld/%ld), um Patch-Bereich bei %d anzuwenden"
 
 #: builtin/apply.c:2824
 #, c-format
@@ -2691,7 +2691,7 @@ msgstr "Unterdrückt den Namen des Autors und den Zeitstempel (Standard: aus)"
 
 #: builtin/blame.c:2514
 msgid "Show author email instead of name (Default: off)"
-msgstr "Zeigt anstatt des Namens die Email-Adresse des Autors (Standard: aus)"
+msgstr "Zeigt anstatt des Namens die E-Mail-Adresse des Autors (Standard: aus)"
 
 #: builtin/blame.c:2515
 msgid "Ignore whitespace differences"
@@ -3085,7 +3085,7 @@ msgstr "zu viele Branches für eine Umbenennen-Operation angegeben"
 
 #: builtin/branch.c:952
 msgid "too many branches to set new upstream"
-msgstr "zu viele Branches angegeben um Upstream-Branch zu setzen"
+msgstr "zu viele Branches angegeben, um Upstream-Branch zu setzen"
 
 #: builtin/branch.c:956
 #, c-format
@@ -3108,7 +3108,7 @@ msgstr "Branch '%s' existiert nicht"
 #: builtin/branch.c:975
 msgid "too many branches to unset upstream"
 msgstr ""
-"zu viele Branches angegeben um Konfiguration zu Upstream-Branch zu entfernen"
+"zu viele Branches angegeben, um Konfiguration zu Upstream-Branch zu entfernen"
 
 #: builtin/branch.c:979
 msgid "could not unset upstream of HEAD when it does not point to any branch."
@@ -5071,7 +5071,7 @@ msgstr "gibt für jeden Commit das gesamte Verzeichnis aus"
 
 #: builtin/fast-export.c:718
 msgid "Use the done feature to terminate the stream"
-msgstr "Benutzt die \"done\"-Funktion um den Strom abzuschließen"
+msgstr "Benutzt die \"done\"-Funktion, um den Strom abzuschließen"
 
 #: builtin/fast-export.c:719
 msgid "Skip output of blob data"
@@ -5268,7 +5268,7 @@ msgid ""
 " 'git remote prune %s' to remove any old, conflicting branches"
 msgstr ""
 "Einige lokale Referenzen konnten nicht aktualisiert werden; versuchen Sie\n"
-"'git remote prune %s' um jeden älteren, widersprüchlichen Branch zu löschen."
+"'git remote prune %s', um jeden älteren, widersprüchlichen Branch zu löschen."
 
 #: builtin/fetch.c:759
 #, c-format
@@ -6535,7 +6535,7 @@ msgstr "zeigt Patchformat anstatt des Standards (Patch + Zusammenfassung)"
 
 #: builtin/log.c:1217
 msgid "Messaging"
-msgstr "Email-Einstellungen"
+msgstr "E-Mail-Einstellungen"
 
 #: builtin/log.c:1218
 msgid "header"
@@ -6543,11 +6543,11 @@ msgstr "Header"
 
 #: builtin/log.c:1219
 msgid "add email header"
-msgstr "fügt Email-Header hinzu"
+msgstr "fügt E-Mail-Header hinzu"
 
 #: builtin/log.c:1220 builtin/log.c:1222
 msgid "email"
-msgstr "Email"
+msgstr "E-Mail"
 
 #: builtin/log.c:1220
 msgid "add To: header"
@@ -6573,7 +6573,7 @@ msgstr "message-id"
 
 #: builtin/log.c:1228
 msgid "make first mail a reply to <message-id>"
-msgstr "macht aus erster Email eine Antwort zu <message-id>"
+msgstr "macht aus erster E-Mail eine Antwort zu <message-id>"
 
 #: builtin/log.c:1229 builtin/log.c:1232
 msgid "boundary"
@@ -6978,7 +6978,7 @@ msgstr "konnte nicht von '%s' lesen"
 #, c-format
 msgid "Not committing merge; use 'git commit' to complete the merge.\n"
 msgstr ""
-"Merge wurde nicht committet; benutzen Sie 'git commit' um den Merge "
+"Merge wurde nicht committet; benutzen Sie 'git commit', um den Merge "
 "abzuschließen.\n"
 
 #: builtin/merge.c:809
@@ -6990,7 +6990,7 @@ msgid ""
 "Lines starting with '%c' will be ignored, and an empty message aborts\n"
 "the commit.\n"
 msgstr ""
-"Bitte geben Sie eine Commit-Beschreibung ein um zu erklären, warum dieser\n"
+"Bitte geben Sie eine Commit-Beschreibung ein, um zu erklären, warum dieser\n"
 "Merge erforderlich ist, insbesondere wenn es einen aktualisierten\n"
 "Upstream-Branch mit einem Thema-Branch zusammenführt.\n"
 "\n"
@@ -7156,7 +7156,7 @@ msgstr "Merge mit Strategie %s fehlgeschlagen.\n"
 #: builtin/merge.c:1539
 #, c-format
 msgid "Using the %s to prepare resolving by hand.\n"
-msgstr "Benutzen Sie \"%s\" um die Auflösung per Hand vorzubereiten.\n"
+msgstr "Benutzen Sie \"%s\", um die Auflösung per Hand vorzubereiten.\n"
 
 #: builtin/merge.c:1551
 #, c-format
@@ -7299,7 +7299,7 @@ msgid "Please, stage your changes to .gitmodules or stash them to proceed"
 msgstr ""
 "Bitte merken Sie Ihre Änderungen in .gitmodules zum Commit vor oder "
 "benutzen\n"
-"Sie \"stash\" um fortzufahren."
+"Sie \"stash\", um fortzufahren."
 
 #: builtin/mv.c:156
 #, c-format
@@ -7368,7 +7368,7 @@ msgstr "zeigt nur Namen an (keine SHA-1)"
 
 #: builtin/name-rev.c:310
 msgid "only use tags to name the commits"
-msgstr "verwendet nur Tags um die Commits zu benennen"
+msgstr "verwendet nur Tags, um die Commits zu benennen"
 
 #: builtin/name-rev.c:312
 msgid "only use refs matching <pattern>"
@@ -7621,7 +7621,7 @@ msgid ""
 "existing notes"
 msgstr ""
 "Konnte Notizen nicht hinzufügen. Existierende Notizen für Objekt %s "
-"gefunden. Verwenden Sie '-f' um die existierenden Notizen zu überschreiben."
+"gefunden. Verwenden Sie '-f', um die existierenden Notizen zu überschreiben."
 
 #: builtin/notes.c:460 builtin/notes.c:537
 #, c-format
@@ -7649,7 +7649,7 @@ msgid ""
 "existing notes"
 msgstr ""
 "Kann Notizen nicht kopieren. Existierende Notizen für Objekt %s gefunden. "
-"Verwenden Sie '-f' um die existierenden Notizen zu überschreiben."
+"Verwenden Sie '-f', um die existierenden Notizen zu überschreiben."
 
 #: builtin/notes.c:543
 #, c-format
@@ -9359,7 +9359,7 @@ msgid ""
 "(use -f to force removal)"
 msgstr ""
 "\n"
-"(benutzen Sie -f um die Löschung zu erzwingen)"
+"(benutzen Sie -f, um die Löschung zu erzwingen)"
 
 #: builtin/rm.c:240
 msgid "the following file has changes staged in the index:"
@@ -9373,7 +9373,7 @@ msgid ""
 "(use --cached to keep the file, or -f to force removal)"
 msgstr ""
 "\n"
-"(benutzen Sie --cached um die Datei zu behalten, oder -f um die Entfernung "
+"(benutzen Sie --cached, um die Datei zu behalten, oder -f, um die Entfernung "
 "zu erzwingen)"
 
 #: builtin/rm.c:252
@@ -9431,7 +9431,7 @@ msgstr "Unterdrückt Commit-Beschreibungen, liefert nur Anzahl der Commits"
 
 #: builtin/shortlog.c:234
 msgid "Show the email address of each author"
-msgstr "Zeigt die Email-Adresse von jedem Autor"
+msgstr "Zeigt die E-Mail-Adresse von jedem Autor"
 
 #: builtin/shortlog.c:235
 msgid "w[,i1[,i2]]"
@@ -9751,7 +9751,7 @@ msgstr "annotiertes und GPG-signiertes Tag"
 
 #: builtin/tag.c:605
 msgid "use another key to sign the tag"
-msgstr "verwendet einen anderen Schlüssel um das Tag zu signieren"
+msgstr "verwendet einen anderen Schlüssel, um das Tag zu signieren"
 
 #: builtin/tag.c:606
 msgid "replace the tag if exists"
@@ -10046,7 +10046,7 @@ msgid ""
 msgstr ""
 "'git help -a' und 'git help -g' listet verfügbare Unterkommandos und\n"
 "einige Anleitungen zu Git-Konzepten auf. Benutzen Sie 'git help <Kommando>'\n"
-"oder 'git help <Konzept>' um mehr über ein spezifisches Kommando oder\n"
+"oder 'git help <Konzept>', um mehr über ein spezifisches Kommando oder\n"
 "Konzept zu erfahren."
 
 #: parse-options.h:143
@@ -10200,7 +10200,7 @@ msgstr ""
 #: git-am.sh:141
 msgid "Using index info to reconstruct a base tree..."
 msgstr ""
-"Verwende Informationen aus der Staging-Area um einen Basisverzeichnis "
+"Verwende Informationen aus der Staging-Area, um einen Basisverzeichnis "
 "nachzustellen"
 
 #: git-am.sh:156
@@ -10257,7 +10257,7 @@ msgid ""
 "Use \"git am --abort\" to remove it."
 msgstr ""
 "Stray $dotest Verzeichnis gefunden.\n"
-"Benutzen Sie \"git am --abort\" um es zu entfernen."
+"Benutzen Sie \"git am --abort\", um es zu entfernen."
 
 #: git-am.sh:535
 msgid "Resolve operation not in progress, we are not resuming."
@@ -10284,7 +10284,7 @@ msgstr ""
 
 #: git-am.sh:732
 msgid "Patch does not have a valid e-mail address."
-msgstr "Patch enthält keine gültige Email-Adresse."
+msgstr "Patch enthält keine gültige E-Mail-Adresse."
 
 #: git-am.sh:779
 msgid "cannot be interactive without stdin connected to a terminal."
@@ -10515,7 +10515,7 @@ msgid ""
 msgstr ""
 "\"pull\" ist nicht möglich, weil Sie nicht zusammengeführte Dateien haben.\n"
 "Bitte korrigieren Sie dies im Arbeitsverzeichnis und benutzen Sie dann \n"
-"'git add/rm <Datei>' um die Auflösung entsprechend zu markieren, oder\n"
+"'git add/rm <Datei>', um die Auflösung entsprechend zu markieren, oder\n"
 "benutzen Sie 'git commit -a'."
 
 #: git-pull.sh:25
@@ -11014,7 +11014,7 @@ msgid ""
 "discard them"
 msgstr ""
 "Arbeitsverzeichnis von Submodul in '$displaypath' enthält lokale Änderungen; "
-"verwenden Sie '-f' um diese zu verwerfen"
+"verwenden Sie '-f', um diese zu verwerfen"
 
 #: git-submodule.sh:701
 #, sh-format
index 654a8c5..6f6835b 100644 (file)
@@ -229,6 +229,9 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
        /* Add all external refs */
        for_each_ref(add_one_ref, revs);
 
+       /* detached HEAD is not included in the list above */
+       head_ref(add_one_ref, revs);
+
        /* Add all reflog info */
        if (mark_reflog)
                for_each_reflog(add_one_reflog, revs);
index 0fcf2ce..558b9fe 100644 (file)
@@ -863,6 +863,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
        int i, err;
        struct argv_array args;
        struct string_list_item *cas_option;
+       struct strbuf preamble = STRBUF_INIT;
 
        argv_array_init(&args);
        argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
@@ -880,17 +881,22 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
        for_each_string_list_item(cas_option, &cas_options)
                argv_array_push(&args, cas_option->string);
        argv_array_push(&args, url.buf);
+
+       argv_array_push(&args, "--stdin");
        for (i = 0; i < nr_spec; i++)
-               argv_array_push(&args, specs[i]);
+               packet_buf_write(&preamble, "%s\n", specs[i]);
+       packet_buf_flush(&preamble);
 
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-receive-pack",
        rpc.argv = args.argv;
+       rpc.stdin_preamble = &preamble;
 
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
                write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
+       strbuf_release(&preamble);
        argv_array_clear(&args);
        return err;
 }
index 3a2c819..3b92083 100755 (executable)
@@ -111,4 +111,24 @@ test_expect_success 'unset many entries' '
        test_must_fail git config section.key
 '
 
+test_expect_success '--add appends new value after existing empty value' '
+       cat >expect <<-\EOF &&
+
+
+       fool
+       roll
+       EOF
+       cp .git/config .git/config.old &&
+       test_when_finished "mv .git/config.old .git/config" &&
+       cat >.git/config <<-\EOF &&
+       [foo]
+               baz
+               baz =
+               baz = fool
+       EOF
+       git config --add foo.baz roll &&
+       git config --get-all foo.baz >output &&
+       test_cmp expect output
+'
+
 test_done
index 8c739c9..b52397a 100755 (executable)
@@ -69,7 +69,7 @@ test_expect_success 'object with bad sha1' '
        git update-ref refs/heads/bogus $cmt &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
 
-       test_might_fail git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "$sha.*corrupt" out
 '
@@ -101,7 +101,7 @@ test_expect_success 'email with embedded > is not okay' '
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new" out
 '
@@ -113,7 +113,7 @@ test_expect_success 'missing < email delimiter is reported nicely' '
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.* - bad name" out
 '
@@ -125,7 +125,7 @@ test_expect_success 'missing email is reported nicely' '
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.* - missing email" out
 '
@@ -137,7 +137,7 @@ test_expect_success '> in name is reported' '
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new" out
 '
@@ -151,11 +151,31 @@ test_expect_success 'integer overflow in timestamps is reported' '
        test_when_finished "remove_object $new" &&
        git update-ref refs/heads/bogus "$new" &&
        test_when_finished "git update-ref -d refs/heads/bogus" &&
-       git fsck 2>out &&
+       test_must_fail git fsck 2>out &&
        cat out &&
        grep "error in commit $new.*integer overflow" out
 '
 
+test_expect_success 'malformatted tree object' '
+       test_when_finished "git update-ref -d refs/tags/wrong" &&
+       test_when_finished "remove_object \$T" &&
+       T=$(
+               GIT_INDEX_FILE=test-index &&
+               export GIT_INDEX_FILE &&
+               rm -f test-index &&
+               >x &&
+               git add x &&
+               T=$(git write-tree) &&
+               (
+                       git cat-file tree $T &&
+                       git cat-file tree $T
+               ) |
+               git hash-object -w -t tree --stdin
+       ) &&
+       test_must_fail git fsck 2>out &&
+       grep "error in tree .*contains duplicate file entries" out
+'
+
 test_expect_success 'tag pointing to nonexistent' '
        cat >invalid-tag <<-\EOF &&
        object ffffffffffffffffffffffffffffffffffffffff
@@ -282,4 +302,60 @@ test_expect_success 'fsck notices ".git" in trees' '
        )
 '
 
+# create a static test repo which is broken by omitting
+# one particular object ($1, which is looked up via rev-parse
+# in the new repository).
+create_repo_missing () {
+       rm -rf missing &&
+       git init missing &&
+       (
+               cd missing &&
+               git commit -m one --allow-empty &&
+               mkdir subdir &&
+               echo content >subdir/file &&
+               git add subdir/file &&
+               git commit -m two &&
+               unrelated=$(echo unrelated | git hash-object --stdin -w) &&
+               git tag -m foo tag $unrelated &&
+               sha1=$(git rev-parse --verify "$1") &&
+               path=$(echo $sha1 | sed 's|..|&/|') &&
+               rm .git/objects/$path
+       )
+}
+
+test_expect_success 'fsck notices missing blob' '
+       create_repo_missing HEAD:subdir/file &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing subtree' '
+       create_repo_missing HEAD:subdir &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing root tree' '
+       create_repo_missing HEAD^{tree} &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing parent' '
+       create_repo_missing HEAD^ &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices missing tagged object' '
+       create_repo_missing tag^{blob} &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices ref pointing to missing commit' '
+       create_repo_missing HEAD &&
+       test_must_fail git -C missing fsck
+'
+
+test_expect_success 'fsck notices ref pointing to missing tag' '
+       create_repo_missing tag &&
+       test_must_fail git -C missing fsck
+'
+
 test_done
index 58b792b..67bd8ec 100755 (executable)
@@ -14,7 +14,7 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'fsck notices broken commit' '
-       git fsck 2>actual &&
+       test_must_fail git fsck 2>actual &&
        test_i18ngrep invalid.author actual
 '
 
index 377d3d3..01c6a3f 100755 (executable)
@@ -104,6 +104,28 @@ test_expect_success 'prune: prune unreachable heads' '
 
 '
 
+test_expect_success 'prune: do not prune detached HEAD with no reflog' '
+
+       git checkout --detach --quiet &&
+       git commit --allow-empty -m "detached commit" &&
+       # verify that there is no reflogs
+       # (should be removed and disabled by previous test)
+       test ! -e .git/logs &&
+       git prune -n >prune_actual &&
+       : >prune_expected &&
+       test_cmp prune_actual prune_expected
+
+'
+
+test_expect_success 'prune: prune former HEAD after checking out branch' '
+
+       head_sha1=$(git rev-parse HEAD) &&
+       git checkout --quiet master &&
+       git prune -v >prune_actual &&
+       grep "$head_sha1" prune_actual
+
+'
+
 test_expect_success 'prune: do not prune heads listed as an argument' '
 
        : > file2 &&
diff --git a/t/t5408-send-pack-stdin.sh b/t/t5408-send-pack-stdin.sh
new file mode 100755 (executable)
index 0000000..e8737df
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='send-pack --stdin tests'
+. ./test-lib.sh
+
+create_ref () {
+       tree=$(git write-tree) &&
+       test_tick &&
+       commit=$(echo "$1" | git commit-tree $tree) &&
+       git update-ref "$1" $commit
+}
+
+clear_remote () {
+       rm -rf remote.git &&
+       git init --bare remote.git
+}
+
+verify_push () {
+       git rev-parse "$1" >expect &&
+       git --git-dir=remote.git rev-parse "${2:-$1}" >actual &&
+       test_cmp expect actual
+}
+
+test_expect_success 'setup refs' '
+       cat >refs <<-\EOF &&
+       refs/heads/A
+       refs/heads/C
+       refs/tags/D
+       refs/heads/B
+       refs/tags/E
+       EOF
+       for i in $(cat refs); do
+               create_ref $i || return 1
+       done
+'
+
+# sanity check our setup
+test_expect_success 'refs on cmdline' '
+       clear_remote &&
+       git send-pack remote.git $(cat refs) &&
+       for i in $(cat refs); do
+               verify_push $i || return 1
+       done
+'
+
+test_expect_success 'refs over stdin' '
+       clear_remote &&
+       git send-pack remote.git --stdin <refs &&
+       for i in $(cat refs); do
+               verify_push $i || return 1
+       done
+'
+
+test_expect_success 'stdin lines are full refspecs' '
+       clear_remote &&
+       echo "A:other" >input &&
+       git send-pack remote.git --stdin <input &&
+       verify_push refs/heads/A refs/heads/other
+'
+
+test_expect_success 'stdin mixed with cmdline' '
+       clear_remote &&
+       echo A >input &&
+       git send-pack remote.git --stdin B <input &&
+       verify_push A &&
+       verify_push B
+'
+
+test_expect_success 'cmdline refs written in order' '
+       clear_remote &&
+       test_must_fail git send-pack remote.git A:foo B:foo &&
+       verify_push A foo
+'
+
+test_expect_success '--stdin refs come after cmdline' '
+       clear_remote &&
+       echo A:foo >input &&
+       test_must_fail git send-pack remote.git --stdin B:foo <input &&
+       verify_push B foo
+'
+
+test_expect_success 'refspecs and --mirror do not mix (cmdline)' '
+       clear_remote &&
+       test_must_fail git send-pack remote.git --mirror $(cat refs)
+'
+
+test_expect_success 'refspecs and --mirror do not mix (stdin)' '
+       clear_remote &&
+       test_must_fail git send-pack remote.git --mirror --stdin <refs
+'
+
+test_done
index 73af16f..db19988 100755 (executable)
@@ -323,5 +323,20 @@ test_expect_success 'push into half-auth-complete requires password' '
        test_cmp expect actual
 '
 
+run_with_limited_cmdline () {
+       (ulimit -s 128 && "$@")
+}
+
+test_lazy_prereq CMDLINE_LIMIT 'run_with_limited_cmdline true'
+
+test_expect_success CMDLINE_LIMIT 'push 2000 tags over http' '
+       sha1=$(git rev-parse HEAD) &&
+       test_seq 2000 |
+         sort |
+         sed "s|.*|$sha1 refs/tags/really-long-tag-name-&|" \
+         >.git/packed-refs &&
+       run_with_limited_cmdline git push --mirror
+'
+
 stop_httpd
 test_done
index 5fc9ef2..d400442 100755 (executable)
@@ -3017,4 +3017,108 @@ test_expect_success 'T: empty reset doesnt delete branch' '
        git rev-parse --verify refs/heads/not-to-delete
 '
 
+###
+### series U (filedelete)
+###
+
+cat >input <<INPUT_END
+commit refs/heads/U
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+test setup
+COMMIT
+M 100644 inline hello.c
+data <<BLOB
+blob 1
+BLOB
+M 100644 inline good/night.txt
+data <<BLOB
+sleep well
+BLOB
+M 100644 inline good/bye.txt
+data <<BLOB
+au revoir
+BLOB
+
+INPUT_END
+
+test_expect_success 'U: initialize for U tests' '
+       git fast-import <input
+'
+
+cat >input <<INPUT_END
+commit refs/heads/U
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+delete good/night.txt
+COMMIT
+from refs/heads/U^0
+D good/night.txt
+
+INPUT_END
+
+test_expect_success 'U: filedelete file succeeds' '
+       git fast-import <input
+'
+
+cat >expect <<EOF
+:100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D     good/night.txt
+EOF
+
+git diff-tree -M -r U^1 U >actual
+
+test_expect_success 'U: validate file delete result' '
+       compare_diff_raw expect actual
+'
+
+cat >input <<INPUT_END
+commit refs/heads/U
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+delete good dir
+COMMIT
+from refs/heads/U^0
+D good
+
+INPUT_END
+
+test_expect_success 'U: filedelete directory succeeds' '
+       git fast-import <input
+'
+
+cat >expect <<EOF
+:100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D     good/bye.txt
+EOF
+
+git diff-tree -M -r U^1 U >actual
+
+test_expect_success 'U: validate directory delete result' '
+       compare_diff_raw expect actual
+'
+
+cat >input <<INPUT_END
+commit refs/heads/U
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+must succeed
+COMMIT
+from refs/heads/U^0
+D ""
+
+INPUT_END
+
+test_expect_success 'U: filedelete root succeeds' '
+    git fast-import <input
+'
+
+cat >expect <<EOF
+:100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D     hello.c
+EOF
+
+git diff-tree -M -r U^1 U >actual
+
+test_expect_success 'U: validate root delete result' '
+       compare_diff_raw expect actual
+'
+
 test_done