{"thread":{"id":"3533","subject":"[PATCH] Add --temp and --stage=all options to checkout-index.","startedAt":"2006-03-03T01:20:32Z","lastAt":"2006-03-05T08:55:13Z","messageCount":6,"participants":["Shawn Pearce","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"17095","messageId":"20060303012032.GC6321@spearce.org","threadId":"3533","inReplyTo":null,"subject":"[PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-03-03T01:20:32Z","receivedAt":"2006-03-03T01:20:32Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Sometimes it is convient for a Porcelain to be able to checkout all\nunmerged files in all stages so that an external merge tool can be\nexecuted by the Porcelain or the end-user.  Using git-unpack-file\non each stage individually incurs a rather high penalty due to the\nneed to fork for each file version obtained.  git-checkout-index -a\n--stage=all will now do the same thing, but faster.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n\n---\n This spawned out of the discussion of my prior --suffix patch\n and replaces it entirely.\n\n The output format is Junio's idea, which I happened to like a lot.\n I think I have faithfully implemented it here, but added a case\n where you can obtain temporary files for only a specific stage\n without needing to read the 'wider' 3-stage format.\n\n Unfortunately this change lead me down a path which changed the core\n checkout code also used by apply and read-tree.  So those commands\n could now checkout to temporary files if they wanted to.  :-)\n\n It currently passes all test cases, including the new one provided\n by this patch.\n\n Documentation/git-checkout-index.txt |   51 ++++++++\n apply.c                              |    3 \n cache.h                              |    5 -\n checkout-index.c                     |   84 ++++++++++++-\n entry.c                              |   92 +++++++++------\n read-tree.c                          |    3 \n t/t2004-checkout-cache-temp.sh       |  212 ++++++++++++++++++++++++++++++++++\n 7 files changed, 396 insertions(+), 54 deletions(-)\n create mode 100755 t/t2004-checkout-cache-temp.sh\n\nbase c6b84bc40310f8e0cf2fe290b5f291e31cf688ef\nlast 022659f9b239fd5513a883e09ac0e0688b69dbe1\ndiff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt\nindex b0b6588..09bd6a5 100644\n--- a/Documentation/git-checkout-index.txt\n+++ b/Documentation/git-checkout-index.txt\n@@ -10,7 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]\n-\t\t   [--stage=<number>]\n+\t\t   [--stage=<number>|all]\n+\t\t   [--temp]\n \t\t   [-z] [--stdin]\n \t\t   [--] [<file>]\\*\n \n@@ -43,9 +44,15 @@ OPTIONS\n \tWhen creating files, prepend <string> (usually a directory\n \tincluding a trailing /)\n \n---stage=<number>::\n+--stage=<number>|all::\n \tInstead of checking out unmerged entries, copy out the\n \tfiles from named stage.  <number> must be between 1 and 3.\n+\tNote: --stage=all automatically implies --temp.\n+\n+--temp::\n+\tInstead of copying the files to the working directory\n+\twrite the content to temporary files.  The temporary name\n+\tassociations will be written to stdout.\n \n --stdin::\n \tInstead of taking list of paths from the command line,\n@@ -87,6 +94,46 @@ it will prevent problems with a filename\n Using `--` is probably a good policy in scripts.\n \n \n+Using --temp or --stage=all\n+---------------------------\n+When `--temp` is used (or implied by `--stage=all`)\n+`git-checkout-index` will create a temporary file for each index\n+entry being checked out.  The index will not be updated with stat\n+information.  These options can be useful if the caller needs all\n+stages of all unmerged entries so that the unmerged files can be\n+processed by an external merge tool.\n+\n+A listing will be written to stdout providing the association of\n+temporary file names to tracked path names.  The listing format\n+has two variations:\n+\n+    . tempname TAB path RS\n++\n+The first format is what gets used when `--stage` is omitted or\n+is not `--stage=all`. The field tempname is the temporary file\n+name holding the file content and path is the tracked path name in\n+the index.  Only the requested entries are output.\n+\n+    . stage1temp SP stage2temp SP stage3tmp TAB path RS\n++\n+The second format is what gets used when `--stage=all`.  The three\n+stage temporary fields (stage1temp, stage2temp, stage3temp) list the\n+name of the temporary file if there is a stage entry in the index\n+or `.` if there is no stage entry.  Paths which only have a stage 0\n+entry will always be omitted from the output.\n+\n+In both formats RS (the record separator) is newline by default\n+but will be the null byte if -z was passed on the command line.\n+The temporary file names are always safe strings; they will never\n+contain directory separators or whitespace characters.  The path\n+field is always relative to the current directory and the temporary\n+file names are always relative to the top level directory.\n+\n+If the object being copied out to a temporary file is a symbolic\n+link the content of the link will be written to a normal file.  It is\n+up to the end-user or the Porcelain to make use of this information.\n+\n+\n EXAMPLES\n --------\n To update and refresh only the files already checked out::\ndiff --git a/apply.c b/apply.c\nindex c369966..5583a80 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1376,6 +1376,7 @@ static int apply_data(struct patch *patc\n static int check_patch(struct patch *patch)\n {\n \tstruct stat st;\n+\tstatic char topath[MAXPATHLEN+1];\n \tconst char *old_name = patch->old_name;\n \tconst char *new_name = patch->new_name;\n \tconst char *name = old_name ? old_name : new_name;\n@@ -1402,7 +1403,7 @@ static int check_patch(struct patch *pat\n \t\t\t\tcostate.not_new = 0;\n \t\t\t\tcostate.refresh_cache = 1;\n \t\t\t\tif (checkout_entry(active_cache[pos],\n-\t\t\t\t\t\t   &costate) ||\n+\t\t\t\t\t\t   &costate, topath) ||\n \t\t\t\t    lstat(old_name, &st))\n \t\t\t\t\treturn -1;\n \t\t\t}\ndiff --git a/cache.h b/cache.h\nindex 0d3b244..f87744c 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -259,10 +259,11 @@ struct checkout {\n \tunsigned force:1,\n \t\t quiet:1,\n \t\t not_new:1,\n-\t\t refresh_cache:1;\n+\t\t refresh_cache:1,\n+\t\t to_tempfile:1;\n };\n \n-extern int checkout_entry(struct cache_entry *ce, struct checkout *state);\n+extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);\n \n extern struct alternate_object_database {\n \tstruct alternate_object_database *next;\ndiff --git a/checkout-index.c b/checkout-index.c\nindex f54c606..24845c3 100644\n--- a/checkout-index.c\n+++ b/checkout-index.c\n@@ -40,9 +40,12 @@\n #include \"strbuf.h\"\n #include \"quote.h\"\n \n+#define CHECKOUT_ALL 4\n static const char *prefix;\n static int prefix_length;\n+static int line_termination = '\\n';\n static int checkout_stage; /* default to checkout stage0 */\n+static char topath[4][MAXPATHLEN+1];\n \n static struct checkout state = {\n \t.base_dir = \"\",\n@@ -51,13 +54,42 @@ static struct checkout state = {\n \t.quiet = 0,\n \t.not_new = 0,\n \t.refresh_cache = 0,\n+\t.to_tempfile = 0,\n };\n \n+static void write_tempfile_record (const char *name)\n+{\n+\tint i;\n+\n+\tif (CHECKOUT_ALL == checkout_stage) {\n+\t\tfor (i = 1; i < 4; i++) {\n+\t\t\tif (i > 1)\n+\t\t\t\tputchar(' ');\n+\t\t\tif (topath[i][0])\n+\t\t\t\tfputs(topath[i], stdout);\n+\t\t\telse\n+\t\t\t\tputchar('.');\n+\t\t}\n+\t} else\n+\t\tfputs(topath[checkout_stage], stdout);\n+\n+\tputchar('\\t');\n+\twrite_name_quoted(\"\", 0, name + prefix_length,\n+\t\tline_termination, stdout);\n+\tputchar(line_termination);\n+\n+\tfor (i = 0; i < 4; i++) {\n+\t\ttopath[i][0] = 0;\n+\t}\n+}\n+\n static int checkout_file(const char *name)\n {\n \tint namelen = strlen(name);\n \tint pos = cache_name_pos(name, namelen);\n \tint has_same_name = 0;\n+\tint did_checkout = 0;\n+\tint errs = 0;\n \n \tif (pos < 0)\n \t\tpos = -pos - 1;\n@@ -68,9 +100,19 @@ static int checkout_file(const char *nam\n \t\t    memcmp(ce->name, name, namelen))\n \t\t\tbreak;\n \t\thas_same_name = 1;\n-\t\tif (checkout_stage == ce_stage(ce))\n-\t\t\treturn checkout_entry(ce, &state);\n \t\tpos++;\n+\t\tif (ce_stage(ce) != checkout_stage\n+\t\t    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))\n+\t\t\tcontinue;\n+\t\tdid_checkout = 1;\n+\t\tif (checkout_entry(ce, &state, topath[ce_stage(ce)]) < 0)\n+\t\t\terrs++;\n+\t}\n+\n+\tif (did_checkout) {\n+\t\tif (state.to_tempfile)\n+\t\t\twrite_tempfile_record(name);\n+\t\treturn errs > 0 ? -1 : 0;\n \t}\n \n \tif (!state.quiet) {\n@@ -90,18 +132,28 @@ static int checkout_file(const char *nam\n static int checkout_all(void)\n {\n \tint i, errs = 0;\n+\tstruct cache_entry* last_ce = 0;\n \n \tfor (i = 0; i < active_nr ; i++) {\n \t\tstruct cache_entry *ce = active_cache[i];\n-\t\tif (ce_stage(ce) != checkout_stage)\n+\t\tif (ce_stage(ce) != checkout_stage\n+\t\t    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))\n \t\t\tcontinue;\n \t\tif (prefix && *prefix &&\n \t\t    (ce_namelen(ce) <= prefix_length ||\n \t\t     memcmp(prefix, ce->name, prefix_length)))\n \t\t\tcontinue;\n-\t\tif (checkout_entry(ce, &state) < 0)\n+\t\tif (last_ce && state.to_tempfile) {\n+\t\t\tif (ce_namelen(last_ce) != ce_namelen(ce)\n+\t\t\t    || memcmp(last_ce->name, ce->name, ce_namelen(ce)))\n+\t\t\t\twrite_tempfile_record(last_ce->name);\n+\t\t}\n+\t\tif (checkout_entry(ce, &state, topath[ce_stage(ce)]) < 0)\n \t\t\terrs++;\n+\t\tlast_ce = ce;\n \t}\n+\tif (last_ce && state.to_tempfile)\n+\t\twrite_tempfile_record(last_ce->name);\n \tif (errs)\n \t\t/* we have already done our error reporting.\n \t\t * exit with the same code as die().\n@@ -111,7 +163,7 @@ static int checkout_all(void)\n }\n \n static const char checkout_cache_usage[] =\n-\"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]] [--prefix=<string>] [--] <file>...\";\n+\"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...\";\n \n static struct cache_file cache_file;\n \n@@ -121,7 +173,6 @@ int main(int argc, char **argv)\n \tint newfd = -1;\n \tint all = 0;\n \tint read_from_stdin = 0;\n-\tint line_termination = '\\n';\n \n \tprefix = setup_git_directory();\n \tgit_config(git_default_config);\n@@ -175,17 +226,26 @@ int main(int argc, char **argv)\n \t\t\ti++; /* do not consider arg as a file name */\n \t\t\tbreak;\n \t\t}\n+\t\tif (!strcmp(arg, \"--temp\")) {\n+\t\t\tstate.to_tempfile = 1;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strncmp(arg, \"--prefix=\", 9)) {\n \t\t\tstate.base_dir = arg+9;\n \t\t\tstate.base_dir_len = strlen(state.base_dir);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strncmp(arg, \"--stage=\", 8)) {\n-\t\t\tint ch = arg[8];\n-\t\t\tif ('1' <= ch && ch <= '3')\n-\t\t\t\tcheckout_stage = arg[8] - '0';\n-\t\t\telse\n-\t\t\t\tdie(\"stage should be between 1 and 3\");\n+\t\t\tif (!strcmp(arg + 8, \"all\")) {\n+\t\t\t\tstate.to_tempfile = 1;\n+\t\t\t\tcheckout_stage = CHECKOUT_ALL;\n+\t\t\t} else {\n+\t\t\t\tint ch = arg[8];\n+\t\t\t\tif ('1' <= ch && ch <= '3')\n+\t\t\t\t\tcheckout_stage = arg[8] - '0';\n+\t\t\t\telse\n+\t\t\t\t\tdie(\"stage should be between 1 and 3 or all\");\n+\t\t\t}\n \t\t\tcontinue;\n \t\t}\n \t\tif (arg[0] == '-')\n@@ -193,7 +253,7 @@ int main(int argc, char **argv)\n \t\tbreak;\n \t}\n \n-\tif (state.base_dir_len) {\n+\tif (state.base_dir_len || state.to_tempfile) {\n \t\t/* when --prefix is specified we do not\n \t\t * want to update cache.\n \t\t */\ndiff --git a/entry.c b/entry.c\nindex 8fb99bc..c33a35d 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -63,7 +63,7 @@ static int create_file(const char *path,\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)\n+static int write_entry(struct cache_entry *ce, char *path, struct checkout *state)\n {\n \tint fd;\n \tvoid *new;\n@@ -80,7 +80,11 @@ static int write_entry(struct cache_entr\n \t}\n \tswitch (ntohl(ce->ce_mode) & S_IFMT) {\n \tcase S_IFREG:\n-\t\tfd = create_file(path, ntohl(ce->ce_mode));\n+\t\tif (state->to_tempfile) {\n+\t\t\tstrcpy(path, \".merge_file_XXXXXX\");\n+\t\t\tfd = mkstemp(path);\n+\t\t} else \n+\t\t\tfd = create_file(path, ntohl(ce->ce_mode));\n \t\tif (fd < 0) {\n \t\t\tfree(new);\n \t\t\treturn error(\"git-checkout-index: unable to create file %s (%s)\",\n@@ -93,12 +97,27 @@ static int write_entry(struct cache_entr\n \t\t\treturn error(\"git-checkout-index: unable to write file %s\", path);\n \t\tbreak;\n \tcase S_IFLNK:\n-\t\tif (symlink(new, path)) {\n+\t\tif (state->to_tempfile) {\n+\t\t\tstrcpy(path, \".merge_link_XXXXXX\");\n+\t\t\tfd = mkstemp(path);\n+\t\t\tif (fd < 0) {\n+\t\t\t\tfree(new);\n+\t\t\t\treturn error(\"git-checkout-index: unable to create \"\n+\t\t\t\t\t\t \"file %s (%s)\", path, strerror(errno));\n+\t\t\t}\n+\t\t\twrote = write(fd, new, size);\n+\t\t\tclose(fd);\n \t\t\tfree(new);\n-\t\t\treturn error(\"git-checkout-index: unable to create \"\n-\t\t\t\t     \"symlink %s (%s)\", path, strerror(errno));\n+\t\t\tif (wrote != size)\n+\t\t\t\treturn error(\"git-checkout-index: unable to write file %s\",\n+\t\t\t\t\tpath);\n+\t\t} else {\n+\t\t\twrote = symlink(new, path);\n+\t\t\tfree(new);\n+\t\t\tif (wrote)\n+\t\t\t\treturn error(\"git-checkout-index: unable to create \"\n+\t\t\t\t\t\t \"symlink %s (%s)\", path, strerror(errno));\n \t\t}\n-\t\tfree(new);\n \t\tbreak;\n \tdefault:\n \t\tfree(new);\n@@ -113,41 +132,42 @@ static int write_entry(struct cache_entr\n \treturn 0;\n }\n \n-int checkout_entry(struct cache_entry *ce, struct checkout *state)\n+int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)\n {\n \tstruct stat st;\n-\tstatic char path[MAXPATHLEN+1];\n \tint len = state->base_dir_len;\n \n-\tmemcpy(path, state->base_dir, len);\n-\tstrcpy(path + len, ce->name);\n-\n-\tif (!lstat(path, &st)) {\n-\t\tunsigned changed = ce_match_stat(ce, &st, 1);\n-\t\tif (!changed)\n+\tif (!state->to_tempfile) {\n+\t\tmemcpy(topath, state->base_dir, len);\n+\t\tstrcpy(topath + len, ce->name);\n+\n+\t\tif (!lstat(topath, &st)) {\n+\t\t\tunsigned changed = ce_match_stat(ce, &st, 1);\n+\t\t\tif (!changed)\n+\t\t\t\treturn 0;\n+\t\t\tif (!state->force) {\n+\t\t\t\tif (!state->quiet)\n+\t\t\t\t\tfprintf(stderr, \"git-checkout-index: %s already exists\\n\", topath);\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * We unlink the old file, to get the new one with the\n+\t\t\t * right permissions (including umask, which is nasty\n+\t\t\t * to emulate by hand - much easier to let the system\n+\t\t\t * just do the right thing)\n+\t\t\t */\n+\t\t\tunlink(topath);\n+\t\t\tif (S_ISDIR(st.st_mode)) {\n+\t\t\t\tif (!state->force)\n+\t\t\t\t\treturn error(\"%s is a directory\", topath);\n+\t\t\t\tremove_subtree(topath);\n+\t\t\t}\n+\t\t} else if (state->not_new) \n \t\t\treturn 0;\n-\t\tif (!state->force) {\n-\t\t\tif (!state->quiet)\n-\t\t\t\tfprintf(stderr, \"git-checkout-index: %s already exists\\n\", path);\n-\t\t\treturn -1;\n-\t\t}\n-\n-\t\t/*\n-\t\t * We unlink the old file, to get the new one with the\n-\t\t * right permissions (including umask, which is nasty\n-\t\t * to emulate by hand - much easier to let the system\n-\t\t * just do the right thing)\n-\t\t */\n-\t\tunlink(path);\n-\t\tif (S_ISDIR(st.st_mode)) {\n-\t\t\tif (!state->force)\n-\t\t\t\treturn error(\"%s is a directory\", path);\n-\t\t\tremove_subtree(path);\n-\t\t}\n-\t} else if (state->not_new) \n-\t\treturn 0;\n-\tcreate_directories(path, state);\n-\treturn write_entry(ce, path, state);\n+\t\tcreate_directories(topath, state);\n+\t}\n+\treturn write_entry(ce, topath, state);\n }\n \n \ndiff --git a/read-tree.c b/read-tree.c\nindex be29b3f..e88d69e 100644\n--- a/read-tree.c\n+++ b/read-tree.c\n@@ -279,6 +279,7 @@ static void progress_interval(int signum\n \n static void check_updates(struct cache_entry **src, int nr)\n {\n+\tstatic char topath[MAXPATHLEN+1];\n \tstatic struct checkout state = {\n \t\t.base_dir = \"\",\n \t\t.force = 1,\n@@ -337,7 +338,7 @@ static void check_updates(struct cache_e\n \t\tif (ce->ce_flags & mask) {\n \t\t\tce->ce_flags &= ~mask;\n \t\t\tif (update)\n-\t\t\t\tcheckout_entry(ce, &state);\n+\t\t\t\tcheckout_entry(ce, &state, topath);\n \t\t}\n \t}\n \tif (total) {\ndiff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh\nnew file mode 100755\nindex 0000000..c100959\n--- /dev/null\n+++ b/t/t2004-checkout-cache-temp.sh\n@@ -0,0 +1,212 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2006 Shawn Pearce\n+#\n+\n+test_description='git-checkout-index --temp test.\n+\n+With --temp flag, git-checkout-index writes to temporary merge files\n+rather than the tracked path.'\n+\n+. ./test-lib.sh\n+\n+test_expect_success \\\n+'preparation' '\n+mkdir asubdir &&\n+echo tree1path0 >path0 &&\n+echo tree1path1 >path1 &&\n+echo tree1path3 >path3 &&\n+echo tree1path4 >path4 &&\n+echo tree1asubdir/path5 >asubdir/path5 &&\n+git-update-index --add path0 path1 path3 path4 asubdir/path5 &&\n+t1=$(git-write-tree) &&\n+rm -f path* .merge_* out .git/index &&\n+echo tree2path0 >path0 &&\n+echo tree2path1 >path1 &&\n+echo tree2path2 >path2 &&\n+echo tree2path4 >path4 &&\n+git-update-index --add path0 path1 path2 path4 &&\n+t2=$(git-write-tree) &&\n+rm -f path* .merge_* out .git/index &&\n+echo tree2path0 >path0 &&\n+echo tree3path1 >path1 &&\n+echo tree3path2 >path2 &&\n+echo tree3path3 >path3 &&\n+git-update-index --add path0 path1 path2 path3 &&\n+t3=$(git-write-tree)'\n+\n+test_expect_success \\\n+'checkout one stage 0 to temporary file' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree $t1 &&\n+git-checkout-index --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = tree1path1'\n+\n+test_expect_success \\\n+'checkout all stage 0 to temporary files' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree $t1 &&\n+git-checkout-index -a --temp >out &&\n+test $(wc -l <out) = 5 &&\n+for f in path0 path1 path3 path4 asubdir/path5\n+do\n+\ttest $(grep $f out | cut \"-d\t\" -f2) = $f &&\n+\tp=$(grep $f out | cut \"-d\t\" -f1) &&\n+\ttest -f $p &&\n+\ttest $(cat $p) = tree1$f\n+done'\n+\n+test_expect_success \\\n+'prepare 3-way merge' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree -m $t1 $t2 $t3'\n+\n+test_expect_success \\\n+'checkout one stage 2 to temporary file' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=2 --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = tree2path1'\n+\n+test_expect_success \\\n+'checkout all stage 2 to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --all --stage=2 --temp >out &&\n+test $(wc -l <out) = 3 &&\n+for f in path1 path2 path4\n+do\n+\ttest $(grep $f out | cut \"-d\t\" -f2) = $f &&\n+\tp=$(grep $f out | cut \"-d\t\" -f1) &&\n+\ttest -f $p &&\n+\ttest $(cat $p) = tree2$f\n+done'\n+\n+test_expect_success \\\n+'checkout all stages/one file to nothing' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path0 >out &&\n+test $(wc -l <out) = 0'\n+\n+test_expect_success \\\n+'checkout all stages/one file to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+cut \"-d\t\" -f1 out | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path1 &&\n+test $(cat $s2) = tree2path1 &&\n+test $(cat $s3) = tree3path1)'\n+\n+test_expect_success \\\n+'checkout some stages/one file to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path2 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path2 &&\n+cut \"-d\t\" -f1 out | (read s1 s2 s3 &&\n+test $s1 = . &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s2) = tree2path2 &&\n+test $(cat $s3) = tree3path2)'\n+\n+test_expect_success \\\n+'checkout all stages/all files to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index -a --stage=all --temp >out &&\n+test $(wc -l <out) = 5'\n+\n+test_expect_success \\\n+'-- path0: no entry' '\n+test x$(grep path0 out | cut \"-d\t\" -f2) = x'\n+\n+test_expect_success \\\n+'-- path1: all 3 stages' '\n+test $(grep path1 out | cut \"-d\t\" -f2) = path1 &&\n+grep path1 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path1 &&\n+test $(cat $s2) = tree2path1 &&\n+test $(cat $s3) = tree3path1)'\n+\n+test_expect_success \\\n+'-- path2: no stage 1, have stage 2 and 3' '\n+test $(grep path2 out | cut \"-d\t\" -f2) = path2 &&\n+grep path2 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test $s1 = . &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s2) = tree2path2 &&\n+test $(cat $s3) = tree3path2)'\n+\n+test_expect_success \\\n+'-- path3: no stage 2, have stage 1 and 3' '\n+test $(grep path3 out | cut \"-d\t\" -f2) = path3 &&\n+grep path3 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test $s2 = . &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path3 &&\n+test $(cat $s3) = tree3path3)'\n+\n+test_expect_success \\\n+'-- path4: no stage 3, have stage 1 and 3' '\n+test $(grep path4 out | cut \"-d\t\" -f2) = path4 &&\n+grep path4 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test $s3 = . &&\n+test $(cat $s1) = tree1path4 &&\n+test $(cat $s2) = tree2path4)'\n+\n+test_expect_success \\\n+'-- asubdir/path5: no stage 2 and 3 have stage 1' '\n+test $(grep asubdir/path5 out | cut \"-d\t\" -f2) = asubdir/path5 &&\n+grep asubdir/path5 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test $s2 = . &&\n+test $s3 = . &&\n+test $(cat $s1) = tree1asubdir/path5)'\n+\n+test_expect_success \\\n+'checkout --temp within subdir' '\n+(cd asubdir &&\n+ git-checkout-index -a --stage=all >out &&\n+ test $(wc -l <out) = 1 &&\n+ test $(grep path5 out | cut \"-d\t\" -f2) = path5 &&\n+ grep path5 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+ test -f ../$s1 &&\n+ test $s2 = . &&\n+ test $s3 = . &&\n+ test $(cat ../$s1) = tree1asubdir/path5)\n+)'\n+\n+test_expect_success \\\n+'checkout --temp symlink' '\n+rm -f path* .merge_* out .git/index &&\n+ln -s b a &&\n+git-update-index --add a &&\n+t4=$(git-write-tree) &&\n+rm -f .git/index &&\n+git-read-tree $t4 &&\n+git-checkout-index --temp -a >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = a &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = b'\n+\n+test_done\n-- \n1.2.3.g5f0e\n"},{"id":"17098","messageId":"7vmzg83xro.fsf@assigned-by-dhcp.cox.net","threadId":"3533","inReplyTo":"20060303012032.GC6321@spearce.org","subject":"Re: [PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-03-03T05:25:31Z","receivedAt":"2006-03-03T05:25:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Pearce <spearce@spearce.org> writes:\n\n>  Unfortunately this change lead me down a path which changed the core\n>  checkout code also used by apply and read-tree.\n\n... which makes it much harder to swallow without careful\ninspection X-<.\n\nI think the patch only appears much bigger than it actually is,\nbecause of reindentation effect coming from \"if (to-temp-file)\".\nHowever, I am too tired to carefully examine them tonight, so I\nhope you do not mind my postponing this for now.\n"},{"id":"17118","messageId":"20060303151331.GA16369@spearce.org","threadId":"3533","inReplyTo":"7vmzg83xro.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-03-03T15:13:31Z","receivedAt":"2006-03-03T15:13:31Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Junio C Hamano <junkio@cox.net> wrote:\n> Shawn Pearce <spearce@spearce.org> writes:\n> \n> >  Unfortunately this change lead me down a path which changed the core\n> >  checkout code also used by apply and read-tree.\n> \n> ... which makes it much harder to swallow without careful\n> inspection X-<.\n> \n> I think the patch only appears much bigger than it actually is,\n> because of reindentation effect coming from \"if (to-temp-file)\".\n> However, I am too tired to carefully examine them tonight, so I\n> hope you do not mind my postponing this for now.\n\nNot at all.  I read over the diff before sending it and thought it\nmade the patch look much worse than it really is, simply because\nof the reindentation.  :-|\n\nSometimes good 'ole diff ain't that great for conveying a change.\n\n-- \nShawn.\n"},{"id":"17166","messageId":"7vlkvpihm4.fsf@assigned-by-dhcp.cox.net","threadId":"3533","inReplyTo":"20060303151331.GA16369@spearce.org","subject":"Re: [PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-03-05T05:29:55Z","receivedAt":"2006-03-05T05:29:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Pearce <spearce@spearce.org> writes:\n\n> Junio C Hamano <junkio@cox.net> wrote:\n>> Shawn Pearce <spearce@spearce.org> writes:\n>> \n>> >  Unfortunately this change lead me down a path which changed the core\n>> >  checkout code also used by apply and read-tree.\n>> \n\nI would have preferred not to add to_tempfile member to the\ncheckout struct.  Instead, if checkout_entry has non NULL\ntopath, check out to a temporary location and return the path;\notherwise behave the way it did before.\n\nThat way everybody else would not need to declare useless\ntopath[] array on the stack and pass it to checkout_entry\n(instead they would just pass NULL because they are not\ninterested in checking out to temporary files).\n\nI am unsure about what the tempfile option should do when asked\nto checkout a symbolic link.  Creating a temporary regular file\nthat has the readlink result does not sound very useful to me.\n\nBTW, in any case, I think there is one breakage that needs to be\nfixed with something like this...\n\n---\ndiff --git a/apply.c b/apply.c\nindex 5a2cf54..727d63d 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1311,6 +1311,7 @@ static int check_patch(struct patch *pat\n \t\t\t\tcostate.force = 0;\n \t\t\t\tcostate.quiet = 0;\n \t\t\t\tcostate.not_new = 0;\n+\t\t\t\tcostate.to_tempfile = 0;\n \t\t\t\tcostate.refresh_cache = 1;\n \t\t\t\tif (checkout_entry(active_cache[pos],\n \t\t\t\t\t\t   &costate, topath) ||\n"},{"id":"17169","messageId":"20060305082415.GD24437@spearce.org","threadId":"3533","inReplyTo":"7vlkvpihm4.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2006-03-05T08:24:15Z","receivedAt":"2006-03-05T08:24:15Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Sometimes it is convient for a Porcelain to be able to checkout all\nunmerged files in all stages so that an external merge tool can be\nexecuted by the Porcelain or the end-user.  Using git-unpack-file\non each stage individually incurs a rather high penalty due to the\nneed to fork for each file version obtained.  git-checkout-index -a\n--stage=all will now do the same thing, but faster.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n\n---\nThis replaces my prior patch.  It looks a heck of a lot cleaner too\ndue to less indentation changes.  :-)\n\nJunio C Hamano <junkio@cox.net> wrote:\n> Shawn Pearce <spearce@spearce.org> writes:\n> \n> > Junio C Hamano <junkio@cox.net> wrote:\n> >> Shawn Pearce <spearce@spearce.org> writes:\n> >> \n> >> >  Unfortunately this change lead me down a path which changed the core\n> >> >  checkout code also used by apply and read-tree.\n> >> \n> \n> I would have preferred not to add to_tempfile member to the\n> checkout struct.  Instead, if checkout_entry has non NULL\n> topath, check out to a temporary location and return the path;\n> otherwise behave the way it did before.\n\nGood idea.\n\n> I am unsure about what the tempfile option should do when asked\n> to checkout a symbolic link.  Creating a temporary regular file\n> that has the readlink result does not sound very useful to me.\n\nI couldn't decide either here; but there's no `mkstemplink'\nor some-such function to generate a temporary symbolic link.\nWith the target in a temp file a Porcelain can at least generate\na true symbolic link with the correct target or present it the\ndifferent versions to the user for resolution.  Symlinks are\ndefinately difficult.\n\n> BTW, in any case, I think there is one breakage that needs to be\n> fixed with something like this...\n\nThat should be fixed with this version of the patch.  Good catch.\n\n Documentation/git-checkout-index.txt |   51 ++++++++\n apply.c                              |    3 \n cache.h                              |    2 \n checkout-index.c                     |   86 ++++++++++++--\n entry.c                              |   40 +++++-\n read-tree.c                          |    2 \n t/t2004-checkout-cache-temp.sh       |  212 ++++++++++++++++++++++++++++++++++\n 7 files changed, 370 insertions(+), 26 deletions(-)\n create mode 100755 t/t2004-checkout-cache-temp.sh\n\nbase 1c7fee5d0857d809fe5d93a1d56968e44296ce55\nlast aa1305c3181ac1a3a493765bf41c2d59f8b8ce3e\ndiff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt\nindex b0b6588..09bd6a5 100644\n--- a/Documentation/git-checkout-index.txt\n+++ b/Documentation/git-checkout-index.txt\n@@ -10,7 +10,8 @@ SYNOPSIS\n --------\n [verse]\n 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]\n-\t\t   [--stage=<number>]\n+\t\t   [--stage=<number>|all]\n+\t\t   [--temp]\n \t\t   [-z] [--stdin]\n \t\t   [--] [<file>]\\*\n \n@@ -43,9 +44,15 @@ OPTIONS\n \tWhen creating files, prepend <string> (usually a directory\n \tincluding a trailing /)\n \n---stage=<number>::\n+--stage=<number>|all::\n \tInstead of checking out unmerged entries, copy out the\n \tfiles from named stage.  <number> must be between 1 and 3.\n+\tNote: --stage=all automatically implies --temp.\n+\n+--temp::\n+\tInstead of copying the files to the working directory\n+\twrite the content to temporary files.  The temporary name\n+\tassociations will be written to stdout.\n \n --stdin::\n \tInstead of taking list of paths from the command line,\n@@ -87,6 +94,46 @@ it will prevent problems with a filename\n Using `--` is probably a good policy in scripts.\n \n \n+Using --temp or --stage=all\n+---------------------------\n+When `--temp` is used (or implied by `--stage=all`)\n+`git-checkout-index` will create a temporary file for each index\n+entry being checked out.  The index will not be updated with stat\n+information.  These options can be useful if the caller needs all\n+stages of all unmerged entries so that the unmerged files can be\n+processed by an external merge tool.\n+\n+A listing will be written to stdout providing the association of\n+temporary file names to tracked path names.  The listing format\n+has two variations:\n+\n+    . tempname TAB path RS\n++\n+The first format is what gets used when `--stage` is omitted or\n+is not `--stage=all`. The field tempname is the temporary file\n+name holding the file content and path is the tracked path name in\n+the index.  Only the requested entries are output.\n+\n+    . stage1temp SP stage2temp SP stage3tmp TAB path RS\n++\n+The second format is what gets used when `--stage=all`.  The three\n+stage temporary fields (stage1temp, stage2temp, stage3temp) list the\n+name of the temporary file if there is a stage entry in the index\n+or `.` if there is no stage entry.  Paths which only have a stage 0\n+entry will always be omitted from the output.\n+\n+In both formats RS (the record separator) is newline by default\n+but will be the null byte if -z was passed on the command line.\n+The temporary file names are always safe strings; they will never\n+contain directory separators or whitespace characters.  The path\n+field is always relative to the current directory and the temporary\n+file names are always relative to the top level directory.\n+\n+If the object being copied out to a temporary file is a symbolic\n+link the content of the link will be written to a normal file.  It is\n+up to the end-user or the Porcelain to make use of this information.\n+\n+\n EXAMPLES\n --------\n To update and refresh only the files already checked out::\ndiff --git a/apply.c b/apply.c\nindex c369966..849a8b4 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -1402,7 +1402,8 @@ static int check_patch(struct patch *pat\n \t\t\t\tcostate.not_new = 0;\n \t\t\t\tcostate.refresh_cache = 1;\n \t\t\t\tif (checkout_entry(active_cache[pos],\n-\t\t\t\t\t\t   &costate) ||\n+\t\t\t\t\t\t   &costate,\n+\t\t\t\t\t\t   NULL) ||\n \t\t\t\t    lstat(old_name, &st))\n \t\t\t\t\treturn -1;\n \t\t\t}\ndiff --git a/cache.h b/cache.h\nindex 8dc1de1..1f96280 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -262,7 +262,7 @@ struct checkout {\n \t\t refresh_cache:1;\n };\n \n-extern int checkout_entry(struct cache_entry *ce, struct checkout *state);\n+extern int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath);\n \n extern struct alternate_object_database {\n \tstruct alternate_object_database *next;\ndiff --git a/checkout-index.c b/checkout-index.c\nindex f54c606..7b78715 100644\n--- a/checkout-index.c\n+++ b/checkout-index.c\n@@ -40,9 +40,13 @@\n #include \"strbuf.h\"\n #include \"quote.h\"\n \n+#define CHECKOUT_ALL 4\n static const char *prefix;\n static int prefix_length;\n+static int line_termination = '\\n';\n static int checkout_stage; /* default to checkout stage0 */\n+static int to_tempfile;\n+static char topath[4][MAXPATHLEN+1];\n \n static struct checkout state = {\n \t.base_dir = \"\",\n@@ -53,11 +57,39 @@ static struct checkout state = {\n \t.refresh_cache = 0,\n };\n \n+static void write_tempfile_record (const char *name)\n+{\n+\tint i;\n+\n+\tif (CHECKOUT_ALL == checkout_stage) {\n+\t\tfor (i = 1; i < 4; i++) {\n+\t\t\tif (i > 1)\n+\t\t\t\tputchar(' ');\n+\t\t\tif (topath[i][0])\n+\t\t\t\tfputs(topath[i], stdout);\n+\t\t\telse\n+\t\t\t\tputchar('.');\n+\t\t}\n+\t} else\n+\t\tfputs(topath[checkout_stage], stdout);\n+\n+\tputchar('\\t');\n+\twrite_name_quoted(\"\", 0, name + prefix_length,\n+\t\tline_termination, stdout);\n+\tputchar(line_termination);\n+\n+\tfor (i = 0; i < 4; i++) {\n+\t\ttopath[i][0] = 0;\n+\t}\n+}\n+\n static int checkout_file(const char *name)\n {\n \tint namelen = strlen(name);\n \tint pos = cache_name_pos(name, namelen);\n \tint has_same_name = 0;\n+\tint did_checkout = 0;\n+\tint errs = 0;\n \n \tif (pos < 0)\n \t\tpos = -pos - 1;\n@@ -68,9 +100,20 @@ static int checkout_file(const char *nam\n \t\t    memcmp(ce->name, name, namelen))\n \t\t\tbreak;\n \t\thas_same_name = 1;\n-\t\tif (checkout_stage == ce_stage(ce))\n-\t\t\treturn checkout_entry(ce, &state);\n \t\tpos++;\n+\t\tif (ce_stage(ce) != checkout_stage\n+\t\t    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))\n+\t\t\tcontinue;\n+\t\tdid_checkout = 1;\n+\t\tif (checkout_entry(ce, &state,\n+\t\t    to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)\n+\t\t\terrs++;\n+\t}\n+\n+\tif (did_checkout) {\n+\t\tif (to_tempfile)\n+\t\t\twrite_tempfile_record(name);\n+\t\treturn errs > 0 ? -1 : 0;\n \t}\n \n \tif (!state.quiet) {\n@@ -90,18 +133,29 @@ static int checkout_file(const char *nam\n static int checkout_all(void)\n {\n \tint i, errs = 0;\n+\tstruct cache_entry* last_ce = 0;\n \n \tfor (i = 0; i < active_nr ; i++) {\n \t\tstruct cache_entry *ce = active_cache[i];\n-\t\tif (ce_stage(ce) != checkout_stage)\n+\t\tif (ce_stage(ce) != checkout_stage\n+\t\t    && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))\n \t\t\tcontinue;\n \t\tif (prefix && *prefix &&\n \t\t    (ce_namelen(ce) <= prefix_length ||\n \t\t     memcmp(prefix, ce->name, prefix_length)))\n \t\t\tcontinue;\n-\t\tif (checkout_entry(ce, &state) < 0)\n+\t\tif (last_ce && to_tempfile) {\n+\t\t\tif (ce_namelen(last_ce) != ce_namelen(ce)\n+\t\t\t    || memcmp(last_ce->name, ce->name, ce_namelen(ce)))\n+\t\t\t\twrite_tempfile_record(last_ce->name);\n+\t\t}\n+\t\tif (checkout_entry(ce, &state,\n+\t\t    to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)\n \t\t\terrs++;\n+\t\tlast_ce = ce;\n \t}\n+\tif (last_ce && to_tempfile)\n+\t\twrite_tempfile_record(last_ce->name);\n \tif (errs)\n \t\t/* we have already done our error reporting.\n \t\t * exit with the same code as die().\n@@ -111,7 +165,7 @@ static int checkout_all(void)\n }\n \n static const char checkout_cache_usage[] =\n-\"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]] [--prefix=<string>] [--] <file>...\";\n+\"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...\";\n \n static struct cache_file cache_file;\n \n@@ -121,7 +175,6 @@ int main(int argc, char **argv)\n \tint newfd = -1;\n \tint all = 0;\n \tint read_from_stdin = 0;\n-\tint line_termination = '\\n';\n \n \tprefix = setup_git_directory();\n \tgit_config(git_default_config);\n@@ -175,17 +228,26 @@ int main(int argc, char **argv)\n \t\t\ti++; /* do not consider arg as a file name */\n \t\t\tbreak;\n \t\t}\n+\t\tif (!strcmp(arg, \"--temp\")) {\n+\t\t\tto_tempfile = 1;\n+\t\t\tcontinue;\n+\t\t}\n \t\tif (!strncmp(arg, \"--prefix=\", 9)) {\n \t\t\tstate.base_dir = arg+9;\n \t\t\tstate.base_dir_len = strlen(state.base_dir);\n \t\t\tcontinue;\n \t\t}\n \t\tif (!strncmp(arg, \"--stage=\", 8)) {\n-\t\t\tint ch = arg[8];\n-\t\t\tif ('1' <= ch && ch <= '3')\n-\t\t\t\tcheckout_stage = arg[8] - '0';\n-\t\t\telse\n-\t\t\t\tdie(\"stage should be between 1 and 3\");\n+\t\t\tif (!strcmp(arg + 8, \"all\")) {\n+\t\t\t\tto_tempfile = 1;\n+\t\t\t\tcheckout_stage = CHECKOUT_ALL;\n+\t\t\t} else {\n+\t\t\t\tint ch = arg[8];\n+\t\t\t\tif ('1' <= ch && ch <= '3')\n+\t\t\t\t\tcheckout_stage = arg[8] - '0';\n+\t\t\t\telse\n+\t\t\t\t\tdie(\"stage should be between 1 and 3 or all\");\n+\t\t\t}\n \t\t\tcontinue;\n \t\t}\n \t\tif (arg[0] == '-')\n@@ -193,7 +255,7 @@ int main(int argc, char **argv)\n \t\tbreak;\n \t}\n \n-\tif (state.base_dir_len) {\n+\tif (state.base_dir_len || to_tempfile) {\n \t\t/* when --prefix is specified we do not\n \t\t * want to update cache.\n \t\t */\ndiff --git a/entry.c b/entry.c\nindex 8fb99bc..38b5241 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -63,7 +63,7 @@ static int create_file(const char *path,\n \treturn open(path, O_WRONLY | O_CREAT | O_EXCL, mode);\n }\n \n-static int write_entry(struct cache_entry *ce, const char *path, struct checkout *state)\n+static int write_entry(struct cache_entry *ce, char *path, struct checkout *state, int to_tempfile)\n {\n \tint fd;\n \tvoid *new;\n@@ -80,7 +80,11 @@ static int write_entry(struct cache_entr\n \t}\n \tswitch (ntohl(ce->ce_mode) & S_IFMT) {\n \tcase S_IFREG:\n-\t\tfd = create_file(path, ntohl(ce->ce_mode));\n+\t\tif (to_tempfile) {\n+\t\t\tstrcpy(path, \".merge_file_XXXXXX\");\n+\t\t\tfd = mkstemp(path);\n+\t\t} else \n+\t\t\tfd = create_file(path, ntohl(ce->ce_mode));\n \t\tif (fd < 0) {\n \t\t\tfree(new);\n \t\t\treturn error(\"git-checkout-index: unable to create file %s (%s)\",\n@@ -93,12 +97,27 @@ static int write_entry(struct cache_entr\n \t\t\treturn error(\"git-checkout-index: unable to write file %s\", path);\n \t\tbreak;\n \tcase S_IFLNK:\n-\t\tif (symlink(new, path)) {\n+\t\tif (to_tempfile) {\n+\t\t\tstrcpy(path, \".merge_link_XXXXXX\");\n+\t\t\tfd = mkstemp(path);\n+\t\t\tif (fd < 0) {\n+\t\t\t\tfree(new);\n+\t\t\t\treturn error(\"git-checkout-index: unable to create \"\n+\t\t\t\t\t\t \"file %s (%s)\", path, strerror(errno));\n+\t\t\t}\n+\t\t\twrote = write(fd, new, size);\n+\t\t\tclose(fd);\n \t\t\tfree(new);\n-\t\t\treturn error(\"git-checkout-index: unable to create \"\n-\t\t\t\t     \"symlink %s (%s)\", path, strerror(errno));\n+\t\t\tif (wrote != size)\n+\t\t\t\treturn error(\"git-checkout-index: unable to write file %s\",\n+\t\t\t\t\tpath);\n+\t\t} else {\n+\t\t\twrote = symlink(new, path);\n+\t\t\tfree(new);\n+\t\t\tif (wrote)\n+\t\t\t\treturn error(\"git-checkout-index: unable to create \"\n+\t\t\t\t\t\t \"symlink %s (%s)\", path, strerror(errno));\n \t\t}\n-\t\tfree(new);\n \t\tbreak;\n \tdefault:\n \t\tfree(new);\n@@ -113,12 +132,15 @@ static int write_entry(struct cache_entr\n \treturn 0;\n }\n \n-int checkout_entry(struct cache_entry *ce, struct checkout *state)\n+int checkout_entry(struct cache_entry *ce, struct checkout *state, char *topath)\n {\n-\tstruct stat st;\n \tstatic char path[MAXPATHLEN+1];\n+\tstruct stat st;\n \tint len = state->base_dir_len;\n \n+\tif (topath)\n+\t\treturn write_entry(ce, topath, state, 1);\n+\n \tmemcpy(path, state->base_dir, len);\n \tstrcpy(path + len, ce->name);\n \n@@ -147,7 +169,7 @@ int checkout_entry(struct cache_entry *c\n \t} else if (state->not_new) \n \t\treturn 0;\n \tcreate_directories(path, state);\n-\treturn write_entry(ce, path, state);\n+\treturn write_entry(ce, path, state, 0);\n }\n \n \ndiff --git a/read-tree.c b/read-tree.c\nindex be29b3f..1c3b09b 100644\n--- a/read-tree.c\n+++ b/read-tree.c\n@@ -337,7 +337,7 @@ static void check_updates(struct cache_e\n \t\tif (ce->ce_flags & mask) {\n \t\t\tce->ce_flags &= ~mask;\n \t\t\tif (update)\n-\t\t\t\tcheckout_entry(ce, &state);\n+\t\t\t\tcheckout_entry(ce, &state, NULL);\n \t\t}\n \t}\n \tif (total) {\ndiff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh\nnew file mode 100755\nindex 0000000..c100959\n--- /dev/null\n+++ b/t/t2004-checkout-cache-temp.sh\n@@ -0,0 +1,212 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2006 Shawn Pearce\n+#\n+\n+test_description='git-checkout-index --temp test.\n+\n+With --temp flag, git-checkout-index writes to temporary merge files\n+rather than the tracked path.'\n+\n+. ./test-lib.sh\n+\n+test_expect_success \\\n+'preparation' '\n+mkdir asubdir &&\n+echo tree1path0 >path0 &&\n+echo tree1path1 >path1 &&\n+echo tree1path3 >path3 &&\n+echo tree1path4 >path4 &&\n+echo tree1asubdir/path5 >asubdir/path5 &&\n+git-update-index --add path0 path1 path3 path4 asubdir/path5 &&\n+t1=$(git-write-tree) &&\n+rm -f path* .merge_* out .git/index &&\n+echo tree2path0 >path0 &&\n+echo tree2path1 >path1 &&\n+echo tree2path2 >path2 &&\n+echo tree2path4 >path4 &&\n+git-update-index --add path0 path1 path2 path4 &&\n+t2=$(git-write-tree) &&\n+rm -f path* .merge_* out .git/index &&\n+echo tree2path0 >path0 &&\n+echo tree3path1 >path1 &&\n+echo tree3path2 >path2 &&\n+echo tree3path3 >path3 &&\n+git-update-index --add path0 path1 path2 path3 &&\n+t3=$(git-write-tree)'\n+\n+test_expect_success \\\n+'checkout one stage 0 to temporary file' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree $t1 &&\n+git-checkout-index --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = tree1path1'\n+\n+test_expect_success \\\n+'checkout all stage 0 to temporary files' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree $t1 &&\n+git-checkout-index -a --temp >out &&\n+test $(wc -l <out) = 5 &&\n+for f in path0 path1 path3 path4 asubdir/path5\n+do\n+\ttest $(grep $f out | cut \"-d\t\" -f2) = $f &&\n+\tp=$(grep $f out | cut \"-d\t\" -f1) &&\n+\ttest -f $p &&\n+\ttest $(cat $p) = tree1$f\n+done'\n+\n+test_expect_success \\\n+'prepare 3-way merge' '\n+rm -f path* .merge_* out .git/index &&\n+git-read-tree -m $t1 $t2 $t3'\n+\n+test_expect_success \\\n+'checkout one stage 2 to temporary file' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=2 --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = tree2path1'\n+\n+test_expect_success \\\n+'checkout all stage 2 to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --all --stage=2 --temp >out &&\n+test $(wc -l <out) = 3 &&\n+for f in path1 path2 path4\n+do\n+\ttest $(grep $f out | cut \"-d\t\" -f2) = $f &&\n+\tp=$(grep $f out | cut \"-d\t\" -f1) &&\n+\ttest -f $p &&\n+\ttest $(cat $p) = tree2$f\n+done'\n+\n+test_expect_success \\\n+'checkout all stages/one file to nothing' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path0 >out &&\n+test $(wc -l <out) = 0'\n+\n+test_expect_success \\\n+'checkout all stages/one file to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path1 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path1 &&\n+cut \"-d\t\" -f1 out | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path1 &&\n+test $(cat $s2) = tree2path1 &&\n+test $(cat $s3) = tree3path1)'\n+\n+test_expect_success \\\n+'checkout some stages/one file to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index --stage=all --temp -- path2 >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = path2 &&\n+cut \"-d\t\" -f1 out | (read s1 s2 s3 &&\n+test $s1 = . &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s2) = tree2path2 &&\n+test $(cat $s3) = tree3path2)'\n+\n+test_expect_success \\\n+'checkout all stages/all files to temporary files' '\n+rm -f path* .merge_* out &&\n+git-checkout-index -a --stage=all --temp >out &&\n+test $(wc -l <out) = 5'\n+\n+test_expect_success \\\n+'-- path0: no entry' '\n+test x$(grep path0 out | cut \"-d\t\" -f2) = x'\n+\n+test_expect_success \\\n+'-- path1: all 3 stages' '\n+test $(grep path1 out | cut \"-d\t\" -f2) = path1 &&\n+grep path1 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path1 &&\n+test $(cat $s2) = tree2path1 &&\n+test $(cat $s3) = tree3path1)'\n+\n+test_expect_success \\\n+'-- path2: no stage 1, have stage 2 and 3' '\n+test $(grep path2 out | cut \"-d\t\" -f2) = path2 &&\n+grep path2 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test $s1 = . &&\n+test -f $s2 &&\n+test -f $s3 &&\n+test $(cat $s2) = tree2path2 &&\n+test $(cat $s3) = tree3path2)'\n+\n+test_expect_success \\\n+'-- path3: no stage 2, have stage 1 and 3' '\n+test $(grep path3 out | cut \"-d\t\" -f2) = path3 &&\n+grep path3 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test $s2 = . &&\n+test -f $s3 &&\n+test $(cat $s1) = tree1path3 &&\n+test $(cat $s3) = tree3path3)'\n+\n+test_expect_success \\\n+'-- path4: no stage 3, have stage 1 and 3' '\n+test $(grep path4 out | cut \"-d\t\" -f2) = path4 &&\n+grep path4 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test -f $s2 &&\n+test $s3 = . &&\n+test $(cat $s1) = tree1path4 &&\n+test $(cat $s2) = tree2path4)'\n+\n+test_expect_success \\\n+'-- asubdir/path5: no stage 2 and 3 have stage 1' '\n+test $(grep asubdir/path5 out | cut \"-d\t\" -f2) = asubdir/path5 &&\n+grep asubdir/path5 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+test -f $s1 &&\n+test $s2 = . &&\n+test $s3 = . &&\n+test $(cat $s1) = tree1asubdir/path5)'\n+\n+test_expect_success \\\n+'checkout --temp within subdir' '\n+(cd asubdir &&\n+ git-checkout-index -a --stage=all >out &&\n+ test $(wc -l <out) = 1 &&\n+ test $(grep path5 out | cut \"-d\t\" -f2) = path5 &&\n+ grep path5 out | cut \"-d\t\" -f1 | (read s1 s2 s3 &&\n+ test -f ../$s1 &&\n+ test $s2 = . &&\n+ test $s3 = . &&\n+ test $(cat ../$s1) = tree1asubdir/path5)\n+)'\n+\n+test_expect_success \\\n+'checkout --temp symlink' '\n+rm -f path* .merge_* out .git/index &&\n+ln -s b a &&\n+git-update-index --add a &&\n+t4=$(git-write-tree) &&\n+rm -f .git/index &&\n+git-read-tree $t4 &&\n+git-checkout-index --temp -a >out &&\n+test $(wc -l <out) = 1 &&\n+test $(cut \"-d\t\" -f2 out) = a &&\n+p=$(cut \"-d\t\" -f1 out) &&\n+test -f $p &&\n+test $(cat $p) = b'\n+\n+test_done\n-- \n1.2.4.g0226\n"},{"id":"17170","messageId":"7v8xrpi83y.fsf@assigned-by-dhcp.cox.net","threadId":"3533","inReplyTo":"20060305082415.GD24437@spearce.org","subject":"Re: [PATCH] Add --temp and --stage=all options to checkout-index.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-03-05T08:55:13Z","receivedAt":"2006-03-05T08:55:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Pearce <spearce@spearce.org> writes:\n\n> This replaces my prior patch.  It looks a heck of a lot cleaner too\n> due to less indentation changes.  :-)\n\nMuch nicer.  Will put in \"next\" for wider exposure.\n"}]}