builtin-add.con commit Do "git add" as a builtin (0d78153)
   1/*
   2 * "git add" builtin command
   3 *
   4 * Copyright (C) 2006 Linus Torvalds
   5 */
   6#include <fnmatch.h>
   7
   8#include "cache.h"
   9#include "builtin.h"
  10#include "dir.h"
  11
  12static const char builtin_add_usage[] =
  13"git-add [-n] [-v] <filepattern>...";
  14
  15static int common_prefix(const char **pathspec)
  16{
  17        const char *path, *slash, *next;
  18        int prefix;
  19
  20        if (!pathspec)
  21                return 0;
  22
  23        path = *pathspec;
  24        slash = strrchr(path, '/');
  25        if (!slash)
  26                return 0;
  27
  28        prefix = slash - path + 1;
  29        while ((next = *++pathspec) != NULL) {
  30                int len = strlen(next);
  31                if (len >= prefix && !memcmp(path, next, len))
  32                        continue;
  33                for (;;) {
  34                        if (!len)
  35                                return 0;
  36                        if (next[--len] != '/')
  37                                continue;
  38                        if (memcmp(path, next, len+1))
  39                                continue;
  40                        prefix = len + 1;
  41                        break;
  42                }
  43        }
  44        return prefix;
  45}
  46
  47static int match(const char **pathspec, const char *name, int namelen, int prefix)
  48{
  49        const char *match;
  50
  51        name += prefix;
  52        namelen -= prefix;
  53
  54        while ((match = *pathspec++) != NULL) {
  55                int matchlen;
  56
  57                match += prefix;
  58                matchlen = strlen(match);
  59                if (!matchlen)
  60                        return 1;
  61                if (!strncmp(match, name, matchlen)) {
  62                        if (match[matchlen-1] == '/')
  63                                return 1;
  64                        switch (name[matchlen]) {
  65                        case '/': case '\0':
  66                                return 1;
  67                        }
  68                }
  69                if (!fnmatch(match, name, 0))
  70                        return 1;
  71        }
  72        return 0;
  73}
  74
  75static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
  76{
  77        int i;
  78        struct dir_entry **src, **dst;
  79
  80        src = dst = dir->entries;
  81        i = dir->nr;
  82        while (--i >= 0) {
  83                struct dir_entry *entry = *src++;
  84                if (!match(pathspec, entry->name, entry->len, prefix)) {
  85                        free(entry);
  86                        continue;
  87                }
  88                *dst++ = entry;
  89        }
  90        dir->nr = dst - dir->entries;
  91}
  92
  93static void fill_directory(struct dir_struct *dir, const char **pathspec)
  94{
  95        const char *path, *base;
  96        int baselen;
  97
  98        /* Set up the default git porcelain excludes */
  99        memset(dir, 0, sizeof(*dir));
 100        dir->exclude_per_dir = ".gitignore";
 101        path = git_path("info/exclude");
 102        if (!access(path, R_OK))
 103                add_excludes_from_file(dir, path);
 104
 105        /*
 106         * Calculate common prefix for the pathspec, and
 107         * use that to optimize the directory walk
 108         */
 109        baselen = common_prefix(pathspec);
 110        path = ".";
 111        base = "";
 112        if (baselen) {
 113                char *common = xmalloc(baselen + 1);
 114                common = xmalloc(baselen + 1);
 115                memcpy(common, *pathspec, baselen);
 116                common[baselen] = 0;
 117                path = base = common;
 118        }
 119
 120        /* Read the directory and prune it */
 121        read_directory(dir, path, base, baselen);
 122        if (pathspec)
 123                prune_directory(dir, pathspec, baselen);
 124}
 125
 126static int add_file_to_index(const char *path, int verbose)
 127{
 128        int size, namelen;
 129        struct stat st;
 130        struct cache_entry *ce;
 131
 132        if (lstat(path, &st))
 133                die("%s: unable to stat (%s)", path, strerror(errno));
 134
 135        if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
 136                die("%s: can only add regular files or symbolic links", path);
 137
 138        namelen = strlen(path);
 139        size = cache_entry_size(namelen);
 140        ce = xcalloc(1, size);
 141        memcpy(ce->name, path, namelen);
 142        ce->ce_flags = htons(namelen);
 143        fill_stat_cache_info(ce, &st);
 144
 145        ce->ce_mode = create_ce_mode(st.st_mode);
 146        if (!trust_executable_bit) {
 147                /* If there is an existing entry, pick the mode bits
 148                 * from it.
 149                 */
 150                int pos = cache_name_pos(path, namelen);
 151                if (pos >= 0)
 152                        ce->ce_mode = active_cache[pos]->ce_mode;
 153        }
 154
 155        if (index_path(ce->sha1, path, &st, 1))
 156                die("unable to index file %s", path);
 157        if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
 158                die("unable to add %s to index",path);
 159        if (verbose)
 160                printf("add '%s'\n", path);
 161        return 0;
 162}
 163
 164static struct cache_file cache_file;
 165
 166int cmd_add(int argc, const char **argv, char **envp)
 167{
 168        int i, newfd;
 169        int verbose = 0, show_only = 0;
 170        const char *prefix = setup_git_directory();
 171        const char **pathspec;
 172        struct dir_struct dir;
 173
 174        git_config(git_default_config);
 175
 176        newfd = hold_index_file_for_update(&cache_file, get_index_file());
 177        if (newfd < 0)
 178                die("unable to create new cachefile");
 179
 180        if (read_cache() < 0)
 181                die("index file corrupt");
 182
 183        for (i = 1; i < argc; i++) {
 184                const char *arg = argv[i];
 185
 186                if (arg[0] != '-')
 187                        break;
 188                if (!strcmp(arg, "--")) {
 189                        i++;
 190                        break;
 191                }
 192                if (!strcmp(arg, "-n")) {
 193                        show_only = 1;
 194                        continue;
 195                }
 196                if (!strcmp(arg, "-v")) {
 197                        verbose = 1;
 198                        continue;
 199                }
 200                die(builtin_add_usage);
 201        }
 202        git_config(git_default_config);
 203        pathspec = get_pathspec(prefix, argv + i);
 204
 205        fill_directory(&dir, pathspec);
 206
 207        if (show_only) {
 208                const char *sep = "", *eof = "";
 209                for (i = 0; i < dir.nr; i++) {
 210                        printf("%s%s", sep, dir.entries[i]->name);
 211                        sep = " ";
 212                        eof = "\n";
 213                }
 214                fputs(eof, stdout);
 215                return 0;
 216        }
 217
 218        for (i = 0; i < dir.nr; i++)
 219                add_file_to_index(dir.entries[i]->name, verbose);
 220
 221        if (active_cache_changed) {
 222                if (write_cache(newfd, active_cache, active_nr) ||
 223                    commit_index_file(&cache_file))
 224                        die("Unable to write new index file");
 225        }
 226
 227        return 0;
 228}