Fix users of prefix_path() to free() only when necessary
[gitweb.git] / checkout-index.c
index 97845324beb9002a0fed0dd1184988716faa9ed2..cc3a745c149b38ae250ac5656bb4a4843a95f6a4 100644 (file)
  *
  *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
  *
+ * or:
+ *
+ *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
  * which will force all existing *.h files to be replaced with
  * their cached copies. If an empty command line implied "all",
  * then this would force-refresh everything in the cache, which
  * but get used to it in scripting!).
  */
 #include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
+
+#define CHECKOUT_ALL 4
+static const char *prefix;
+static int prefix_length;
+static int line_termination = '\n';
+static int checkout_stage; /* default to checkout stage0 */
+static int to_tempfile;
+static char topath[4][MAXPATHLEN+1];
 
 static struct checkout state = {
        .base_dir = "",
@@ -43,35 +57,105 @@ static struct checkout state = {
        .refresh_cache = 0,
 };
 
-static int checkout_file(const char *name)
+static void write_tempfile_record (const char *name)
 {
-       int pos = cache_name_pos(name, strlen(name));
-       if (pos < 0) {
-               if (!state.quiet) {
-                       pos = -pos - 1;
-                       fprintf(stderr,
-                               "git-checkout-index: %s is %s.\n",
-                               name,
-                               (pos < active_nr &&
-                                !strcmp(active_cache[pos]->name, name)) ?
-                               "unmerged" : "not in the cache");
+       int i;
+
+       if (CHECKOUT_ALL == checkout_stage) {
+               for (i = 1; i < 4; i++) {
+                       if (i > 1)
+                               putchar(' ');
+                       if (topath[i][0])
+                               fputs(topath[i], stdout);
+                       else
+                               putchar('.');
                }
-               return -1;
+       } else
+               fputs(topath[checkout_stage], stdout);
+
+       putchar('\t');
+       write_name_quoted("", 0, name + prefix_length,
+               line_termination, stdout);
+       putchar(line_termination);
+
+       for (i = 0; i < 4; i++) {
+               topath[i][0] = 0;
        }
-       return checkout_entry(active_cache[pos], &state);
+}
+
+static int checkout_file(const char *name)
+{
+       int namelen = strlen(name);
+       int pos = cache_name_pos(name, namelen);
+       int has_same_name = 0;
+       int did_checkout = 0;
+       int errs = 0;
+
+       if (pos < 0)
+               pos = -pos - 1;
+
+       while (pos < active_nr) {
+               struct cache_entry *ce = active_cache[pos];
+               if (ce_namelen(ce) != namelen ||
+                   memcmp(ce->name, name, namelen))
+                       break;
+               has_same_name = 1;
+               pos++;
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
+                       continue;
+               did_checkout = 1;
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
+                       errs++;
+       }
+
+       if (did_checkout) {
+               if (to_tempfile)
+                       write_tempfile_record(name);
+               return errs > 0 ? -1 : 0;
+       }
+
+       if (!state.quiet) {
+               fprintf(stderr, "git-checkout-index: %s ", name);
+               if (!has_same_name)
+                       fprintf(stderr, "is not in the cache");
+               else if (checkout_stage)
+                       fprintf(stderr, "does not exist at stage %d",
+                               checkout_stage);
+               else
+                       fprintf(stderr, "is unmerged");
+               fputc('\n', stderr);
+       }
+       return -1;
 }
 
 static int checkout_all(void)
 {
        int i, errs = 0;
+       struct cache_entry* last_ce = NULL;
 
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce))
+               if (ce_stage(ce) != checkout_stage
+                   && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
                        continue;
-               if (checkout_entry(ce, &state) < 0)
+               if (prefix && *prefix &&
+                   (ce_namelen(ce) <= prefix_length ||
+                    memcmp(prefix, ce->name, prefix_length)))
+                       continue;
+               if (last_ce && to_tempfile) {
+                       if (ce_namelen(last_ce) != ce_namelen(ce)
+                           || memcmp(last_ce->name, ce->name, ce_namelen(ce)))
+                               write_tempfile_record(last_ce->name);
+               }
+               if (checkout_entry(ce, &state,
+                   to_tempfile ? topath[ce_stage(ce)] : NULL) < 0)
                        errs++;
+               last_ce = ce;
        }
+       if (last_ce && to_tempfile)
+               write_tempfile_record(last_ce->name);
        if (errs)
                /* we have already done our error reporting.
                 * exit with the same code as die().
@@ -81,14 +165,20 @@ static int checkout_all(void)
 }
 
 static const char checkout_cache_usage[] =
-"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] [--] <file>...";
+"git-checkout-index [-u] [-q] [-a] [-f] [-n] [--stage=[123]|all] [--prefix=<string>] [--temp] [--] <file>...";
 
 static struct cache_file cache_file;
 
 int main(int argc, char **argv)
 {
-       int i, force_filename = 0;
+       int i;
        int newfd = -1;
+       int all = 0;
+       int read_from_stdin = 0;
+
+       prefix = setup_git_directory();
+       git_config(git_default_config);
+       prefix_length = prefix ? strlen(prefix) : 0;
 
        if (read_cache() < 0) {
                die("invalid cache");
@@ -96,58 +186,129 @@ int main(int argc, char **argv)
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if (!force_filename) {
-                       if (!strcmp(arg, "-a")) {
-                               checkout_all();
-                               continue;
-                       }
-                       if (!strcmp(arg, "--")) {
-                               force_filename = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-f")) {
-                               state.force = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-q")) {
-                               state.quiet = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-n")) {
-                               state.not_new = 1;
-                               continue;
-                       }
-                       if (!strcmp(arg, "-u")) {
-                               state.refresh_cache = 1;
-                               if (newfd < 0)
-                                       newfd = hold_index_file_for_update
-                                               (&cache_file,
-                                                get_index_file());
-                               if (newfd < 0)
-                                       die("cannot open index.lock file.");
-                               continue;
-                       }
-                       if (!memcmp(arg, "--prefix=", 9)) {
-                               state.base_dir = arg+9;
-                               state.base_dir_len = strlen(state.base_dir);
-                               continue;
-                       }
-                       if (arg[0] == '-')
-                               usage(checkout_cache_usage);
+
+               if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
                }
-               if (state.base_dir_len) {
-                       /* when --prefix is specified we do not
-                        * want to update cache.
-                        */
-                       if (state.refresh_cache) {
-                               close(newfd); newfd = -1;
-                               rollback_index_file(&cache_file);
+               if (!strcmp(arg, "-a") || !strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f") || !strcmp(arg, "--force")) {
+                       state.force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) {
+                       state.quiet = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-n") || !strcmp(arg, "--no-create")) {
+                       state.not_new = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-u") || !strcmp(arg, "--index")) {
+                       state.refresh_cache = 1;
+                       if (newfd < 0)
+                               newfd = hold_index_file_for_update
+                                       (&cache_file,
+                                        get_index_file());
+                       if (newfd < 0)
+                               die("cannot open index.lock file.");
+                       continue;
+               }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       if (i != argc - 1)
+                               die("--stdin must be at the end");
+                       read_from_stdin = 1;
+                       i++; /* do not consider arg as a file name */
+                       break;
+               }
+               if (!strcmp(arg, "--temp")) {
+                       to_tempfile = 1;
+                       continue;
+               }
+               if (!strncmp(arg, "--prefix=", 9)) {
+                       state.base_dir = arg+9;
+                       state.base_dir_len = strlen(state.base_dir);
+                       continue;
+               }
+               if (!strncmp(arg, "--stage=", 8)) {
+                       if (!strcmp(arg + 8, "all")) {
+                               to_tempfile = 1;
+                               checkout_stage = CHECKOUT_ALL;
+                       } else {
+                               int ch = arg[8];
+                               if ('1' <= ch && ch <= '3')
+                                       checkout_stage = arg[8] - '0';
+                               else
+                                       die("stage should be between 1 and 3 or all");
                        }
-                       state.refresh_cache = 0;
+                       continue;
+               }
+               if (arg[0] == '-')
+                       usage(checkout_cache_usage);
+               break;
+       }
+
+       if (state.base_dir_len || to_tempfile) {
+               /* when --prefix is specified we do not
+                * want to update cache.
+                */
+               if (state.refresh_cache) {
+                       close(newfd); newfd = -1;
+                       rollback_index_file(&cache_file);
                }
-               checkout_file(arg);
+               state.refresh_cache = 0;
        }
 
+       /* Check out named files first */
+       for ( ; i < argc; i++) {
+               const char *arg = argv[i];
+               const char *p;
+
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and explicit filenames");
+               if (read_from_stdin)
+                       die("git-checkout-index: don't mix '--stdin' and explicit filenames");
+               p = prefix_path(prefix, prefix_length, arg);
+               checkout_file(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
+       }
+
+       if (read_from_stdin) {
+               struct strbuf buf;
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and '--stdin'");
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       const char *p;
+
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       checkout_file(p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char *)p);
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
+       if (all)
+               checkout_all();
+
        if (0 <= newfd &&
            (write_cache(newfd, active_cache, active_nr) ||
             commit_index_file(&cache_file)))