Merge branch 'mm/maint-add-p-quit' into maint
authorJunio C Hamano <gitster@pobox.com>
Sun, 19 Apr 2009 19:40:14 +0000 (12:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 19 Apr 2009 19:40:14 +0000 (12:40 -0700)
* mm/maint-add-p-quit:
Update git-add.txt according to the new possibilities of 'git add -p'.
add-interactive: refactor mode hunk handling
git add -p: new "quit" command at the prompt.

22 files changed:
Documentation/RelNotes-1.6.2.4.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/git-daemon.txt
Documentation/git-imap-send.txt
Documentation/git-init.txt
Documentation/gitattributes.txt
RelNotes
builtin-apply.c
builtin-checkout.c
builtin-init-db.c
builtin-reflog.c
config.c
merge-recursive.c
t/t0001-init.sh
t/t1301-shared-repo.sh
t/t1303-wacky-config.sh
t/t2013-checkout-submodule.sh [new file with mode: 0755]
t/t3101-ls-tree-dirname.sh
t/t4010-diff-pathspec.sh
t/t7405-submodule-merge.sh [new file with mode: 0755]
tree-diff.c
tree.c
diff --git a/Documentation/RelNotes-1.6.2.4.txt b/Documentation/RelNotes-1.6.2.4.txt
new file mode 100644 (file)
index 0000000..21bf4f3
--- /dev/null
@@ -0,0 +1,31 @@
+GIT v1.6.2.4 Release Notes
+==========================
+
+Fixes since v1.6.2.3
+--------------------
+
+* The configuration parser had a buffer overflow while parsing an overlong
+  value.
+
+* "git-checkout <tree-ish> <submodule>" did not update the index entry at
+  the named path; it now does.
+
+* "git init" segfaulted when given an overlong template location via
+  the --template= option.
+
+* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
+  deciding to descend into a subdirectory but they did not match the
+  individual paths correctly.  This caused pathspecs "abc/d ab" to match
+  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
+  and then "ab" incorrectly matched "abc/0" when it shouldn't).
+
+* "git-merge-recursive" was broken when a submodule entry was involved in
+  a criss-cross merge situation.
+
+Many small documentation updates are included as well.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.6.2.3-21-ga51609a
+git shortlog --no-merges $O..maint
index f5152c5038b49ab5ebe3804e8e9b314d4a752690..6af58ff3aa9766a545079740978d85da82b3c3cf 100644 (file)
@@ -290,8 +290,10 @@ core.sharedRepository::
        group-shareable. When 'umask' (or 'false'), git will use permissions
        reported by umask(2). When '0xxx', where '0xxx' is an octal number,
        files in the repository will have this mode value. '0xxx' will override
-       user's umask value, and thus, users with a safe umask (0077) can use
-       this option. Examples: '0660' is equivalent to 'group'. '0640' is a
+       user's umask value (whereas the other options will only override
+       requested parts of the user's umask value). Examples: '0660' will make
+       the repo read/write-able for the owner and group, but inaccessible to
+       others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
        repository that is group-readable but not group-writable.
        See linkgit:git-init[1]. False by default.
 
index 36f00aed6798d543b2ee8f7315e447726fa100e5..a85121c689e394e86f7c97025b92ffa03fca0df9 100644 (file)
@@ -48,7 +48,7 @@ OPTIONS
        'git-daemon' will refuse to start when this option is enabled and no
        whitelist is specified.
 
---base-path::
+--base-path=path::
        Remap all the path requests as relative to the given path.
        This is sort of "GIT root" - if you run 'git-daemon' with
        '--base-path=/srv/git' on example.com, then if you later try to pull
@@ -81,8 +81,8 @@ OPTIONS
        Incompatible with --port, --listen, --user and --group options.
 
 --listen=host_or_ipaddr::
-       Listen on an a specific IP address or hostname.  IP addresses can
-       be either an IPv4 address or an IPV6 address if supported.  If IPv6
+       Listen on a specific IP address or hostname.  IP addresses can
+       be either an IPv4 address or an IPv6 address if supported.  If IPv6
        is not supported, then --listen=hostname is also not supported and
        --listen must be given an IPv4 address.
        Incompatible with '--inetd' option.
@@ -90,17 +90,17 @@ OPTIONS
 --port=n::
        Listen on an alternative port.  Incompatible with '--inetd' option.
 
---init-timeout::
+--init-timeout=n::
        Timeout between the moment the connection is established and the
        client request is received (typically a rather low value, since
        that should be basically immediate).
 
---timeout::
+--timeout=n::
        Timeout for specific client sub-requests. This includes the time
-       it takes for the server to process the sub-request and time spent
-       waiting for next client's request.
+       it takes for the server to process the sub-request and the time spent
+       waiting for the next client's request.
 
---max-connections::
+--max-connections=n::
        Maximum number of concurrent clients, defaults to 32.  Set it to
        zero for no limit.
 
@@ -150,7 +150,7 @@ the facility of inet daemon to achieve the same before spawning
        Enable/disable the service site-wide per default.  Note
        that a service disabled site-wide can still be enabled
        per repository if it is marked overridable and the
-       repository enables the service with an configuration
+       repository enables the service with a configuration
        item.
 
 --allow-override=service::
index 1685f04efea3d162377325406e08a762dad94d40..eed50572e08e10266907811a40d69cbf1cc7a532 100644 (file)
@@ -51,7 +51,7 @@ imap.host::
 imap.user::
        The username to use when logging in to the server.
 
-imap.password::
+imap.pass::
        The password to use when logging in to the server.
 
 imap.port::
index 71749c09d309f4cae2da9788969359d2620224a9..7151d12f349b7c6e265d5a4631029d71028a2c7d 100644 (file)
@@ -54,15 +54,21 @@ is given:
 
  - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since
    the git group may be not the primary group of all users).
+   This is used to loosen the permissions of an otherwise safe umask(2) value.
+   Note that the umask still applies to the other permission bits (e.g. if
+   umask is '0022', using 'group' will not remove read privileges from other
+   (non-group) users). See '0xxx' for how to exactly specify the repository
+   permissions.
 
  - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
    readable by all users.
 
- - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx'
-   Any option except 'umask' can be set using this option. '0xxx' will
-   override users umask(2) value, and thus, users with a safe umask (0077)
-   can use this option. '0640' will create a repository which is group-readable
-   but not writable. '0660' is equivalent to 'group'.
+ - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx'.
+   '0xxx' will override users' umask(2) value (and not only loosen permissions
+   as 'group' and 'all' does). '0640' will create a repository which is
+   group-readable, but not group-writable or accessible to others. '0660' will
+   create a repo that is readable and writable to the current user and group,
+   but inaccessible to others.
 
 By default, the configuration flag receive.denyNonFastForwards is enabled
 in shared repositories, so that you cannot force a non fast-forwarding push
index b762bba759c790eb27c5adc0f622a954c76ecb3a..aaa073efc80522a649f17d60127aae8cc85b0b3b 100644 (file)
@@ -297,7 +297,8 @@ for paths.
 
 Then, you would define a "diff.tex.xfuncname" configuration to
 specify a regular expression that matches a line that you would
-want to appear as the hunk header "TEXT", like this:
+want to appear as the hunk header "TEXT". Add a section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
 
 ------------------------
 [diff "tex"]
@@ -345,7 +346,8 @@ split words in a line, by specifying an appropriate regular expression
 in the "diff.*.wordRegex" configuration variable.  For example, in TeX
 a backslash followed by a sequence of letters forms a command, but
 several such commands can be run together without intervening
-whitespace.  To separate them, use a regular expression such as
+whitespace.  To separate them, use a regular expression in your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
 
 ------------------------
 [diff "tex"]
@@ -373,7 +375,8 @@ resulting text on stdout.
 
 For example, to show the diff of the exif information of a
 file instead of the binary information (assuming you have the
-exif tool installed):
+exif tool installed), add the following section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file):
 
 ------------------------
 [diff "jpg"]
index e90dc8ee42fe41d3c4eb09d324d8d2de6aff425b..22ea275eed8226b2dab6262df7de05afda28174b 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.2.3.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.2.4.txt
\ No newline at end of file
index f312798af38553e0badeda9732736a62460eae05..a6643386435d60743f70b5124caa2628a6b17d81 100644 (file)
@@ -3212,7 +3212,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 
        struct option builtin_apply_options[] = {
                { OPTION_CALLBACK, 0, "exclude", NULL, "path",
-                       "donยดt apply changes matching the given path",
+                       "don't apply changes matching the given path",
                        0, option_parse_exclude },
                { OPTION_CALLBACK, 0, "include", NULL, "path",
                        "apply changes matching the given path",
index ed0f318c0ce8a1c70ff3e45605c760c8bbb173f2..b121fe56de884867b31aeeb5bd704bdaf0310310 100644 (file)
@@ -53,9 +53,6 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
        int len;
        struct cache_entry *ce;
 
-       if (S_ISGITLINK(mode))
-               return 0;
-
        if (S_ISDIR(mode))
                return READ_TREE_RECURSIVE;
 
index 8199e5d4d5168859363b7a644535fd0cb01a1441..bfdfc7b411fb463f06ba98f58c07f5e707813310 100644 (file)
@@ -122,8 +122,10 @@ static void copy_templates(const char *template_dir)
                template_dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
        if (!template_dir[0])
                return;
+       template_len = strlen(template_dir);
+       if (PATH_MAX <= (template_len+strlen("/config")))
+               die("insanely long template path %s", template_dir);
        strcpy(template_path, template_dir);
-       template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
                template_path[template_len++] = '/';
                template_path[template_len] = 0;
index d95f515f2e32aa71c3a09fe5ed9486f352713360..249ad2a311f2e9680618870aed3ac053c9fcf7a1 100644 (file)
@@ -52,6 +52,7 @@ struct collect_reflog_cb {
 
 #define INCOMPLETE     (1u<<10)
 #define STUDYING       (1u<<11)
+#define REACHABLE      (1u<<12)
 
 static int tree_is_complete(const unsigned char *sha1)
 {
@@ -209,6 +210,70 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
        return 1;
 }
 
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+       /*
+        * We may or may not have the commit yet - if not, look it
+        * up using the supplied sha1.
+        */
+       if (!commit) {
+               if (is_null_sha1(sha1))
+                       return 0;
+
+               commit = lookup_commit_reference_gently(sha1, 1);
+
+               /* Not a commit -- keep it */
+               if (!commit)
+                       return 0;
+       }
+
+       /* Reachable from the current ref?  Don't prune. */
+       if (commit->object.flags & REACHABLE)
+               return 0;
+       if (in_merge_bases(commit, &cb->ref_commit, 1))
+               return 0;
+
+       /* We can't reach it - prune it. */
+       return 1;
+}
+
+static void mark_reachable(struct commit *commit, unsigned long expire_limit)
+{
+       /*
+        * We need to compute if commit on either side of an reflog
+        * entry is reachable from the tip of the ref for all entries.
+        * Mark commits that are reachable from the tip down to the
+        * time threashold first; we know a commit marked thusly is
+        * reachable from the tip without running in_merge_bases()
+        * at all.
+        */
+       struct commit_list *pending = NULL;
+
+       commit_list_insert(commit, &pending);
+       while (pending) {
+               struct commit_list *entry = pending;
+               struct commit_list *parent;
+               pending = entry->next;
+               commit = entry->item;
+               free(entry);
+               if (commit->object.flags & REACHABLE)
+                       continue;
+               if (parse_commit(commit))
+                       continue;
+               commit->object.flags |= REACHABLE;
+               if (commit->date < expire_limit)
+                       continue;
+               parent = commit->parents;
+               while (parent) {
+                       commit = parent->item;
+                       parent = parent->next;
+                       if (commit->object.flags & REACHABLE)
+                               continue;
+                       commit_list_insert(commit, &pending);
+               }
+       }
+}
+
 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
@@ -230,12 +295,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
        if (timestamp < cb->cmd->expire_unreachable) {
                if (!cb->ref_commit)
                        goto prune;
-               if (!old && !is_null_sha1(osha1))
-                       old = lookup_commit_reference_gently(osha1, 1);
-               if (!new && !is_null_sha1(nsha1))
-                       new = lookup_commit_reference_gently(nsha1, 1);
-               if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
-                   (new && !in_merge_bases(new, &cb->ref_commit, 1)))
+               if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
                        goto prune;
        }
 
@@ -288,7 +348,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
        cb.ref = ref;
        cb.cmd = cmd;
+       if (cb.ref_commit)
+               mark_reachable(cb.ref_commit, cmd->expire_total);
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+       if (cb.ref_commit)
+               clear_commit_marks(cb.ref_commit, REACHABLE);
  finish:
        if (cb.newlog) {
                if (fclose(cb.newlog)) {
index 0c8c76f13b03028ad400b1c5b72b3cf0a7ec0940..7a83c76f4b0480416b01213c0aca30b1e92556d2 100644 (file)
--- a/config.c
+++ b/config.c
@@ -51,7 +51,7 @@ static char *parse_value(void)
 
        for (;;) {
                int c = get_next_char();
-               if (len >= sizeof(value))
+               if (len >= sizeof(value) - 1)
                        return NULL;
                if (c == '\n') {
                        if (quote)
index ee853b990d8bfb15e0058fefbbd267bd58ed40fc..9bf5cc71754df1df29057822cf16bbfe51579d37 100644 (file)
@@ -1121,21 +1121,13 @@ static int process_entry(struct merge_options *o,
                                 o->branch1, o->branch2);
 
                clean_merge = mfi.clean;
-               if (mfi.clean)
-                       update_file(o, 1, mfi.sha, mfi.mode, path);
-               else if (S_ISGITLINK(mfi.mode))
-                       output(o, 1, "CONFLICT (submodule): Merge conflict in %s "
-                              "- needs %s", path, sha1_to_hex(b.sha1));
-               else {
+               if (!mfi.clean) {
+                       if (S_ISGITLINK(mfi.mode))
+                               reason = "submodule";
                        output(o, 1, "CONFLICT (%s): Merge conflict in %s",
                                        reason, path);
-
-                       if (o->call_depth)
-                               update_file(o, 0, mfi.sha, mfi.mode, path);
-                       else
-                               update_file_flags(o, mfi.sha, mfi.mode, path,
-                                             0 /* update_cache */, 1 /* update_working_directory */);
                }
+               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
        } else if (!o_sha && !a_sha && !b_sha) {
                /*
                 * this entry was deleted altogether. a_mode == 0 means
index 5ac0a273a94c033fbb7c48cb9a22e44c389e0f7d..e3d846420dc09e7b24876b291b9e546ac0628ed3 100755 (executable)
@@ -199,4 +199,13 @@ test_expect_success 'init honors global core.sharedRepository' '
        x`git config -f shared-honor-global/.git/config core.sharedRepository`
 '
 
+test_expect_success 'init rejects insanely long --template' '
+       (
+               insane=$(printf "x%09999dx" 1) &&
+               mkdir test &&
+               cd test &&
+               test_must_fail git init --template=$insane
+       )
+'
+
 test_done
index 3c8a2373ac0bf8e6e9737df3fec3b7d66d044a18..3fddc9ee781ba9fb5d189bbc5ec0b900b159723a 100755 (executable)
@@ -141,11 +141,14 @@ test_expect_success 'forced modes' '
                git commit -a -m initial &&
                git repack
        ) &&
-       find new/.git -print |
+       # List repository files meant to be protected; note that
+       # COMMIT_EDITMSG does not matter---0mode is not about a
+       # repository with a work tree.
+       find new/.git -type f -name COMMIT_EDITMSG -prune -o -print |
        xargs ls -ld >actual &&
 
        # Everything must be unaccessible to others
-       test -z "$(sed -n -e "/^.......---/d" actual)" &&
+       test -z "$(sed -e "/^.......---/d" actual)" &&
 
        # All directories must have either 2770 or 770
        test -z "$(sed -n -e "/^drwxrw[sx]---/d" -e "/^d/p" actual)" &&
@@ -156,10 +159,11 @@ test_expect_success 'forced modes' '
                p
        }" actual)" &&
 
-       # All files inside objects must be 0440
+       # All files inside objects must be accessible by us
        test -z "$(sed -n -e "/objects\//{
                /^d/d
-               /^-r--r-----/d
+               /^-r.-r.----/d
+               p
        }" actual)"
 '
 
index 1983076c753ea12a4f69d2a98eda3c1621daed59..080117c6bcbb61078539f36011ecd62780bae305 100755 (executable)
@@ -10,7 +10,7 @@ setup() {
 
 check() {
        echo "$2" >expected
-       git config --get "$1" >actual
+       git config --get "$1" >actual 2>&1
        test_cmp actual expected
 }
 
@@ -40,4 +40,11 @@ test_expect_success 'make sure git config escapes section names properly' '
        check "$SECTION" bar
 '
 
+LONG_VALUE=$(printf "x%01021dx a" 7)
+test_expect_success 'do not crash on special long config line' '
+       setup &&
+       git config section.key "$LONG_VALUE" &&
+       check section.key "fatal: bad config file line 2 in .git/config"
+'
+
 test_done
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
new file mode 100755 (executable)
index 0000000..fda3f0a
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout can handle submodules'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       mkdir submodule &&
+       (cd submodule &&
+        git init &&
+        test_commit first) &&
+       git add submodule &&
+       test_tick &&
+       git commit -m superproject &&
+       (cd submodule &&
+        test_commit second) &&
+       git add submodule &&
+       test_tick &&
+       git commit -m updated.superproject
+'
+
+test_expect_success '"reset <submodule>" updates the index' '
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --quiet --cached HEAD &&
+       test_must_fail git reset HEAD^ submodule &&
+       test_must_fail git diff-files --quiet &&
+       git reset submodule &&
+       git diff-files --quiet
+'
+
+test_expect_success '"checkout <submodule>" updates the index only' '
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --quiet --cached HEAD &&
+       git checkout HEAD^ submodule &&
+       test_must_fail git diff-files --quiet &&
+       git checkout HEAD submodule &&
+       git diff-files --quiet
+'
+
+test_done
index 4dd7d12bac62eae23516686c0ece0edf037f0317..51cb4a30f571772399697bc073153b7e956853bf 100755 (executable)
@@ -135,4 +135,10 @@ test_expect_success \
 EOF
      test_output'
 
+test_expect_success 'ls-tree filter is leading path match' '
+       git ls-tree $tree pa path3/a >current &&
+       >expected &&
+       test_output
+'
+
 test_done
index 9322298ecc6cb79f0123dea4e329317c5bfbfd11..94df7ae53a0ef47c0ef10ca6b3215ffdf38fa399 100755 (executable)
@@ -62,4 +62,12 @@ test_expect_success \
     'git diff-index --cached $tree -- file0/ >current &&
      compare_diff_raw current expected'
 
+test_expect_success 'diff-tree pathspec' '
+       tree2=$(git write-tree) &&
+       echo "$tree2" &&
+       git diff-tree -r --name-only $tree $tree2 -- pa path1/a >current &&
+       >expected &&
+       test_cmp expected current
+'
+
 test_done
diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
new file mode 100755 (executable)
index 0000000..aa6c44c
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+test_description='merging with submodules'
+
+. ./test-lib.sh
+
+#
+# history
+#
+#        a --- c
+#      /   \ /
+# root      X
+#      \   / \
+#        b --- d
+#
+
+test_expect_success setup '
+
+       mkdir sub &&
+       (cd sub &&
+        git init &&
+        echo original > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-root) &&
+       git add sub &&
+       test_tick &&
+       git commit -m root &&
+
+       git checkout -b a master &&
+       (cd sub &&
+        echo A > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-a) &&
+       git add sub &&
+       test_tick &&
+       git commit -m a &&
+
+       git checkout -b b master &&
+       (cd sub &&
+        echo B > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-b) &&
+       git add sub &&
+       test_tick &&
+       git commit -m b
+
+       git checkout -b c a &&
+       git merge -s ours b &&
+
+       git checkout -b d b &&
+       git merge -s ours a
+'
+
+test_expect_failure 'merging with modify/modify conflict' '
+
+       git checkout -b test1 a &&
+       test_must_fail git merge b &&
+       test -f .git/MERGE_MSG &&
+       git diff
+
+'
+
+test_expect_success 'merging with a modify/modify conflict between merge bases' '
+
+       git reset --hard HEAD &&
+       git checkout -b test2 c &&
+       git merge d
+
+'
+
+test_done
index 9f67af6c1fbb9130962cd373d8e2ebecf543c640..b05d0f43555d28f872fd5225e6773eeb636ef302 100644 (file)
@@ -118,10 +118,16 @@ static int tree_entry_interesting(struct tree_desc *desc, const char *base, int
                                continue;
 
                        /*
-                        * The base is a subdirectory of a path which
-                        * was specified, so all of them are interesting.
+                        * If the base is a subdirectory of a path which
+                        * was specified, all of them are interesting.
                         */
-                       return 2;
+                       if (!matchlen ||
+                           base[matchlen] == '/' ||
+                           match[matchlen - 1] == '/')
+                               return 2;
+
+                       /* Just a random prefix match */
+                       continue;
                }
 
                /* Does the base match? */
diff --git a/tree.c b/tree.c
index 25d2e29fa8b1dfb964b97a10898c77d8fe86ef78..0d703a0c473e65c60d9e130b4e1fd50b79e9462b 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -60,8 +60,12 @@ static int match_tree_entry(const char *base, int baselen, const char *path, uns
                        /* If it doesn't match, move along... */
                        if (strncmp(base, match, matchlen))
                                continue;
-                       /* The base is a subdirectory of a path which was specified. */
-                       return 1;
+                       /* pathspecs match only at the directory boundaries */
+                       if (!matchlen ||
+                           base[matchlen] == '/' ||
+                           match[matchlen - 1] == '/')
+                               return 1;
+                       continue;
                }
 
                /* Does the base match? */