diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4bae37e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,304 @@ +PROJECT( git C ) + +cmake_minimum_required(VERSION 2.6) + +# See configure.ac, config.mak.in and Makefile for more tests (this CMakeLists.txt is lacking A LOT of them!) + +FIND_PACKAGE( OpenSSL ) +FIND_PACKAGE( CURL ) +FIND_PACKAGE( EXPAT ) +FIND_PACKAGE( ZLIB ) + +INCLUDE_DIRECTORIES( ${PROJECT_BINARY_DIR} ${PROJECT_SOURCE_DIR} ) + +ADD_DEFINITIONS( -DNO_STRLCPY ) + +### Variables and directories + +# Git version +IF( EXISTS ${PROJECT_SOURCE_DIR}/version ) + FILE( READ ${PROJECT_SOURCE_DIR}/version GIT_VERSION ) +ELSEIF( EXISTS ${PROJECT_SOURCE_DIR}/GIT-VERSION-FILE ) + FILE( STRINGS ${PROJECT_SOURCE_DIR}/GIT-VERSION-FILE GIT_VERSION_STRING LIMIT_COUNT 1 ) + STRING( REGEX REPLACE "^GIT_VERSION = ([0-9]+(.[a-zA-Z0-9]+)+)$" "\\1" GIT_VERSION ${GIT_VERSION_STRING} ) +ELSE( EXISTS ${PROJECT_SOURCE_DIR}/version ) + EXECUTE_PROCESS( + COMMAND ${PROJECT_SOURCE_DIR}/GIT-VERSION-GEN + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE GIT_VERSION_RESULT + OUTPUT_VARIABLE GIT_VERSION_OUTPUT + ERROR_VARIABLE GIT_VERSION_ERROR + ERROR_STRIP_TRAILING_WHITESPACE + ) + # PCRE: [\d]+(\.[\d]+){2,}(\.[\d]+)*(\.[\d\w]*)* + STRING( REGEX REPLACE "^GIT_VERSION = ([0-9]+(.[a-zA-Z0-9]+)+)" "\\1" GIT_VERSION ${GIT_VERSION_ERROR} ) + +ENDIF( EXISTS ${PROJECT_SOURCE_DIR}/version ) + +FILE( REMOVE ${PROJECT_SOURCE_DIR}/GIT-VERSION-FILE ) + +# User agent +SET( GIT_USER_AGENT "git/${GIT_VERSION}" ) +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/http.c.cmake ${PROJECT_BINARY_DIR}/http.c ) + +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/git.c.cmake ${PROJECT_BINARY_DIR}/git.c ) + +# SHA header include - Very simplified! +IF( OPENSSL_FOUND ) + SET( SHA1_HEADER "" ) +ELSE ( OPENSSL_FOUND ) + SET( SHA1_HEADER "\"mozilla/sha1.h\"" ) +ENDIF ( OPENSSL_FOUND ) + +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/cache.h.cmake ${PROJECT_BINARY_DIR}/cache.h ) + + +# Git config file - I'm not checking for non-Unix platforms! +SET( GIT_PREFIX $ENV{HOME} ) +SET( GIT_BIN_PATH ${GIT_PREFIX}/bin ) +SET( GIT_MAN_PATH ${GIT_PREFIX}/share/man ) +SET( GIT_INFO_PATH ${GIT_PREFIX}/share/info ) +SET( GIT_EXEC_PATH ${GIT_PREFIX}/libexec/git-core ) +SET( GIT_SHARE_PATH ${GIT_PREFIX}/share ) +SET( GIT_TEMPLATE_PATH ${GIT_SHAREDIR}/git-core/templates ) +SET( GIT_HTML_PATH ${GIT_SHAREDIR}/doc/git-doc ) + +# Git exec path in case you want to use git without installing it +#SET( GIT_EXEC_PATH $ENV{GIT_EXEC_PATH} ) +#IF( NOT GIT_EXEC_PATH ) +# SET( GIT_EXEC_PATH \"\" ) +#ENDIF(NOT GIT_EXEC_PATH ) + +IF( ${GIT_PREFIX} STREQUAL "/usr" ) + SET( SYSCONFDIR "/etc" ) +ELSE( ${GIT_PREFIX} STREQUAL "/usr" ) + SET( SYSCONFDIR ${GIT_PREFIX}/etc ) +ENDIF( ${GIT_PREFIX} STREQUAL "/usr" ) + +SET( ETC_GITCONFIG "${SYSCONFDIR}/gitconfig" ) + + +### LIBGIT + +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/config.c.cmake ${PROJECT_BINARY_DIR}/config.c ) + +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/exec_cmd.c.cmake ${PROJECT_BINARY_DIR}/exec_cmd.c ) + +SET( libgit_SRCS + compat/strlcpy.c + abspath.c + alias.c + alloc.c + archive.c + archive-tar.c + archive-zip.c + attr.c + base85.c + blob.c + branch.c + bundle.c + cache-tree.c + color.c + combine-diff.c + commit.c + ${PROJECT_BINARY_DIR}/config.c + connect.c + convert.c + copy.c + csum-file.c + ctype.c + date.c + decorate.c + diffcore-break.c + diffcore-delta.c + diffcore-order.c + diffcore-pickaxe.c + diffcore-rename.c + diff-delta.c + diff-no-index.c + diff-lib.c + diff.c + dir.c + editor.c + entry.c + environment.c + ${PROJECT_BINARY_DIR}/exec_cmd.c + fsck.c + graph.c + grep.c + hash.c + help.c + ident.c + interpolate.c + list-objects.c + ll-merge.c + lockfile.c + log-tree.c + mailmap.c + match-trees.c + merge-file.c + name-hash.c + object.c + pack-check.c + pack-refs.c + pack-revindex.c + pack-write.c + pager.c + parse-options.c + patch-delta.c + patch-ids.c + string-list.c + path.c + pkt-line.c + pretty.c + progress.c + quote.c + reachable.c + read-cache.c + reflog-walk.c + refs.c + remote.c + rerere.c + revision.c + run-command.c + server-info.c + setup.c + sha1_file.c + sha1-lookup.c + sha1_name.c + shallow.c + sideband.c + strbuf.c + symlinks.c + tag.c + trace.c + transport.c + tree-diff.c + tree.c + tree-walk.c + unpack-trees.c + usage.c + utf8.c + walker.c + wrapper.c + write_or_die.c + ws.c + wt-status.c + xdiff-interface.c + builtin-add.c + builtin-annotate.c + builtin-apply.c + builtin-archive.c + builtin-blame.c + builtin-branch.c + builtin-bundle.c + builtin-cat-file.c + builtin-check-attr.c + builtin-check-ref-format.c + builtin-checkout-index.c + builtin-checkout.c + builtin-clean.c + builtin-clone.c + builtin-commit-tree.c + builtin-commit.c + builtin-config.c + builtin-count-objects.c + builtin-describe.c + builtin-diff-files.c + builtin-diff-index.c + builtin-diff-tree.c + builtin-diff.c + builtin-fast-export.c + builtin-fetch--tool.c + builtin-fetch-pack.c + builtin-fetch.c + builtin-fmt-merge-msg.c + builtin-for-each-ref.c + builtin-fsck.c + builtin-gc.c + builtin-grep.c + builtin-init-db.c + builtin-log.c + builtin-ls-files.c + builtin-ls-remote.c + builtin-ls-tree.c + builtin-mailinfo.c + builtin-mailsplit.c + builtin-merge.c + builtin-merge-base.c + builtin-merge-file.c + builtin-merge-ours.c + builtin-merge-recursive.c + builtin-mv.c + builtin-name-rev.c + builtin-pack-objects.c + builtin-pack-refs.c + builtin-prune-packed.c + builtin-prune.c + builtin-push.c + builtin-read-tree.c + builtin-reflog.c + builtin-remote.c + builtin-rerere.c + builtin-reset.c + builtin-rev-list.c + builtin-rev-parse.c + builtin-revert.c + builtin-rm.c + builtin-send-pack.c + builtin-shortlog.c + builtin-show-branch.c + builtin-show-ref.c + builtin-stripspace.c + builtin-symbolic-ref.c + builtin-tag.c + builtin-tar-tree.c + builtin-unpack-objects.c + builtin-update-index.c + builtin-update-ref.c + builtin-upload-archive.c + builtin-verify-pack.c + builtin-verify-tag.c + builtin-write-tree.c + levenshtein.c +) + +IF( CURL_FOUND) + SET( libgit_SRCS ${libgit_SRCS} ${PROJECT_BINARY_DIR}/http.c http-walker.c ) +ENDIF( CURL_FOUND ) +ADD_LIBRARY( libgit STATIC ${libgit_SRCS} ) + + + +### LIBXDIFF + +SET( libxdiff_SRCS + xdiff/xdiffi.c + xdiff/xprepare.c + xdiff/xutils.c + xdiff/xemit.c + xdiff/xmerge.c +) +ADD_LIBRARY( xdiff STATIC ${libxdiff_SRCS} ) + + + +### GIT EXECUTABLE + +CONFIGURE_FILE( ${PROJECT_SOURCE_DIR}/builtin-help.c.cmake ${PROJECT_BINARY_DIR}/builtin-help.c ) + +# Create common-cmds.h + +EXECUTE_PROCESS( COMMAND ${PROJECT_SOURCE_DIR}/generate-cmdlist.sh command-list.txt WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} OUTPUT_VARIABLE COMMON-CMDS_H ) + +FILE( WRITE ${PROJECT_BINARY_DIR}/common-cmds.h "${COMMON-CMDS_H}" ) + +# git executable +SET( git_SRCS + ${PROJECT_BINARY_DIR}/git.c + ${PROJECT_BINARY_DIR}/builtin-help.c + builtin-http-fetch.c + ${PROJECT_BINARY_DIR}/common-cmds.h +) +ADD_EXECUTABLE( git ${git_SRCS} ) +TARGET_LINK_LIBRARIES( git libgit xdiff ${CURL_LIBRARIES} ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ) diff --git a/README.cmake b/README.cmake new file mode 100644 index 0000000..8624111 --- /dev/null +++ b/README.cmake @@ -0,0 +1,3 @@ +CMake-ization by Pau Garcia i Quiles starting with version 1.6.0.2 + +FindICONV.cmake taken from the LyX project (http://www.lyx.org) diff --git a/builtin-help.c b/builtin-help.c deleted file mode 100644 index 64207cb..0000000 --- a/builtin-help.c +++ /dev/null @@ -1,465 +0,0 @@ -/* - * builtin-help.c - * - * Builtin help command - */ -#include "cache.h" -#include "builtin.h" -#include "exec_cmd.h" -#include "common-cmds.h" -#include "parse-options.h" -#include "run-command.h" -#include "help.h" - -static struct man_viewer_list { - struct man_viewer_list *next; - char name[FLEX_ARRAY]; -} *man_viewer_list; - -static struct man_viewer_info_list { - struct man_viewer_info_list *next; - const char *info; - char name[FLEX_ARRAY]; -} *man_viewer_info_list; - -enum help_format { - HELP_FORMAT_MAN, - HELP_FORMAT_INFO, - HELP_FORMAT_WEB, -}; - -static int show_all = 0; -static enum help_format help_format = HELP_FORMAT_MAN; -static struct option builtin_help_options[] = { - OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), - OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), - OPT_SET_INT('w', "web", &help_format, "show manual in web browser", - HELP_FORMAT_WEB), - OPT_SET_INT('i', "info", &help_format, "show info page", - HELP_FORMAT_INFO), - OPT_END(), -}; - -static const char * const builtin_help_usage[] = { - "git help [--all] [--man|--web|--info] [command]", - NULL -}; - -static enum help_format parse_help_format(const char *format) -{ - if (!strcmp(format, "man")) - return HELP_FORMAT_MAN; - if (!strcmp(format, "info")) - return HELP_FORMAT_INFO; - if (!strcmp(format, "web") || !strcmp(format, "html")) - return HELP_FORMAT_WEB; - die("unrecognized help format '%s'", format); -} - -static const char *get_man_viewer_info(const char *name) -{ - struct man_viewer_info_list *viewer; - - for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) - { - if (!strcasecmp(name, viewer->name)) - return viewer->info; - } - return NULL; -} - -static int check_emacsclient_version(void) -{ - struct strbuf buffer = STRBUF_INIT; - struct child_process ec_process; - const char *argv_ec[] = { "emacsclient", "--version", NULL }; - int version; - - /* emacsclient prints its version number on stderr */ - memset(&ec_process, 0, sizeof(ec_process)); - ec_process.argv = argv_ec; - ec_process.err = -1; - ec_process.stdout_to_stderr = 1; - if (start_command(&ec_process)) { - fprintf(stderr, "Failed to start emacsclient.\n"); - return -1; - } - strbuf_read(&buffer, ec_process.err, 20); - close(ec_process.err); - - /* - * Don't bother checking return value, because "emacsclient --version" - * seems to always exits with code 1. - */ - finish_command(&ec_process); - - if (prefixcmp(buffer.buf, "emacsclient")) { - fprintf(stderr, "Failed to parse emacsclient version.\n"); - strbuf_release(&buffer); - return -1; - } - - strbuf_remove(&buffer, 0, strlen("emacsclient")); - version = atoi(buffer.buf); - - if (version < 22) { - fprintf(stderr, - "emacsclient version '%d' too old (< 22).\n", - version); - strbuf_release(&buffer); - return -1; - } - - strbuf_release(&buffer); - return 0; -} - -static void exec_woman_emacs(const char* path, const char *page) -{ - if (!check_emacsclient_version()) { - /* This works only with emacsclient version >= 22. */ - struct strbuf man_page = STRBUF_INIT; - - if (!path) - path = "emacsclient"; - strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_konqueror(const char* path, const char *page) -{ - const char *display = getenv("DISPLAY"); - if (display && *display) { - struct strbuf man_page = STRBUF_INIT; - const char *filename = "kfmclient"; - - /* It's simpler to launch konqueror using kfmclient. */ - if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; - } else - path = "kfmclient"; - strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_man(const char* path, const char *page) -{ - if (!path) - path = "man"; - execlp(path, "man", page, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); -} - -static void exec_man_cmd(const char *cmd, const char *page) -{ - struct strbuf shell_cmd = STRBUF_INIT; - strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); -} - -static void add_man_viewer(const char *name) -{ - struct man_viewer_list **p = &man_viewer_list; - size_t len = strlen(name); - - while (*p) - p = &((*p)->next); - *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); -} - -static int supported_man_viewer(const char *name, size_t len) -{ - return (!strncasecmp("man", name, len) || - !strncasecmp("woman", name, len) || - !strncasecmp("konqueror", name, len)); -} - -static void do_add_man_viewer_info(const char *name, - size_t len, - const char *value) -{ - struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - - strncpy(new->name, name, len); - new->info = xstrdup(value); - new->next = man_viewer_info_list; - man_viewer_info_list = new; -} - -static int add_man_viewer_path(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - do_add_man_viewer_info(name, len, value); - else - warning("'%s': path for unsupported man viewer.\n" - "Please consider using 'man..cmd' instead.", - name); - - return 0; -} - -static int add_man_viewer_cmd(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - warning("'%s': cmd for supported man viewer.\n" - "Please consider using 'man..path' instead.", - name); - else - do_add_man_viewer_info(name, len, value); - - return 0; -} - -static int add_man_viewer_info(const char *var, const char *value) -{ - const char *name = var + 4; - const char *subkey = strrchr(name, '.'); - - if (!subkey) - return error("Config with no key for man viewer: %s", name); - - if (!strcmp(subkey, ".path")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_path(name, subkey - name, value); - } - if (!strcmp(subkey, ".cmd")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_cmd(name, subkey - name, value); - } - - warning("'%s': unsupported man viewer sub key.", subkey); - return 0; -} - -static int git_help_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "help.format")) { - if (!value) - return config_error_nonbool(var); - help_format = parse_help_format(value); - return 0; - } - if (!strcmp(var, "man.viewer")) { - if (!value) - return config_error_nonbool(var); - add_man_viewer(value); - return 0; - } - if (!prefixcmp(var, "man.")) - return add_man_viewer_info(var, value); - - return git_default_config(var, value, cb); -} - -static struct cmdnames main_cmds, other_cmds; - -void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s ", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name)); - puts(common_cmds[i].help); - } -} - -static int is_git_command(const char *s) -{ - return is_in_cmdlist(&main_cmds, s) || - is_in_cmdlist(&other_cmds, s); -} - -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - -static const char *cmd_to_page(const char *git_cmd) -{ - if (!git_cmd) - return "git"; - else if (!prefixcmp(git_cmd, "git")) - return git_cmd; - else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); - else - return prepend("git", git_cmd); -} - -static void setup_man_path(void) -{ - struct strbuf new_path; - const char *old_path = getenv("MANPATH"); - - strbuf_init(&new_path, 0); - - /* We should always put ':' after our path. If there is no - * old_path, the ':' at the end will let 'man' to try - * system-wide paths after ours to find the manual page. If - * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, GIT_MAN_PATH); - strbuf_addch(&new_path, ':'); - if (old_path) - strbuf_addstr(&new_path, old_path); - - setenv("MANPATH", new_path.buf, 1); - - strbuf_release(&new_path); -} - -static void exec_viewer(const char *name, const char *page) -{ - const char *info = get_man_viewer_info(name); - - if (!strcasecmp(name, "man")) - exec_man_man(info, page); - else if (!strcasecmp(name, "woman")) - exec_woman_emacs(info, page); - else if (!strcasecmp(name, "konqueror")) - exec_man_konqueror(info, page); - else if (info) - exec_man_cmd(info, page); - else - warning("'%s': unknown man viewer.", name); -} - -static void show_man_page(const char *git_cmd) -{ - struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); - const char *fallback = getenv("GIT_MAN_VIEWER"); - - setup_man_path(); - for (viewer = man_viewer_list; viewer; viewer = viewer->next) - { - exec_viewer(viewer->name, page); /* will return when unable */ - } - if (fallback) - exec_viewer(fallback, page); - exec_viewer("man", page); - die("no man viewer handled the request"); -} - -static void show_info_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - setenv("INFOPATH", GIT_INFO_PATH, 1); - execlp("info", "info", "gitman", page, NULL); -} - -static void get_html_page_path(struct strbuf *page_path, const char *page) -{ - struct stat st; - const char *html_path = system_path(GIT_HTML_PATH); - - /* Check that we have a git documentation directory. */ - if (stat(mkpath("%s/git.html", html_path), &st) - || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); - - strbuf_init(page_path, 0); - strbuf_addf(page_path, "%s/%s.html", html_path, page); -} - -/* - * If open_html is not defined in a platform-specific way (see for - * example compat/mingw.h), we use the script web--browse to display - * HTML. - */ -#ifndef open_html -void open_html(const char *path) -{ - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); -} -#endif - -static void show_html_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - struct strbuf page_path; /* it leaks but we exec bellow */ - - get_html_page_path(&page_path, page); - - open_html(page_path.buf); -} - -int cmd_help(int argc, const char **argv, const char *prefix) -{ - int nongit; - const char *alias; - load_command_list("git-", &main_cmds, &other_cmds); - - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - - argc = parse_options(argc, argv, builtin_help_options, - builtin_help_usage, 0); - - if (show_all) { - printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", &main_cmds, &other_cmds); - printf("%s\n", git_more_info_string); - return 0; - } - - if (!argv[0]) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); - return 0; - } - - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf("`git %s' is aliased to `%s'\n", argv[0], alias); - return 0; - } - - switch (help_format) { - case HELP_FORMAT_MAN: - show_man_page(argv[0]); - break; - case HELP_FORMAT_INFO: - show_info_page(argv[0]); - break; - case HELP_FORMAT_WEB: - show_html_page(argv[0]); - break; - } - - return 0; -} diff --git a/builtin-help.c.cmake b/builtin-help.c.cmake new file mode 100644 index 0000000..94bc97e --- /dev/null +++ b/builtin-help.c.cmake @@ -0,0 +1,465 @@ +/* + * builtin-help.c + * + * Builtin help command + */ +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" +#include "common-cmds.h" +#include "parse-options.h" +#include "run-command.h" +#include "help.h" + +static struct man_viewer_list { + struct man_viewer_list *next; + char name[FLEX_ARRAY]; +} *man_viewer_list; + +static struct man_viewer_info_list { + struct man_viewer_info_list *next; + const char *info; + char name[FLEX_ARRAY]; +} *man_viewer_info_list; + +enum help_format { + HELP_FORMAT_MAN, + HELP_FORMAT_INFO, + HELP_FORMAT_WEB, +}; + +static int show_all = 0; +static enum help_format help_format = HELP_FORMAT_MAN; +static struct option builtin_help_options[] = { + OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), + OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), + OPT_SET_INT('w', "web", &help_format, "show manual in web browser", + HELP_FORMAT_WEB), + OPT_SET_INT('i', "info", &help_format, "show info page", + HELP_FORMAT_INFO), + OPT_END(), +}; + +static const char * const builtin_help_usage[] = { + "git help [--all] [--man|--web|--info] [command]", + NULL +}; + +static enum help_format parse_help_format(const char *format) +{ + if (!strcmp(format, "man")) + return HELP_FORMAT_MAN; + if (!strcmp(format, "info")) + return HELP_FORMAT_INFO; + if (!strcmp(format, "web") || !strcmp(format, "html")) + return HELP_FORMAT_WEB; + die("unrecognized help format '%s'", format); +} + +static const char *get_man_viewer_info(const char *name) +{ + struct man_viewer_info_list *viewer; + + for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) + { + if (!strcasecmp(name, viewer->name)) + return viewer->info; + } + return NULL; +} + +static int check_emacsclient_version(void) +{ + struct strbuf buffer = STRBUF_INIT; + struct child_process ec_process; + const char *argv_ec[] = { "emacsclient", "--version", NULL }; + int version; + + /* emacsclient prints its version number on stderr */ + memset(&ec_process, 0, sizeof(ec_process)); + ec_process.argv = argv_ec; + ec_process.err = -1; + ec_process.stdout_to_stderr = 1; + if (start_command(&ec_process)) { + fprintf(stderr, "Failed to start emacsclient.\n"); + return -1; + } + strbuf_read(&buffer, ec_process.err, 20); + close(ec_process.err); + + /* + * Don't bother checking return value, because "emacsclient --version" + * seems to always exits with code 1. + */ + finish_command(&ec_process); + + if (prefixcmp(buffer.buf, "emacsclient")) { + fprintf(stderr, "Failed to parse emacsclient version.\n"); + strbuf_release(&buffer); + return -1; + } + + strbuf_remove(&buffer, 0, strlen("emacsclient")); + version = atoi(buffer.buf); + + if (version < 22) { + fprintf(stderr, + "emacsclient version '%d' too old (< 22).\n", + version); + strbuf_release(&buffer); + return -1; + } + + strbuf_release(&buffer); + return 0; +} + +static void exec_woman_emacs(const char* path, const char *page) +{ + if (!check_emacsclient_version()) { + /* This works only with emacsclient version >= 22. */ + struct strbuf man_page = STRBUF_INIT; + + if (!path) + path = "emacsclient"; + strbuf_addf(&man_page, "(woman \"%s\")", page); + execlp(path, "emacsclient", "-e", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); + } +} + +static void exec_man_konqueror(const char* path, const char *page) +{ + const char *display = getenv("DISPLAY"); + if (display && *display) { + struct strbuf man_page = STRBUF_INIT; + const char *filename = "kfmclient"; + + /* It's simpler to launch konqueror using kfmclient. */ + if (path) { + const char *file = strrchr(path, '/'); + if (file && !strcmp(file + 1, "konqueror")) { + char *new = xstrdup(path); + char *dest = strrchr(new, '/'); + + /* strlen("konqueror") == strlen("kfmclient") */ + strcpy(dest + 1, "kfmclient"); + path = new; + } + if (file) + filename = file; + } else + path = "kfmclient"; + strbuf_addf(&man_page, "man:%s(1)", page); + execlp(path, filename, "newTab", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); + } +} + +static void exec_man_man(const char* path, const char *page) +{ + if (!path) + path = "man"; + execlp(path, "man", page, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); +} + +static void exec_man_cmd(const char *cmd, const char *page) +{ + struct strbuf shell_cmd = STRBUF_INIT; + strbuf_addf(&shell_cmd, "%s %s", cmd, page); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + warning("failed to exec '%s': %s", cmd, strerror(errno)); +} + +static void add_man_viewer(const char *name) +{ + struct man_viewer_list **p = &man_viewer_list; + size_t len = strlen(name); + + while (*p) + p = &((*p)->next); + *p = xcalloc(1, (sizeof(**p) + len + 1)); + strncpy((*p)->name, name, len); +} + +static int supported_man_viewer(const char *name, size_t len) +{ + return (!strncasecmp("man", name, len) || + !strncasecmp("woman", name, len) || + !strncasecmp("konqueror", name, len)); +} + +static void do_add_man_viewer_info(const char *name, + size_t len, + const char *value) +{ + struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); + + strncpy(new->name, name, len); + new->info = xstrdup(value); + new->next = man_viewer_info_list; + man_viewer_info_list = new; +} + +static int add_man_viewer_path(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + do_add_man_viewer_info(name, len, value); + else + warning("'%s': path for unsupported man viewer.\n" + "Please consider using 'man..cmd' instead.", + name); + + return 0; +} + +static int add_man_viewer_cmd(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + warning("'%s': cmd for supported man viewer.\n" + "Please consider using 'man..path' instead.", + name); + else + do_add_man_viewer_info(name, len, value); + + return 0; +} + +static int add_man_viewer_info(const char *var, const char *value) +{ + const char *name = var + 4; + const char *subkey = strrchr(name, '.'); + + if (!subkey) + return error("Config with no key for man viewer: %s", name); + + if (!strcmp(subkey, ".path")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_path(name, subkey - name, value); + } + if (!strcmp(subkey, ".cmd")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_cmd(name, subkey - name, value); + } + + warning("'%s': unsupported man viewer sub key.", subkey); + return 0; +} + +static int git_help_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "help.format")) { + if (!value) + return config_error_nonbool(var); + help_format = parse_help_format(value); + return 0; + } + if (!strcmp(var, "man.viewer")) { + if (!value) + return config_error_nonbool(var); + add_man_viewer(value); + return 0; + } + if (!prefixcmp(var, "man.")) + return add_man_viewer_info(var, value); + + return git_default_config(var, value, cb); +} + +static struct cmdnames main_cmds, other_cmds; + +void list_common_cmds_help(void) +{ + int i, longest = 0; + + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + if (longest < strlen(common_cmds[i].name)) + longest = strlen(common_cmds[i].name); + } + + puts("The most commonly used git commands are:"); + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + printf(" %s ", common_cmds[i].name); + mput_char(' ', longest - strlen(common_cmds[i].name)); + puts(common_cmds[i].help); + } +} + +static int is_git_command(const char *s) +{ + return is_in_cmdlist(&main_cmds, s) || + is_in_cmdlist(&other_cmds, s); +} + +static const char *prepend(const char *prefix, const char *cmd) +{ + size_t pre_len = strlen(prefix); + size_t cmd_len = strlen(cmd); + char *p = xmalloc(pre_len + cmd_len + 1); + memcpy(p, prefix, pre_len); + strcpy(p + pre_len, cmd); + return p; +} + +static const char *cmd_to_page(const char *git_cmd) +{ + if (!git_cmd) + return "git"; + else if (!prefixcmp(git_cmd, "git")) + return git_cmd; + else if (is_git_command(git_cmd)) + return prepend("git-", git_cmd); + else + return prepend("git", git_cmd); +} + +static void setup_man_path(void) +{ + struct strbuf new_path; + const char *old_path = getenv("MANPATH"); + + strbuf_init(&new_path, 0); + + /* We should always put ':' after our path. If there is no + * old_path, the ':' at the end will let 'man' to try + * system-wide paths after ours to find the manual page. If + * there is old_path, we need ':' as delimiter. */ + strbuf_addstr(&new_path, "@GIT_MAN_PATH@"); + strbuf_addch(&new_path, ':'); + if (old_path) + strbuf_addstr(&new_path, old_path); + + setenv("MANPATH", new_path.buf, 1); + + strbuf_release(&new_path); +} + +static void exec_viewer(const char *name, const char *page) +{ + const char *info = get_man_viewer_info(name); + + if (!strcasecmp(name, "man")) + exec_man_man(info, page); + else if (!strcasecmp(name, "woman")) + exec_woman_emacs(info, page); + else if (!strcasecmp(name, "konqueror")) + exec_man_konqueror(info, page); + else if (info) + exec_man_cmd(info, page); + else + warning("'%s': unknown man viewer.", name); +} + +static void show_man_page(const char *git_cmd) +{ + struct man_viewer_list *viewer; + const char *page = cmd_to_page(git_cmd); + const char *fallback = getenv("GIT_MAN_VIEWER"); + + setup_man_path(); + for (viewer = man_viewer_list; viewer; viewer = viewer->next) + { + exec_viewer(viewer->name, page); /* will return when unable */ + } + if (fallback) + exec_viewer(fallback, page); + exec_viewer("man", page); + die("no man viewer handled the request"); +} + +static void show_info_page(const char *git_cmd) +{ + const char *page = cmd_to_page(git_cmd); + setenv("INFOPATH", "@GIT_INFO_PATH@", 1); + execlp("info", "info", "gitman", page, NULL); +} + +static void get_html_page_path(struct strbuf *page_path, const char *page) +{ + struct stat st; + const char *html_path = system_path("@GIT_HTML_PATH@"); + + /* Check that we have a git documentation directory. */ + if (stat(mkpath("%s/git.html", html_path), &st) + || !S_ISREG(st.st_mode)) + die("'%s': not a documentation directory.", html_path); + + strbuf_init(page_path, 0); + strbuf_addf(page_path, "%s/%s.html", html_path, page); +} + +/* + * If open_html is not defined in a platform-specific way (see for + * example compat/mingw.h), we use the script web--browse to display + * HTML. + */ +#ifndef open_html +void open_html(const char *path) +{ + execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); +} +#endif + +static void show_html_page(const char *git_cmd) +{ + const char *page = cmd_to_page(git_cmd); + struct strbuf page_path; /* it leaks but we exec bellow */ + + get_html_page_path(&page_path, page); + + open_html(page_path.buf); +} + +int cmd_help(int argc, const char **argv, const char *prefix) +{ + int nongit; + const char *alias; + load_command_list("git-", &main_cmds, &other_cmds); + + setup_git_directory_gently(&nongit); + git_config(git_help_config, NULL); + + argc = parse_options(argc, argv, builtin_help_options, + builtin_help_usage, 0); + + if (show_all) { + printf("usage: %s\n\n", git_usage_string); + list_commands("git commands", &main_cmds, &other_cmds); + printf("%s\n", git_more_info_string); + return 0; + } + + if (!argv[0]) { + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + printf("\n%s\n", git_more_info_string); + return 0; + } + + alias = alias_lookup(argv[0]); + if (alias && !is_git_command(argv[0])) { + printf("`git %s' is aliased to `%s'\n", argv[0], alias); + return 0; + } + + switch (help_format) { + case HELP_FORMAT_MAN: + show_man_page(argv[0]); + break; + case HELP_FORMAT_INFO: + show_info_page(argv[0]); + break; + case HELP_FORMAT_WEB: + show_html_page(argv[0]); + break; + } + + return 0; +} diff --git a/cache.h b/cache.h deleted file mode 100644 index de8c2b6..0000000 --- a/cache.h +++ /dev/null @@ -1,851 +0,0 @@ -#ifndef CACHE_H -#define CACHE_H - -#include "git-compat-util.h" -#include "strbuf.h" -#include "hash.h" - -#include SHA1_HEADER -#include - -#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 -#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) -#endif - -#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT) -#define DTYPE(de) ((de)->d_type) -#else -#undef DT_UNKNOWN -#undef DT_DIR -#undef DT_REG -#undef DT_LNK -#define DT_UNKNOWN 0 -#define DT_DIR 1 -#define DT_REG 2 -#define DT_LNK 3 -#define DTYPE(de) DT_UNKNOWN -#endif - -/* unknown mode (impossible combination S_IFIFO|S_IFCHR) */ -#define S_IFINVALID 0030000 - -/* - * A "directory link" is a link to another git directory. - * - * The value 0160000 is not normally a valid mode, and - * also just happens to be S_IFDIR + S_IFLNK - * - * NOTE! We *really* shouldn't depend on the S_IFxxx macros - * always having the same values everywhere. We should use - * our internal git values for these things, and then we can - * translate that to the OS-specific value. It just so - * happens that everybody shares the same bit representation - * in the UNIX world (and apparently wider too..) - */ -#define S_IFGITLINK 0160000 -#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) - -/* - * Intensive research over the course of many years has shown that - * port 9418 is totally unused by anything else. Or - * - * Your search - "port 9418" - did not match any documents. - * - * as www.google.com puts it. - * - * This port has been properly assigned for git use by IANA: - * git (Assigned-9418) [I06-050728-0001]. - * - * git 9418/tcp git pack transfer service - * git 9418/udp git pack transfer service - * - * with Linus Torvalds as the point of - * contact. September 2005. - * - * See http://www.iana.org/assignments/port-numbers - */ -#define DEFAULT_GIT_PORT 9418 - -/* - * Basic data structures for the directory cache - */ - -#define CACHE_SIGNATURE 0x44495243 /* "DIRC" */ -struct cache_header { - unsigned int hdr_signature; - unsigned int hdr_version; - unsigned int hdr_entries; -}; - -/* - * The "cache_time" is just the low 32 bits of the - * time. It doesn't matter if it overflows - we only - * check it for equality in the 32 bits we save. - */ -struct cache_time { - unsigned int sec; - unsigned int nsec; -}; - -/* - * dev/ino/uid/gid/size are also just tracked to the low 32 bits - * Again - this is just a (very strong in practice) heuristic that - * the inode hasn't changed. - * - * We save the fields in big-endian order to allow using the - * index file over NFS transparently. - */ -struct ondisk_cache_entry { - struct cache_time ctime; - struct cache_time mtime; - unsigned int dev; - unsigned int ino; - unsigned int mode; - unsigned int uid; - unsigned int gid; - unsigned int size; - unsigned char sha1[20]; - unsigned short flags; - char name[FLEX_ARRAY]; /* more */ -}; - -struct cache_entry { - unsigned int ce_ctime; - unsigned int ce_mtime; - unsigned int ce_dev; - unsigned int ce_ino; - unsigned int ce_mode; - unsigned int ce_uid; - unsigned int ce_gid; - unsigned int ce_size; - unsigned int ce_flags; - unsigned char sha1[20]; - struct cache_entry *next; - char name[FLEX_ARRAY]; /* more */ -}; - -#define CE_NAMEMASK (0x0fff) -#define CE_STAGEMASK (0x3000) -#define CE_EXTENDED (0x4000) -#define CE_VALID (0x8000) -#define CE_STAGESHIFT 12 - -/* In-memory only */ -#define CE_UPDATE (0x10000) -#define CE_REMOVE (0x20000) -#define CE_UPTODATE (0x40000) -#define CE_ADDED (0x80000) - -#define CE_HASHED (0x100000) -#define CE_UNHASHED (0x200000) - -/* - * Copy the sha1 and stat state of a cache entry from one to - * another. But we never change the name, or the hash state! - */ -#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED) -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src) -{ - unsigned int state = dst->ce_flags & CE_STATE_MASK; - - /* Don't copy hash chain and name */ - memcpy(dst, src, offsetof(struct cache_entry, next)); - - /* Restore the hash state */ - dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state; -} - -static inline unsigned create_ce_flags(size_t len, unsigned stage) -{ - if (len >= CE_NAMEMASK) - len = CE_NAMEMASK; - return (len | (stage << CE_STAGESHIFT)); -} - -static inline size_t ce_namelen(const struct cache_entry *ce) -{ - size_t len = ce->ce_flags & CE_NAMEMASK; - if (len < CE_NAMEMASK) - return len; - return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK; -} - -#define ce_size(ce) cache_entry_size(ce_namelen(ce)) -#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce)) -#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) -#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) -#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) - -#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) -static inline unsigned int create_ce_mode(unsigned int mode) -{ - if (S_ISLNK(mode)) - return S_IFLNK; - if (S_ISDIR(mode) || S_ISGITLINK(mode)) - return S_IFGITLINK; - return S_IFREG | ce_permissions(mode); -} -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode) -{ - extern int trust_executable_bit, has_symlinks; - if (!has_symlinks && S_ISREG(mode) && - ce && S_ISLNK(ce->ce_mode)) - return ce->ce_mode; - if (!trust_executable_bit && S_ISREG(mode)) { - if (ce && S_ISREG(ce->ce_mode)) - return ce->ce_mode; - return create_ce_mode(0666); - } - return create_ce_mode(mode); -} -static inline int ce_to_dtype(const struct cache_entry *ce) -{ - unsigned ce_mode = ntohl(ce->ce_mode); - if (S_ISREG(ce_mode)) - return DT_REG; - else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode)) - return DT_DIR; - else if (S_ISLNK(ce_mode)) - return DT_LNK; - else - return DT_UNKNOWN; -} -#define canon_mode(mode) \ - (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \ - S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK) - -#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7) -#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7) - -struct index_state { - struct cache_entry **cache; - unsigned int cache_nr, cache_alloc, cache_changed; - struct cache_tree *cache_tree; - time_t timestamp; - void *alloc; - unsigned name_hash_initialized : 1, - initialized : 1; - struct hash_table name_hash; -}; - -extern struct index_state the_index; - -/* Name hashing */ -extern void add_name_hash(struct index_state *istate, struct cache_entry *ce); -/* - * We don't actually *remove* it, we can just mark it invalid so that - * we won't find it in lookups. - * - * Not only would we have to search the lists (simple enough), but - * we'd also have to rehash other hash buckets in case this makes the - * hash bucket empty (common). So it's much better to just mark - * it. - */ -static inline void remove_name_hash(struct cache_entry *ce) -{ - ce->ce_flags |= CE_UNHASHED; -} - - -#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS -#define active_cache (the_index.cache) -#define active_nr (the_index.cache_nr) -#define active_alloc (the_index.cache_alloc) -#define active_cache_changed (the_index.cache_changed) -#define active_cache_tree (the_index.cache_tree) - -#define read_cache() read_index(&the_index) -#define read_cache_from(path) read_index_from(&the_index, (path)) -#define read_cache_unmerged() read_index_unmerged(&the_index) -#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd)) -#define discard_cache() discard_index(&the_index) -#define unmerged_cache() unmerged_index(&the_index) -#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen)) -#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option)) -#define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name)) -#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos)) -#define remove_file_from_cache(path) remove_file_from_index(&the_index, (path)) -#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags)) -#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags)) -#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL) -#define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options)) -#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) -#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) -#endif - -enum object_type { - OBJ_BAD = -1, - OBJ_NONE = 0, - OBJ_COMMIT = 1, - OBJ_TREE = 2, - OBJ_BLOB = 3, - OBJ_TAG = 4, - /* 5 for future expansion */ - OBJ_OFS_DELTA = 6, - OBJ_REF_DELTA = 7, - OBJ_ANY, - OBJ_MAX, -}; - -static inline enum object_type object_type(unsigned int mode) -{ - return S_ISDIR(mode) ? OBJ_TREE : - S_ISGITLINK(mode) ? OBJ_COMMIT : - OBJ_BLOB; -} - -#define GIT_DIR_ENVIRONMENT "GIT_DIR" -#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" -#define DEFAULT_GIT_DIR_ENVIRONMENT ".git" -#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" -#define INDEX_ENVIRONMENT "GIT_INDEX_FILE" -#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE" -#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR" -#define CONFIG_ENVIRONMENT "GIT_CONFIG" -#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH" -#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES" -#define GITATTRIBUTES_FILE ".gitattributes" -#define INFOATTRIBUTES_FILE "info/attributes" -#define ATTRIBUTE_MACRO_PREFIX "[attr]" - -extern int is_bare_repository_cfg; -extern int is_bare_repository(void); -extern int is_inside_git_dir(void); -extern char *git_work_tree_cfg; -extern int is_inside_work_tree(void); -extern const char *get_git_dir(void); -extern char *get_object_directory(void); -extern char *get_index_file(void); -extern char *get_graft_file(void); -extern int set_git_dir(const char *path); -extern const char *get_git_work_tree(void); -extern const char *read_gitfile_gently(const char *path); -extern void set_git_work_tree(const char *tree); - -#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" - -extern const char **get_pathspec(const char *prefix, const char **pathspec); -extern void setup_work_tree(void); -extern const char *setup_git_directory_gently(int *); -extern const char *setup_git_directory(void); -extern const char *prefix_path(const char *prefix, int len, const char *path); -extern const char *prefix_filename(const char *prefix, int len, const char *path); -extern void verify_filename(const char *prefix, const char *name); -extern void verify_non_filename(const char *prefix, const char *name); - -#define INIT_DB_QUIET 0x0001 - -extern int init_db(const char *template_dir, unsigned int flags); - -#define alloc_nr(x) (((x)+16)*3/2) - -/* - * Realloc the buffer pointed at by variable 'x' so that it can hold - * at least 'nr' entries; the number of entries currently allocated - * is 'alloc', using the standard growing factor alloc_nr() macro. - * - * DO NOT USE any expression with side-effect for 'x' or 'alloc'. - */ -#define ALLOC_GROW(x, nr, alloc) \ - do { \ - if ((nr) > alloc) { \ - if (alloc_nr(alloc) < (nr)) \ - alloc = (nr); \ - else \ - alloc = alloc_nr(alloc); \ - x = xrealloc((x), alloc * sizeof(*(x))); \ - } \ - } while(0) - -/* Initialize and use the cache information */ -extern int read_index(struct index_state *); -extern int read_index_from(struct index_state *, const char *path); -extern int read_index_unmerged(struct index_state *); -extern int write_index(const struct index_state *, int newfd); -extern int discard_index(struct index_state *); -extern int unmerged_index(const struct index_state *); -extern int verify_path(const char *path); -extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase); -extern int index_name_pos(const struct index_state *, const char *name, int namelen); -#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ -#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ -#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ -#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ -extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); -extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); -extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); -extern int remove_index_entry_at(struct index_state *, int pos); -extern int remove_file_from_index(struct index_state *, const char *path); -#define ADD_CACHE_VERBOSE 1 -#define ADD_CACHE_PRETEND 2 -#define ADD_CACHE_IGNORE_ERRORS 4 -#define ADD_CACHE_IGNORE_REMOVAL 8 -extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); -extern int add_file_to_index(struct index_state *, const char *path, int flags); -extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); - -/* do stat comparison even if CE_VALID is true */ -#define CE_MATCH_IGNORE_VALID 01 -/* do not check the contents but report dirty on racily-clean entries */ -#define CE_MATCH_RACY_IS_DIRTY 02 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); - -extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); -extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); -extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); -extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); - -#define REFRESH_REALLY 0x0001 /* ignore_valid */ -#define REFRESH_UNMERGED 0x0002 /* allow unmerged */ -#define REFRESH_QUIET 0x0004 /* be quiet about it */ -#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ -#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ -#define REFRESH_SAY_CHANGED 0x0020 /* say "changed" not "needs update" */ -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen); - -struct lock_file { - struct lock_file *next; - int fd; - pid_t owner; - char on_list; - char filename[PATH_MAX]; -}; -extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); -extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); -extern int commit_lock_file(struct lock_file *); - -extern int hold_locked_index(struct lock_file *, int); -extern int commit_locked_index(struct lock_file *); -extern void set_alternate_index_output(const char *); -extern int close_lock_file(struct lock_file *); -extern void rollback_lock_file(struct lock_file *); -extern int delete_ref(const char *, const unsigned char *sha1); - -/* Environment bits from configuration mechanism */ -extern int trust_executable_bit; -extern int trust_ctime; -extern int quote_path_fully; -extern int has_symlinks; -extern int ignore_case; -extern int assume_unchanged; -extern int prefer_symlink_refs; -extern int log_all_ref_updates; -extern int warn_ambiguous_refs; -extern int shared_repository; -extern const char *apply_default_whitespace; -extern int zlib_compression_level; -extern int core_compression_level; -extern int core_compression_seen; -extern size_t packed_git_window_size; -extern size_t packed_git_limit; -extern size_t delta_base_cache_limit; -extern int auto_crlf; -extern int fsync_object_files; - -enum safe_crlf { - SAFE_CRLF_FALSE = 0, - SAFE_CRLF_FAIL = 1, - SAFE_CRLF_WARN = 2, -}; - -extern enum safe_crlf safe_crlf; - -enum branch_track { - BRANCH_TRACK_UNSPECIFIED = -1, - BRANCH_TRACK_NEVER = 0, - BRANCH_TRACK_REMOTE, - BRANCH_TRACK_ALWAYS, - BRANCH_TRACK_EXPLICIT, -}; - -enum rebase_setup_type { - AUTOREBASE_NEVER = 0, - AUTOREBASE_LOCAL, - AUTOREBASE_REMOTE, - AUTOREBASE_ALWAYS, -}; - -extern enum branch_track git_branch_track; -extern enum rebase_setup_type autorebase; - -#define GIT_REPO_VERSION 0 -extern int repository_format_version; -extern int check_repository_format(void); - -#define MTIME_CHANGED 0x0001 -#define CTIME_CHANGED 0x0002 -#define OWNER_CHANGED 0x0004 -#define MODE_CHANGED 0x0008 -#define INODE_CHANGED 0x0010 -#define DATA_CHANGED 0x0020 -#define TYPE_CHANGED 0x0040 - -/* Return a statically allocated filename matching the sha1 signature */ -extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); -extern char *sha1_file_name(const unsigned char *sha1); -extern char *sha1_pack_name(const unsigned char *sha1); -extern char *sha1_pack_index_name(const unsigned char *sha1); -extern const char *find_unique_abbrev(const unsigned char *sha1, int); -extern const unsigned char null_sha1[20]; -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); -} -static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) -{ - return memcmp(sha1, sha2, 20); -} -static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) -{ - memcpy(sha_dst, sha_src, 20); -} -static inline void hashclr(unsigned char *hash) -{ - memset(hash, 0, 20); -} - -int git_mkstemp(char *path, size_t n, const char *template); - -/* - * NOTE NOTE NOTE!! - * - * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must - * not be changed. Old repositories have core.sharedrepository written in - * numeric format, and therefore these values are preserved for compatibility - * reasons. - */ -enum sharedrepo { - PERM_UMASK = 0, - OLD_PERM_GROUP = 1, - OLD_PERM_EVERYBODY = 2, - PERM_GROUP = 0660, - PERM_EVERYBODY = 0664, -}; -int git_config_perm(const char *var, const char *value); -int adjust_shared_perm(const char *path); -int safe_create_leading_directories(char *path); -int safe_create_leading_directories_const(const char *path); -char *enter_repo(char *path, int strict); -static inline int is_absolute_path(const char *path) -{ - return path[0] == '/' || has_dos_drive_prefix(path); -} -const char *make_absolute_path(const char *path); -const char *make_nonrelative_path(const char *path); -const char *make_relative_path(const char *abs, const char *base); -int normalize_absolute_path(char *buf, const char *path); -int longest_ancestor_length(const char *path, const char *prefix_list); - -/* Read and unpack a sha1 file into memory, write memory to a sha1 file */ -extern int sha1_object_info(const unsigned char *, unsigned long *); -extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size); -extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); -extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); -extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); -extern int force_object_loose(const unsigned char *sha1, time_t mtime); - -/* just like read_sha1_file(), but non fatal in presence of bad objects */ -extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); - -extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); - -extern int move_temp_to_file(const char *tmpfile, const char *filename); - -extern int has_sha1_pack(const unsigned char *sha1, const char **ignore); -extern int has_sha1_file(const unsigned char *sha1); - -extern int has_pack_file(const unsigned char *sha1); -extern int has_pack_index(const unsigned char *sha1); - -extern const signed char hexval_table[256]; -static inline unsigned int hexval(unsigned char c) -{ - return hexval_table[c]; -} - -/* Convert to/from hex/sha1 representation */ -#define MINIMUM_ABBREV 4 -#define DEFAULT_ABBREV 7 - -extern int get_sha1(const char *str, unsigned char *sha1); -extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); -extern int get_sha1_hex(const char *hex, unsigned char *sha1); -extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ -extern int read_ref(const char *filename, unsigned char *sha1); -extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); - -extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); -extern const char *ref_rev_parse_rules[]; -extern const char *ref_fetch_rules[]; - -extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); -extern int validate_headref(const char *ref); - -extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); -extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); -extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); - -extern void *read_object_with_reference(const unsigned char *sha1, - const char *required_type, - unsigned long *size, - unsigned char *sha1_ret); - -extern struct object *peel_to_type(const char *name, int namelen, - struct object *o, enum object_type); - -enum date_mode { - DATE_NORMAL = 0, - DATE_RELATIVE, - DATE_SHORT, - DATE_LOCAL, - DATE_ISO8601, - DATE_RFC2822 -}; - -const char *show_date(unsigned long time, int timezone, enum date_mode mode); -int parse_date(const char *date, char *buf, int bufsize); -void datestamp(char *buf, int bufsize); -unsigned long approxidate(const char *); -enum date_mode parse_date_format(const char *format); - -#define IDENT_WARN_ON_NO_NAME 1 -#define IDENT_ERROR_ON_NO_NAME 2 -#define IDENT_NO_DATE 4 -extern const char *git_author_info(int); -extern const char *git_committer_info(int); -extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); -extern const char *fmt_name(const char *name, const char *email); - -struct checkout { - const char *base_dir; - int base_dir_len; - unsigned force:1, - quiet:1, - not_new:1, - refresh_cache:1; -}; - -extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); -extern int has_symlink_leading_path(int len, const char *name); - -extern struct alternate_object_database { - struct alternate_object_database *next; - char *name; - char base[FLEX_ARRAY]; /* more */ -} *alt_odb_list; -extern void prepare_alt_odb(void); -extern void add_to_alternates_file(const char *reference); - -struct pack_window { - struct pack_window *next; - unsigned char *base; - off_t offset; - size_t len; - unsigned int last_used; - unsigned int inuse_cnt; -}; - -extern struct packed_git { - struct packed_git *next; - struct pack_window *windows; - off_t pack_size; - const void *index_data; - size_t index_size; - uint32_t num_objects; - uint32_t num_bad_objects; - unsigned char *bad_object_sha1; - int index_version; - time_t mtime; - int pack_fd; - int pack_local; - unsigned char sha1[20]; - /* something like ".git/objects/pack/xxxxx.pack" */ - char pack_name[FLEX_ARRAY]; /* more */ -} *packed_git; - -struct pack_entry { - off_t offset; - unsigned char sha1[20]; - struct packed_git *p; -}; - -struct ref { - struct ref *next; - unsigned char old_sha1[20]; - unsigned char new_sha1[20]; - char *symref; - unsigned int force:1, - merge:1, - nonfastforward:1, - deletion:1; - enum { - REF_STATUS_NONE = 0, - REF_STATUS_OK, - REF_STATUS_REJECT_NONFASTFORWARD, - REF_STATUS_REJECT_NODELETE, - REF_STATUS_UPTODATE, - REF_STATUS_REMOTE_REJECT, - REF_STATUS_EXPECTING_REPORT, - } status; - char *remote_status; - struct ref *peer_ref; /* when renaming */ - char name[FLEX_ARRAY]; /* more */ -}; - -#define REF_NORMAL (1u << 0) -#define REF_HEADS (1u << 1) -#define REF_TAGS (1u << 2) - -extern struct ref *find_ref_by_name(struct ref *list, const char *name); - -#define CONNECT_VERBOSE (1u << 0) -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); -extern int finish_connect(struct child_process *conn); -extern int path_match(const char *path, int nr, char **match); -extern int get_ack(int fd, unsigned char *result_sha1); -extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); -extern int server_supports(const char *feature); - -extern struct packed_git *parse_pack_index(unsigned char *sha1); - -extern void prepare_packed_git(void); -extern void reprepare_packed_git(void); -extern void install_packed_git(struct packed_git *pack); - -extern struct packed_git *find_sha1_pack(const unsigned char *sha1, - struct packed_git *packs); - -extern void pack_report(void); -extern int open_pack_index(struct packed_git *); -extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); -extern void close_pack_windows(struct packed_git *); -extern void unuse_pack(struct pack_window **); -extern struct packed_git *add_packed_git(const char *, int, int); -extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t); -extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); -extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); -extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); -extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); -extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); -extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); -extern int matches_pack_name(struct packed_git *p, const char *name); - -/* Dumb servers support */ -extern int update_server_info(int); - -typedef int (*config_fn_t)(const char *, const char *, void *); -extern int git_default_config(const char *, const char *, void *); -extern int git_config_from_file(config_fn_t fn, const char *, void *); -extern int git_config(config_fn_t fn, void *); -extern int git_parse_long(const char *, long *); -extern int git_parse_ulong(const char *, unsigned long *); -extern int git_config_int(const char *, const char *); -extern unsigned long git_config_ulong(const char *, const char *); -extern int git_config_bool_or_int(const char *, const char *, int *); -extern int git_config_bool(const char *, const char *); -extern int git_config_string(const char **, const char *, const char *); -extern int git_config_set(const char *, const char *); -extern int git_config_set_multivar(const char *, const char *, const char *, int); -extern int git_config_rename_section(const char *, const char *); -extern const char *git_etc_gitconfig(void); -extern int check_repository_format_version(const char *var, const char *value, void *cb); -extern int git_config_system(void); -extern int git_config_global(void); -extern int config_error_nonbool(const char *); -extern const char *config_exclusive_filename; - -#define MAX_GITNAME (1000) -extern char git_default_email[MAX_GITNAME]; -extern char git_default_name[MAX_GITNAME]; -extern int user_ident_explicitly_given; - -extern const char *git_commit_encoding; -extern const char *git_log_output_encoding; - -/* IO helper functions */ -extern void maybe_flush_or_die(FILE *, const char *); -extern int copy_fd(int ifd, int ofd); -extern int copy_file(const char *dst, const char *src, int mode); -extern ssize_t read_in_full(int fd, void *buf, size_t count); -extern ssize_t write_in_full(int fd, const void *buf, size_t count); -extern void write_or_die(int fd, const void *buf, size_t count); -extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); -extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); -extern void fsync_or_die(int fd, const char *); - -/* pager.c */ -extern void setup_pager(void); -extern const char *pager_program; -extern int pager_in_use(void); -extern int pager_use_color; - -extern const char *editor_program; -extern const char *excludes_file; - -/* base85 */ -int decode_85(char *dst, const char *line, int linelen); -void encode_85(char *buf, const unsigned char *data, int bytes); - -/* alloc.c */ -extern void *alloc_blob_node(void); -extern void *alloc_tree_node(void); -extern void *alloc_commit_node(void); -extern void *alloc_tag_node(void); -extern void *alloc_object_node(void); -extern void alloc_report(void); - -/* trace.c */ -extern void trace_printf(const char *format, ...); -extern void trace_argv_printf(const char **argv, const char *format, ...); - -/* convert.c */ -/* returns 1 if *dst was used */ -extern int convert_to_git(const char *path, const char *src, size_t len, - struct strbuf *dst, enum safe_crlf checksafe); -extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); - -/* add */ -/* - * return 0 if success, 1 - if addition of a file failed and - * ADD_FILES_IGNORE_ERRORS was specified in flags - */ -int add_files_to_cache(const char *prefix, const char **pathspec, int flags); - -/* diff.c */ -extern int diff_auto_refresh_index; - -/* match-trees.c */ -void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); - -/* - * whitespace rules. - * used by both diff and apply - */ -#define WS_TRAILING_SPACE 01 -#define WS_SPACE_BEFORE_TAB 02 -#define WS_INDENT_WITH_NON_TAB 04 -#define WS_CR_AT_EOL 010 -#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) -extern unsigned whitespace_rule_cfg; -extern unsigned whitespace_rule(const char *); -extern unsigned parse_whitespace_rule(const char *); -extern unsigned ws_check(const char *line, int len, unsigned ws_rule); -extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); -extern char *whitespace_error_string(unsigned ws); -extern int ws_fix_copy(char *, const char *, int, unsigned, int *); -extern int ws_blank_line(const char *line, int len, unsigned ws_rule); - -/* ls-files */ -int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); -int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); -void overlay_tree_on_cache(const char *tree_name, const char *prefix); - -char *alias_lookup(const char *alias); -int split_cmdline(char *cmdline, const char ***argv); - -#endif /* CACHE_H */ diff --git a/cache.h.cmake b/cache.h.cmake new file mode 100644 index 0000000..3eb04b1 --- /dev/null +++ b/cache.h.cmake @@ -0,0 +1,851 @@ +#ifndef CACHE_H +#define CACHE_H + +#include "git-compat-util.h" +#include "strbuf.h" +#include "hash.h" + +#include @SHA1_HEADER@ +#include + +#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 +#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) +#endif + +#if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT) +#define DTYPE(de) ((de)->d_type) +#else +#undef DT_UNKNOWN +#undef DT_DIR +#undef DT_REG +#undef DT_LNK +#define DT_UNKNOWN 0 +#define DT_DIR 1 +#define DT_REG 2 +#define DT_LNK 3 +#define DTYPE(de) DT_UNKNOWN +#endif + +/* unknown mode (impossible combination S_IFIFO|S_IFCHR) */ +#define S_IFINVALID 0030000 + +/* + * A "directory link" is a link to another git directory. + * + * The value 0160000 is not normally a valid mode, and + * also just happens to be S_IFDIR + S_IFLNK + * + * NOTE! We *really* shouldn't depend on the S_IFxxx macros + * always having the same values everywhere. We should use + * our internal git values for these things, and then we can + * translate that to the OS-specific value. It just so + * happens that everybody shares the same bit representation + * in the UNIX world (and apparently wider too..) + */ +#define S_IFGITLINK 0160000 +#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) + +/* + * Intensive research over the course of many years has shown that + * port 9418 is totally unused by anything else. Or + * + * Your search - "port 9418" - did not match any documents. + * + * as www.google.com puts it. + * + * This port has been properly assigned for git use by IANA: + * git (Assigned-9418) [I06-050728-0001]. + * + * git 9418/tcp git pack transfer service + * git 9418/udp git pack transfer service + * + * with Linus Torvalds as the point of + * contact. September 2005. + * + * See http://www.iana.org/assignments/port-numbers + */ +#define DEFAULT_GIT_PORT 9418 + +/* + * Basic data structures for the directory cache + */ + +#define CACHE_SIGNATURE 0x44495243 /* "DIRC" */ +struct cache_header { + unsigned int hdr_signature; + unsigned int hdr_version; + unsigned int hdr_entries; +}; + +/* + * The "cache_time" is just the low 32 bits of the + * time. It doesn't matter if it overflows - we only + * check it for equality in the 32 bits we save. + */ +struct cache_time { + unsigned int sec; + unsigned int nsec; +}; + +/* + * dev/ino/uid/gid/size are also just tracked to the low 32 bits + * Again - this is just a (very strong in practice) heuristic that + * the inode hasn't changed. + * + * We save the fields in big-endian order to allow using the + * index file over NFS transparently. + */ +struct ondisk_cache_entry { + struct cache_time ctime; + struct cache_time mtime; + unsigned int dev; + unsigned int ino; + unsigned int mode; + unsigned int uid; + unsigned int gid; + unsigned int size; + unsigned char sha1[20]; + unsigned short flags; + char name[FLEX_ARRAY]; /* more */ +}; + +struct cache_entry { + unsigned int ce_ctime; + unsigned int ce_mtime; + unsigned int ce_dev; + unsigned int ce_ino; + unsigned int ce_mode; + unsigned int ce_uid; + unsigned int ce_gid; + unsigned int ce_size; + unsigned int ce_flags; + unsigned char sha1[20]; + struct cache_entry *next; + char name[FLEX_ARRAY]; /* more */ +}; + +#define CE_NAMEMASK (0x0fff) +#define CE_STAGEMASK (0x3000) +#define CE_EXTENDED (0x4000) +#define CE_VALID (0x8000) +#define CE_STAGESHIFT 12 + +/* In-memory only */ +#define CE_UPDATE (0x10000) +#define CE_REMOVE (0x20000) +#define CE_UPTODATE (0x40000) +#define CE_ADDED (0x80000) + +#define CE_HASHED (0x100000) +#define CE_UNHASHED (0x200000) + +/* + * Copy the sha1 and stat state of a cache entry from one to + * another. But we never change the name, or the hash state! + */ +#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED) +static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src) +{ + unsigned int state = dst->ce_flags & CE_STATE_MASK; + + /* Don't copy hash chain and name */ + memcpy(dst, src, offsetof(struct cache_entry, next)); + + /* Restore the hash state */ + dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state; +} + +static inline unsigned create_ce_flags(size_t len, unsigned stage) +{ + if (len >= CE_NAMEMASK) + len = CE_NAMEMASK; + return (len | (stage << CE_STAGESHIFT)); +} + +static inline size_t ce_namelen(const struct cache_entry *ce) +{ + size_t len = ce->ce_flags & CE_NAMEMASK; + if (len < CE_NAMEMASK) + return len; + return strlen(ce->name + CE_NAMEMASK) + CE_NAMEMASK; +} + +#define ce_size(ce) cache_entry_size(ce_namelen(ce)) +#define ondisk_ce_size(ce) ondisk_cache_entry_size(ce_namelen(ce)) +#define ce_stage(ce) ((CE_STAGEMASK & (ce)->ce_flags) >> CE_STAGESHIFT) +#define ce_uptodate(ce) ((ce)->ce_flags & CE_UPTODATE) +#define ce_mark_uptodate(ce) ((ce)->ce_flags |= CE_UPTODATE) + +#define ce_permissions(mode) (((mode) & 0100) ? 0755 : 0644) +static inline unsigned int create_ce_mode(unsigned int mode) +{ + if (S_ISLNK(mode)) + return S_IFLNK; + if (S_ISDIR(mode) || S_ISGITLINK(mode)) + return S_IFGITLINK; + return S_IFREG | ce_permissions(mode); +} +static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode) +{ + extern int trust_executable_bit, has_symlinks; + if (!has_symlinks && S_ISREG(mode) && + ce && S_ISLNK(ce->ce_mode)) + return ce->ce_mode; + if (!trust_executable_bit && S_ISREG(mode)) { + if (ce && S_ISREG(ce->ce_mode)) + return ce->ce_mode; + return create_ce_mode(0666); + } + return create_ce_mode(mode); +} +static inline int ce_to_dtype(const struct cache_entry *ce) +{ + unsigned ce_mode = ntohl(ce->ce_mode); + if (S_ISREG(ce_mode)) + return DT_REG; + else if (S_ISDIR(ce_mode) || S_ISGITLINK(ce_mode)) + return DT_DIR; + else if (S_ISLNK(ce_mode)) + return DT_LNK; + else + return DT_UNKNOWN; +} +#define canon_mode(mode) \ + (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \ + S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK) + +#define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7) +#define ondisk_cache_entry_size(len) ((offsetof(struct ondisk_cache_entry,name) + (len) + 8) & ~7) + +struct index_state { + struct cache_entry **cache; + unsigned int cache_nr, cache_alloc, cache_changed; + struct cache_tree *cache_tree; + time_t timestamp; + void *alloc; + unsigned name_hash_initialized : 1, + initialized : 1; + struct hash_table name_hash; +}; + +extern struct index_state the_index; + +/* Name hashing */ +extern void add_name_hash(struct index_state *istate, struct cache_entry *ce); +/* + * We don't actually *remove* it, we can just mark it invalid so that + * we won't find it in lookups. + * + * Not only would we have to search the lists (simple enough), but + * we'd also have to rehash other hash buckets in case this makes the + * hash bucket empty (common). So it's much better to just mark + * it. + */ +static inline void remove_name_hash(struct cache_entry *ce) +{ + ce->ce_flags |= CE_UNHASHED; +} + + +#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS +#define active_cache (the_index.cache) +#define active_nr (the_index.cache_nr) +#define active_alloc (the_index.cache_alloc) +#define active_cache_changed (the_index.cache_changed) +#define active_cache_tree (the_index.cache_tree) + +#define read_cache() read_index(&the_index) +#define read_cache_from(path) read_index_from(&the_index, (path)) +#define read_cache_unmerged() read_index_unmerged(&the_index) +#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd)) +#define discard_cache() discard_index(&the_index) +#define unmerged_cache() unmerged_index(&the_index) +#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen)) +#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option)) +#define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name)) +#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos)) +#define remove_file_from_cache(path) remove_file_from_index(&the_index, (path)) +#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags)) +#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags)) +#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL) +#define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options)) +#define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) +#define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) +#endif + +enum object_type { + OBJ_BAD = -1, + OBJ_NONE = 0, + OBJ_COMMIT = 1, + OBJ_TREE = 2, + OBJ_BLOB = 3, + OBJ_TAG = 4, + /* 5 for future expansion */ + OBJ_OFS_DELTA = 6, + OBJ_REF_DELTA = 7, + OBJ_ANY, + OBJ_MAX, +}; + +static inline enum object_type object_type(unsigned int mode) +{ + return S_ISDIR(mode) ? OBJ_TREE : + S_ISGITLINK(mode) ? OBJ_COMMIT : + OBJ_BLOB; +} + +#define GIT_DIR_ENVIRONMENT "GIT_DIR" +#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE" +#define DEFAULT_GIT_DIR_ENVIRONMENT ".git" +#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY" +#define INDEX_ENVIRONMENT "GIT_INDEX_FILE" +#define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE" +#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR" +#define CONFIG_ENVIRONMENT "GIT_CONFIG" +#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH" +#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES" +#define GITATTRIBUTES_FILE ".gitattributes" +#define INFOATTRIBUTES_FILE "info/attributes" +#define ATTRIBUTE_MACRO_PREFIX "[attr]" + +extern int is_bare_repository_cfg; +extern int is_bare_repository(void); +extern int is_inside_git_dir(void); +extern char *git_work_tree_cfg; +extern int is_inside_work_tree(void); +extern const char *get_git_dir(void); +extern char *get_object_directory(void); +extern char *get_index_file(void); +extern char *get_graft_file(void); +extern int set_git_dir(const char *path); +extern const char *get_git_work_tree(void); +extern const char *read_gitfile_gently(const char *path); +extern void set_git_work_tree(const char *tree); + +#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES" + +extern const char **get_pathspec(const char *prefix, const char **pathspec); +extern void setup_work_tree(void); +extern const char *setup_git_directory_gently(int *); +extern const char *setup_git_directory(void); +extern const char *prefix_path(const char *prefix, int len, const char *path); +extern const char *prefix_filename(const char *prefix, int len, const char *path); +extern void verify_filename(const char *prefix, const char *name); +extern void verify_non_filename(const char *prefix, const char *name); + +#define INIT_DB_QUIET 0x0001 + +extern int init_db(const char *template_dir, unsigned int flags); + +#define alloc_nr(x) (((x)+16)*3/2) + +/* + * Realloc the buffer pointed at by variable 'x' so that it can hold + * at least 'nr' entries; the number of entries currently allocated + * is 'alloc', using the standard growing factor alloc_nr() macro. + * + * DO NOT USE any expression with side-effect for 'x' or 'alloc'. + */ +#define ALLOC_GROW(x, nr, alloc) \ + do { \ + if ((nr) > alloc) { \ + if (alloc_nr(alloc) < (nr)) \ + alloc = (nr); \ + else \ + alloc = alloc_nr(alloc); \ + x = xrealloc((x), alloc * sizeof(*(x))); \ + } \ + } while(0) + +/* Initialize and use the cache information */ +extern int read_index(struct index_state *); +extern int read_index_from(struct index_state *, const char *path); +extern int read_index_unmerged(struct index_state *); +extern int write_index(const struct index_state *, int newfd); +extern int discard_index(struct index_state *); +extern int unmerged_index(const struct index_state *); +extern int verify_path(const char *path); +extern struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int igncase); +extern int index_name_pos(const struct index_state *, const char *name, int namelen); +#define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ +#define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ +#define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ +#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ +extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); +extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); +extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); +extern int remove_index_entry_at(struct index_state *, int pos); +extern int remove_file_from_index(struct index_state *, const char *path); +#define ADD_CACHE_VERBOSE 1 +#define ADD_CACHE_PRETEND 2 +#define ADD_CACHE_IGNORE_ERRORS 4 +#define ADD_CACHE_IGNORE_REMOVAL 8 +extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); +extern int add_file_to_index(struct index_state *, const char *path, int flags); +extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); +extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); + +/* do stat comparison even if CE_VALID is true */ +#define CE_MATCH_IGNORE_VALID 01 +/* do not check the contents but report dirty on racily-clean entries */ +#define CE_MATCH_RACY_IS_DIRTY 02 +extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); +extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); + +extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); +extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); +extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); +extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); + +#define REFRESH_REALLY 0x0001 /* ignore_valid */ +#define REFRESH_UNMERGED 0x0002 /* allow unmerged */ +#define REFRESH_QUIET 0x0004 /* be quiet about it */ +#define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ +#define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ +#define REFRESH_SAY_CHANGED 0x0020 /* say "changed" not "needs update" */ +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen); + +struct lock_file { + struct lock_file *next; + int fd; + pid_t owner; + char on_list; + char filename[PATH_MAX]; +}; +extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); +extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); +extern int commit_lock_file(struct lock_file *); + +extern int hold_locked_index(struct lock_file *, int); +extern int commit_locked_index(struct lock_file *); +extern void set_alternate_index_output(const char *); +extern int close_lock_file(struct lock_file *); +extern void rollback_lock_file(struct lock_file *); +extern int delete_ref(const char *, const unsigned char *sha1); + +/* Environment bits from configuration mechanism */ +extern int trust_executable_bit; +extern int trust_ctime; +extern int quote_path_fully; +extern int has_symlinks; +extern int ignore_case; +extern int assume_unchanged; +extern int prefer_symlink_refs; +extern int log_all_ref_updates; +extern int warn_ambiguous_refs; +extern int shared_repository; +extern const char *apply_default_whitespace; +extern int zlib_compression_level; +extern int core_compression_level; +extern int core_compression_seen; +extern size_t packed_git_window_size; +extern size_t packed_git_limit; +extern size_t delta_base_cache_limit; +extern int auto_crlf; +extern int fsync_object_files; + +enum safe_crlf { + SAFE_CRLF_FALSE = 0, + SAFE_CRLF_FAIL = 1, + SAFE_CRLF_WARN = 2, +}; + +extern enum safe_crlf safe_crlf; + +enum branch_track { + BRANCH_TRACK_UNSPECIFIED = -1, + BRANCH_TRACK_NEVER = 0, + BRANCH_TRACK_REMOTE, + BRANCH_TRACK_ALWAYS, + BRANCH_TRACK_EXPLICIT, +}; + +enum rebase_setup_type { + AUTOREBASE_NEVER = 0, + AUTOREBASE_LOCAL, + AUTOREBASE_REMOTE, + AUTOREBASE_ALWAYS, +}; + +extern enum branch_track git_branch_track; +extern enum rebase_setup_type autorebase; + +#define GIT_REPO_VERSION 0 +extern int repository_format_version; +extern int check_repository_format(void); + +#define MTIME_CHANGED 0x0001 +#define CTIME_CHANGED 0x0002 +#define OWNER_CHANGED 0x0004 +#define MODE_CHANGED 0x0008 +#define INODE_CHANGED 0x0010 +#define DATA_CHANGED 0x0020 +#define TYPE_CHANGED 0x0040 + +/* Return a statically allocated filename matching the sha1 signature */ +extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2))); +extern char *sha1_file_name(const unsigned char *sha1); +extern char *sha1_pack_name(const unsigned char *sha1); +extern char *sha1_pack_index_name(const unsigned char *sha1); +extern const char *find_unique_abbrev(const unsigned char *sha1, int); +extern const unsigned char null_sha1[20]; +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) +{ + return memcmp(sha1, sha2, 20); +} +static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) +{ + memcpy(sha_dst, sha_src, 20); +} +static inline void hashclr(unsigned char *hash) +{ + memset(hash, 0, 20); +} + +int git_mkstemp(char *path, size_t n, const char *template); + +/* + * NOTE NOTE NOTE!! + * + * PERM_UMASK, OLD_PERM_GROUP and OLD_PERM_EVERYBODY enumerations must + * not be changed. Old repositories have core.sharedrepository written in + * numeric format, and therefore these values are preserved for compatibility + * reasons. + */ +enum sharedrepo { + PERM_UMASK = 0, + OLD_PERM_GROUP = 1, + OLD_PERM_EVERYBODY = 2, + PERM_GROUP = 0660, + PERM_EVERYBODY = 0664, +}; +int git_config_perm(const char *var, const char *value); +int adjust_shared_perm(const char *path); +int safe_create_leading_directories(char *path); +int safe_create_leading_directories_const(const char *path); +char *enter_repo(char *path, int strict); +static inline int is_absolute_path(const char *path) +{ + return path[0] == '/' || has_dos_drive_prefix(path); +} +const char *make_absolute_path(const char *path); +const char *make_nonrelative_path(const char *path); +const char *make_relative_path(const char *abs, const char *base); +int normalize_absolute_path(char *buf, const char *path); +int longest_ancestor_length(const char *path, const char *prefix_list); + +/* Read and unpack a sha1 file into memory, write memory to a sha1 file */ +extern int sha1_object_info(const unsigned char *, unsigned long *); +extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size); +extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); +extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1); +extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); +extern int force_object_loose(const unsigned char *sha1, time_t mtime); + +/* just like read_sha1_file(), but non fatal in presence of bad objects */ +extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); + +extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); + +extern int move_temp_to_file(const char *tmpfile, const char *filename); + +extern int has_sha1_pack(const unsigned char *sha1, const char **ignore); +extern int has_sha1_file(const unsigned char *sha1); + +extern int has_pack_file(const unsigned char *sha1); +extern int has_pack_index(const unsigned char *sha1); + +extern const signed char hexval_table[256]; +static inline unsigned int hexval(unsigned char c) +{ + return hexval_table[c]; +} + +/* Convert to/from hex/sha1 representation */ +#define MINIMUM_ABBREV 4 +#define DEFAULT_ABBREV 7 + +extern int get_sha1(const char *str, unsigned char *sha1); +extern int get_sha1_with_mode(const char *str, unsigned char *sha1, unsigned *mode); +extern int get_sha1_hex(const char *hex, unsigned char *sha1); +extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ +extern int read_ref(const char *filename, unsigned char *sha1); +extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); +extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); +extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); + +extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); +extern const char *ref_rev_parse_rules[]; +extern const char *ref_fetch_rules[]; + +extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg); +extern int validate_headref(const char *ref); + +extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); +extern int df_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2); +extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2); + +extern void *read_object_with_reference(const unsigned char *sha1, + const char *required_type, + unsigned long *size, + unsigned char *sha1_ret); + +extern struct object *peel_to_type(const char *name, int namelen, + struct object *o, enum object_type); + +enum date_mode { + DATE_NORMAL = 0, + DATE_RELATIVE, + DATE_SHORT, + DATE_LOCAL, + DATE_ISO8601, + DATE_RFC2822 +}; + +const char *show_date(unsigned long time, int timezone, enum date_mode mode); +int parse_date(const char *date, char *buf, int bufsize); +void datestamp(char *buf, int bufsize); +unsigned long approxidate(const char *); +enum date_mode parse_date_format(const char *format); + +#define IDENT_WARN_ON_NO_NAME 1 +#define IDENT_ERROR_ON_NO_NAME 2 +#define IDENT_NO_DATE 4 +extern const char *git_author_info(int); +extern const char *git_committer_info(int); +extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); +extern const char *fmt_name(const char *name, const char *email); + +struct checkout { + const char *base_dir; + int base_dir_len; + unsigned force:1, + quiet:1, + not_new:1, + refresh_cache:1; +}; + +extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); +extern int has_symlink_leading_path(int len, const char *name); + +extern struct alternate_object_database { + struct alternate_object_database *next; + char *name; + char base[FLEX_ARRAY]; /* more */ +} *alt_odb_list; +extern void prepare_alt_odb(void); +extern void add_to_alternates_file(const char *reference); + +struct pack_window { + struct pack_window *next; + unsigned char *base; + off_t offset; + size_t len; + unsigned int last_used; + unsigned int inuse_cnt; +}; + +extern struct packed_git { + struct packed_git *next; + struct pack_window *windows; + off_t pack_size; + const void *index_data; + size_t index_size; + uint32_t num_objects; + uint32_t num_bad_objects; + unsigned char *bad_object_sha1; + int index_version; + time_t mtime; + int pack_fd; + int pack_local; + unsigned char sha1[20]; + /* something like ".git/objects/pack/xxxxx.pack" */ + char pack_name[FLEX_ARRAY]; /* more */ +} *packed_git; + +struct pack_entry { + off_t offset; + unsigned char sha1[20]; + struct packed_git *p; +}; + +struct ref { + struct ref *next; + unsigned char old_sha1[20]; + unsigned char new_sha1[20]; + char *symref; + unsigned int force:1, + merge:1, + nonfastforward:1, + deletion:1; + enum { + REF_STATUS_NONE = 0, + REF_STATUS_OK, + REF_STATUS_REJECT_NONFASTFORWARD, + REF_STATUS_REJECT_NODELETE, + REF_STATUS_UPTODATE, + REF_STATUS_REMOTE_REJECT, + REF_STATUS_EXPECTING_REPORT, + } status; + char *remote_status; + struct ref *peer_ref; /* when renaming */ + char name[FLEX_ARRAY]; /* more */ +}; + +#define REF_NORMAL (1u << 0) +#define REF_HEADS (1u << 1) +#define REF_TAGS (1u << 2) + +extern struct ref *find_ref_by_name(struct ref *list, const char *name); + +#define CONNECT_VERBOSE (1u << 0) +extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); +extern int finish_connect(struct child_process *conn); +extern int path_match(const char *path, int nr, char **match); +extern int get_ack(int fd, unsigned char *result_sha1); +extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); +extern int server_supports(const char *feature); + +extern struct packed_git *parse_pack_index(unsigned char *sha1); + +extern void prepare_packed_git(void); +extern void reprepare_packed_git(void); +extern void install_packed_git(struct packed_git *pack); + +extern struct packed_git *find_sha1_pack(const unsigned char *sha1, + struct packed_git *packs); + +extern void pack_report(void); +extern int open_pack_index(struct packed_git *); +extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); +extern void close_pack_windows(struct packed_git *); +extern void unuse_pack(struct pack_window **); +extern struct packed_git *add_packed_git(const char *, int, int); +extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t); +extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); +extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); +extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); +extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); +extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); +extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern int matches_pack_name(struct packed_git *p, const char *name); + +/* Dumb servers support */ +extern int update_server_info(int); + +typedef int (*config_fn_t)(const char *, const char *, void *); +extern int git_default_config(const char *, const char *, void *); +extern int git_config_from_file(config_fn_t fn, const char *, void *); +extern int git_config(config_fn_t fn, void *); +extern int git_parse_long(const char *, long *); +extern int git_parse_ulong(const char *, unsigned long *); +extern int git_config_int(const char *, const char *); +extern unsigned long git_config_ulong(const char *, const char *); +extern int git_config_bool_or_int(const char *, const char *, int *); +extern int git_config_bool(const char *, const char *); +extern int git_config_string(const char **, const char *, const char *); +extern int git_config_set(const char *, const char *); +extern int git_config_set_multivar(const char *, const char *, const char *, int); +extern int git_config_rename_section(const char *, const char *); +extern const char *git_etc_gitconfig(void); +extern int check_repository_format_version(const char *var, const char *value, void *cb); +extern int git_config_system(void); +extern int git_config_global(void); +extern int config_error_nonbool(const char *); +extern const char *config_exclusive_filename; + +#define MAX_GITNAME (1000) +extern char git_default_email[MAX_GITNAME]; +extern char git_default_name[MAX_GITNAME]; +extern int user_ident_explicitly_given; + +extern const char *git_commit_encoding; +extern const char *git_log_output_encoding; + +/* IO helper functions */ +extern void maybe_flush_or_die(FILE *, const char *); +extern int copy_fd(int ifd, int ofd); +extern int copy_file(const char *dst, const char *src, int mode); +extern ssize_t read_in_full(int fd, void *buf, size_t count); +extern ssize_t write_in_full(int fd, const void *buf, size_t count); +extern void write_or_die(int fd, const void *buf, size_t count); +extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); +extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); +extern void fsync_or_die(int fd, const char *); + +/* pager.c */ +extern void setup_pager(void); +extern const char *pager_program; +extern int pager_in_use(void); +extern int pager_use_color; + +extern const char *editor_program; +extern const char *excludes_file; + +/* base85 */ +int decode_85(char *dst, const char *line, int linelen); +void encode_85(char *buf, const unsigned char *data, int bytes); + +/* alloc.c */ +extern void *alloc_blob_node(void); +extern void *alloc_tree_node(void); +extern void *alloc_commit_node(void); +extern void *alloc_tag_node(void); +extern void *alloc_object_node(void); +extern void alloc_report(void); + +/* trace.c */ +extern void trace_printf(const char *format, ...); +extern void trace_argv_printf(const char **argv, const char *format, ...); + +/* convert.c */ +/* returns 1 if *dst was used */ +extern int convert_to_git(const char *path, const char *src, size_t len, + struct strbuf *dst, enum safe_crlf checksafe); +extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); + +/* add */ +/* + * return 0 if success, 1 - if addition of a file failed and + * ADD_FILES_IGNORE_ERRORS was specified in flags + */ +int add_files_to_cache(const char *prefix, const char **pathspec, int flags); + +/* diff.c */ +extern int diff_auto_refresh_index; + +/* match-trees.c */ +void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); + +/* + * whitespace rules. + * used by both diff and apply + */ +#define WS_TRAILING_SPACE 01 +#define WS_SPACE_BEFORE_TAB 02 +#define WS_INDENT_WITH_NON_TAB 04 +#define WS_CR_AT_EOL 010 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +extern unsigned whitespace_rule_cfg; +extern unsigned whitespace_rule(const char *); +extern unsigned parse_whitespace_rule(const char *); +extern unsigned ws_check(const char *line, int len, unsigned ws_rule); +extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); +extern char *whitespace_error_string(unsigned ws); +extern int ws_fix_copy(char *, const char *, int, unsigned, int *); +extern int ws_blank_line(const char *line, int len, unsigned ws_rule); + +/* ls-files */ +int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); +int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); +void overlay_tree_on_cache(const char *tree_name, const char *prefix); + +char *alias_lookup(const char *alias); +int split_cmdline(char *cmdline, const char ***argv); + +#endif /* CACHE_H */ diff --git a/config.c b/config.c deleted file mode 100644 index 53f04a0..0000000 --- a/config.c +++ /dev/null @@ -1,1193 +0,0 @@ -/* - * GIT - The information manager from hell - * - * Copyright (C) Linus Torvalds, 2005 - * Copyright (C) Johannes Schindelin, 2005 - * - */ -#include "cache.h" -#include "exec_cmd.h" - -#define MAXNAME (256) - -static FILE *config_file; -static const char *config_file_name; -static int config_linenr; -static int config_file_eof; -static int zlib_compression_seen; - -const char *config_exclusive_filename = NULL; - -static int get_next_char(void) -{ - int c; - FILE *f; - - c = '\n'; - if ((f = config_file) != NULL) { - c = fgetc(f); - if (c == '\r') { - /* DOS like systems */ - c = fgetc(f); - if (c != '\n') { - ungetc(c, f); - c = '\r'; - } - } - if (c == '\n') - config_linenr++; - if (c == EOF) { - config_file_eof = 1; - c = '\n'; - } - } - return c; -} - -static char *parse_value(void) -{ - static char value[1024]; - int quote = 0, comment = 0, len = 0, space = 0; - - for (;;) { - int c = get_next_char(); - if (len >= sizeof(value)) - return NULL; - if (c == '\n') { - if (quote) - return NULL; - value[len] = 0; - return value; - } - if (comment) - continue; - if (isspace(c) && !quote) { - space = 1; - continue; - } - if (!quote) { - if (c == ';' || c == '#') { - comment = 1; - continue; - } - } - if (space) { - if (len) - value[len++] = ' '; - space = 0; - } - if (c == '\\') { - c = get_next_char(); - switch (c) { - case '\n': - continue; - case 't': - c = '\t'; - break; - case 'b': - c = '\b'; - break; - case 'n': - c = '\n'; - break; - /* Some characters escape as themselves */ - case '\\': case '"': - break; - /* Reject unknown escape sequences */ - default: - return NULL; - } - value[len++] = c; - continue; - } - if (c == '"') { - quote = 1-quote; - continue; - } - value[len++] = c; - } -} - -static inline int iskeychar(int c) -{ - return isalnum(c) || c == '-'; -} - -static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) -{ - int c; - char *value; - - /* Get the full name */ - for (;;) { - c = get_next_char(); - if (config_file_eof) - break; - if (!iskeychar(c)) - break; - name[len++] = tolower(c); - if (len >= MAXNAME) - return -1; - } - name[len] = 0; - while (c == ' ' || c == '\t') - c = get_next_char(); - - value = NULL; - if (c != '\n') { - if (c != '=') - return -1; - value = parse_value(); - if (!value) - return -1; - } - return fn(name, value, data); -} - -static int get_extended_base_var(char *name, int baselen, int c) -{ - do { - if (c == '\n') - return -1; - c = get_next_char(); - } while (isspace(c)); - - /* We require the format to be '[base "extension"]' */ - if (c != '"') - return -1; - name[baselen++] = '.'; - - for (;;) { - int c = get_next_char(); - if (c == '\n') - return -1; - if (c == '"') - break; - if (c == '\\') { - c = get_next_char(); - if (c == '\n') - return -1; - } - name[baselen++] = c; - if (baselen > MAXNAME / 2) - return -1; - } - - /* Final ']' */ - if (get_next_char() != ']') - return -1; - return baselen; -} - -static int get_base_var(char *name) -{ - int baselen = 0; - - for (;;) { - int c = get_next_char(); - if (config_file_eof) - return -1; - if (c == ']') - return baselen; - if (isspace(c)) - return get_extended_base_var(name, baselen, c); - if (!iskeychar(c) && c != '.') - return -1; - if (baselen > MAXNAME / 2) - return -1; - name[baselen++] = tolower(c); - } -} - -static int git_parse_file(config_fn_t fn, void *data) -{ - int comment = 0; - int baselen = 0; - static char var[MAXNAME]; - - for (;;) { - int c = get_next_char(); - if (c == '\n') { - if (config_file_eof) - return 0; - comment = 0; - continue; - } - if (comment || isspace(c)) - continue; - if (c == '#' || c == ';') { - comment = 1; - continue; - } - if (c == '[') { - baselen = get_base_var(var); - if (baselen <= 0) - break; - var[baselen++] = '.'; - var[baselen] = 0; - continue; - } - if (!isalpha(c)) - break; - var[baselen] = tolower(c); - if (get_value(fn, data, var, baselen+1) < 0) - break; - } - die("bad config file line %d in %s", config_linenr, config_file_name); -} - -static int parse_unit_factor(const char *end, unsigned long *val) -{ - if (!*end) - return 1; - else if (!strcasecmp(end, "k")) { - *val *= 1024; - return 1; - } - else if (!strcasecmp(end, "m")) { - *val *= 1024 * 1024; - return 1; - } - else if (!strcasecmp(end, "g")) { - *val *= 1024 * 1024 * 1024; - return 1; - } - return 0; -} - -int git_parse_long(const char *value, long *ret) -{ - if (value && *value) { - char *end; - long val = strtol(value, &end, 0); - unsigned long factor = 1; - if (!parse_unit_factor(end, &factor)) - return 0; - *ret = val * factor; - return 1; - } - return 0; -} - -int git_parse_ulong(const char *value, unsigned long *ret) -{ - if (value && *value) { - char *end; - unsigned long val = strtoul(value, &end, 0); - if (!parse_unit_factor(end, &val)) - return 0; - *ret = val; - return 1; - } - return 0; -} - -static void die_bad_config(const char *name) -{ - if (config_file_name) - die("bad config value for '%s' in %s", name, config_file_name); - die("bad config value for '%s'", name); -} - -int git_config_int(const char *name, const char *value) -{ - long ret; - if (!git_parse_long(value, &ret)) - die_bad_config(name); - return ret; -} - -unsigned long git_config_ulong(const char *name, const char *value) -{ - unsigned long ret; - if (!git_parse_ulong(value, &ret)) - die_bad_config(name); - return ret; -} - -int git_config_bool_or_int(const char *name, const char *value, int *is_bool) -{ - *is_bool = 1; - if (!value) - return 1; - if (!*value) - return 0; - if (!strcasecmp(value, "true") || !strcasecmp(value, "yes")) - return 1; - if (!strcasecmp(value, "false") || !strcasecmp(value, "no")) - return 0; - *is_bool = 0; - return git_config_int(name, value); -} - -int git_config_bool(const char *name, const char *value) -{ - int discard; - return !!git_config_bool_or_int(name, value, &discard); -} - -int git_config_string(const char **dest, const char *var, const char *value) -{ - if (!value) - return config_error_nonbool(var); - *dest = xstrdup(value); - return 0; -} - -static int git_default_core_config(const char *var, const char *value) -{ - /* This needs a better name */ - if (!strcmp(var, "core.filemode")) { - trust_executable_bit = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "core.trustctime")) { - trust_ctime = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.quotepath")) { - quote_path_fully = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.symlinks")) { - has_symlinks = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.ignorecase")) { - ignore_case = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.bare")) { - is_bare_repository_cfg = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.ignorestat")) { - assume_unchanged = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.prefersymlinkrefs")) { - prefer_symlink_refs = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.logallrefupdates")) { - log_all_ref_updates = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.warnambiguousrefs")) { - warn_ambiguous_refs = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.loosecompression")) { - int level = git_config_int(var, value); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); - zlib_compression_level = level; - zlib_compression_seen = 1; - return 0; - } - - if (!strcmp(var, "core.compression")) { - int level = git_config_int(var, value); - if (level == -1) - level = Z_DEFAULT_COMPRESSION; - else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); - core_compression_level = level; - core_compression_seen = 1; - if (!zlib_compression_seen) - zlib_compression_level = level; - return 0; - } - - if (!strcmp(var, "core.packedgitwindowsize")) { - int pgsz_x2 = getpagesize() * 2; - packed_git_window_size = git_config_int(var, value); - - /* This value must be multiple of (pagesize * 2) */ - packed_git_window_size /= pgsz_x2; - if (packed_git_window_size < 1) - packed_git_window_size = 1; - packed_git_window_size *= pgsz_x2; - return 0; - } - - if (!strcmp(var, "core.packedgitlimit")) { - packed_git_limit = git_config_int(var, value); - return 0; - } - - if (!strcmp(var, "core.deltabasecachelimit")) { - delta_base_cache_limit = git_config_int(var, value); - return 0; - } - - if (!strcmp(var, "core.autocrlf")) { - if (value && !strcasecmp(value, "input")) { - auto_crlf = -1; - return 0; - } - auto_crlf = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.safecrlf")) { - if (value && !strcasecmp(value, "warn")) { - safe_crlf = SAFE_CRLF_WARN; - return 0; - } - safe_crlf = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "core.pager")) - return git_config_string(&pager_program, var, value); - - if (!strcmp(var, "core.editor")) - return git_config_string(&editor_program, var, value); - - if (!strcmp(var, "core.excludesfile")) - return git_config_string(&excludes_file, var, value); - - if (!strcmp(var, "core.whitespace")) { - if (!value) - return config_error_nonbool(var); - whitespace_rule_cfg = parse_whitespace_rule(value); - return 0; - } - - if (!strcmp(var, "core.fsyncobjectfiles")) { - fsync_object_files = git_config_bool(var, value); - return 0; - } - - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} - -static int git_default_user_config(const char *var, const char *value) -{ - if (!strcmp(var, "user.name")) { - if (!value) - return config_error_nonbool(var); - strlcpy(git_default_name, value, sizeof(git_default_name)); - if (git_default_email[0]) - user_ident_explicitly_given = 1; - return 0; - } - - if (!strcmp(var, "user.email")) { - if (!value) - return config_error_nonbool(var); - strlcpy(git_default_email, value, sizeof(git_default_email)); - if (git_default_name[0]) - user_ident_explicitly_given = 1; - return 0; - } - - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} - -static int git_default_i18n_config(const char *var, const char *value) -{ - if (!strcmp(var, "i18n.commitencoding")) - return git_config_string(&git_commit_encoding, var, value); - - if (!strcmp(var, "i18n.logoutputencoding")) - return git_config_string(&git_log_output_encoding, var, value); - - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} - -static int git_default_branch_config(const char *var, const char *value) -{ - if (!strcmp(var, "branch.autosetupmerge")) { - if (value && !strcasecmp(value, "always")) { - git_branch_track = BRANCH_TRACK_ALWAYS; - return 0; - } - git_branch_track = git_config_bool(var, value); - return 0; - } - if (!strcmp(var, "branch.autosetuprebase")) { - if (!value) - return config_error_nonbool(var); - else if (!strcmp(value, "never")) - autorebase = AUTOREBASE_NEVER; - else if (!strcmp(value, "local")) - autorebase = AUTOREBASE_LOCAL; - else if (!strcmp(value, "remote")) - autorebase = AUTOREBASE_REMOTE; - else if (!strcmp(value, "always")) - autorebase = AUTOREBASE_ALWAYS; - else - return error("Malformed value for %s", var); - return 0; - } - - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} - -int git_default_config(const char *var, const char *value, void *dummy) -{ - if (!prefixcmp(var, "core.")) - return git_default_core_config(var, value); - - if (!prefixcmp(var, "user.")) - return git_default_user_config(var, value); - - if (!prefixcmp(var, "i18n.")) - return git_default_i18n_config(var, value); - - if (!prefixcmp(var, "branch.")) - return git_default_branch_config(var, value); - - if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { - pager_use_color = git_config_bool(var,value); - return 0; - } - - /* Add other config variables here and to Documentation/config.txt. */ - return 0; -} - -int git_config_from_file(config_fn_t fn, const char *filename, void *data) -{ - int ret; - FILE *f = fopen(filename, "r"); - - ret = -1; - if (f) { - config_file = f; - config_file_name = filename; - config_linenr = 1; - config_file_eof = 0; - ret = git_parse_file(fn, data); - fclose(f); - config_file_name = NULL; - } - return ret; -} - -const char *git_etc_gitconfig(void) -{ - static const char *system_wide; - if (!system_wide) - system_wide = system_path(ETC_GITCONFIG); - return system_wide; -} - -static int git_env_bool(const char *k, int def) -{ - const char *v = getenv(k); - return v ? git_config_bool(k, v) : def; -} - -int git_config_system(void) -{ - return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); -} - -int git_config_global(void) -{ - return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); -} - -int git_config(config_fn_t fn, void *data) -{ - int ret = 0; - char *repo_config = NULL; - const char *home = NULL; - - /* $GIT_CONFIG makes git read _only_ the given config file, - * $GIT_CONFIG_LOCAL will make it process it in addition to the - * global config file, the same way it would the per-repository - * config file otherwise. */ - if (config_exclusive_filename) - return git_config_from_file(fn, config_exclusive_filename, data); - if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) - ret += git_config_from_file(fn, git_etc_gitconfig(), - data); - - home = getenv("HOME"); - if (git_config_global() && home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - if (!access(user_config, R_OK)) - ret += git_config_from_file(fn, user_config, data); - free(user_config); - } - - repo_config = xstrdup(git_path("config")); - ret += git_config_from_file(fn, repo_config, data); - free(repo_config); - return ret; -} - -/* - * Find all the stuff for git_config_set() below. - */ - -#define MAX_MATCHES 512 - -static struct { - int baselen; - char* key; - int do_not_match; - regex_t* value_regex; - int multi_replace; - size_t offset[MAX_MATCHES]; - enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; - int seen; -} store; - -static int matches(const char* key, const char* value) -{ - return !strcmp(key, store.key) && - (store.value_regex == NULL || - (store.do_not_match ^ - !regexec(store.value_regex, value, 0, NULL, 0))); -} - -static int store_aux(const char* key, const char* value, void *cb) -{ - const char *ep; - size_t section_len; - - switch (store.state) { - case KEY_SEEN: - if (matches(key, value)) { - if (store.seen == 1 && store.multi_replace == 0) { - warning("%s has multiple values", key); - } else if (store.seen >= MAX_MATCHES) { - error("too many matches for %s", key); - return 1; - } - - store.offset[store.seen] = ftell(config_file); - store.seen++; - } - break; - case SECTION_SEEN: - /* - * What we are looking for is in store.key (both - * section and var), and its section part is baselen - * long. We found key (again, both section and var). - * We would want to know if this key is in the same - * section as what we are looking for. We already - * know we are in the same section as what should - * hold store.key. - */ - ep = strrchr(key, '.'); - section_len = ep - key; - - if ((section_len != store.baselen) || - memcmp(key, store.key, section_len+1)) { - store.state = SECTION_END_SEEN; - break; - } - - /* - * Do not increment matches: this is no match, but we - * just made sure we are in the desired section. - */ - store.offset[store.seen] = ftell(config_file); - /* fallthru */ - case SECTION_END_SEEN: - case START: - if (matches(key, value)) { - store.offset[store.seen] = ftell(config_file); - store.state = KEY_SEEN; - store.seen++; - } else { - if (strrchr(key, '.') - key == store.baselen && - !strncmp(key, store.key, store.baselen)) { - store.state = SECTION_SEEN; - store.offset[store.seen] = ftell(config_file); - } - } - } - return 0; -} - -static int write_error(const char *filename) -{ - error("failed to write new configuration file %s", filename); - - /* Same error code as "failed to rename". */ - return 4; -} - -static int store_write_section(int fd, const char* key) -{ - const char *dot; - int i, success; - struct strbuf sb; - - strbuf_init(&sb, 0); - dot = memchr(key, '.', store.baselen); - if (dot) { - strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key); - for (i = dot - key + 1; i < store.baselen; i++) { - if (key[i] == '"' || key[i] == '\\') - strbuf_addch(&sb, '\\'); - strbuf_addch(&sb, key[i]); - } - strbuf_addstr(&sb, "\"]\n"); - } else { - strbuf_addf(&sb, "[%.*s]\n", store.baselen, key); - } - - success = write_in_full(fd, sb.buf, sb.len) == sb.len; - strbuf_release(&sb); - - return success; -} - -static int store_write_pair(int fd, const char* key, const char* value) -{ - int i, success; - int length = strlen(key + store.baselen + 1); - const char *quote = ""; - struct strbuf sb; - - /* - * Check to see if the value needs to be surrounded with a dq pair. - * Note that problematic characters are always backslash-quoted; this - * check is about not losing leading or trailing SP and strings that - * follow beginning-of-comment characters (i.e. ';' and '#') by the - * configuration parser. - */ - if (value[0] == ' ') - quote = "\""; - for (i = 0; value[i]; i++) - if (value[i] == ';' || value[i] == '#') - quote = "\""; - if (i && value[i - 1] == ' ') - quote = "\""; - - strbuf_init(&sb, 0); - strbuf_addf(&sb, "\t%.*s = %s", - length, key + store.baselen + 1, quote); - - for (i = 0; value[i]; i++) - switch (value[i]) { - case '\n': - strbuf_addstr(&sb, "\\n"); - break; - case '\t': - strbuf_addstr(&sb, "\\t"); - break; - case '"': - case '\\': - strbuf_addch(&sb, '\\'); - default: - strbuf_addch(&sb, value[i]); - break; - } - strbuf_addf(&sb, "%s\n", quote); - - success = write_in_full(fd, sb.buf, sb.len) == sb.len; - strbuf_release(&sb); - - return success; -} - -static ssize_t find_beginning_of_line(const char* contents, size_t size, - size_t offset_, int* found_bracket) -{ - size_t equal_offset = size, bracket_offset = size; - ssize_t offset; - -contline: - for (offset = offset_-2; offset > 0 - && contents[offset] != '\n'; offset--) - switch (contents[offset]) { - case '=': equal_offset = offset; break; - case ']': bracket_offset = offset; break; - } - if (offset > 0 && contents[offset-1] == '\\') { - offset_ = offset; - goto contline; - } - if (bracket_offset < equal_offset) { - *found_bracket = 1; - offset = bracket_offset+1; - } else - offset++; - - return offset; -} - -int git_config_set(const char* key, const char* value) -{ - return git_config_set_multivar(key, value, NULL, 0); -} - -/* - * If value==NULL, unset in (remove from) config, - * if value_regex!=NULL, disregard key/value pairs where value does not match. - * if multi_replace==0, nothing, or only one matching key/value is replaced, - * else all matching key/values (regardless how many) are removed, - * before the new pair is written. - * - * Returns 0 on success. - * - * This function does this: - * - * - it locks the config file by creating ".git/config.lock" - * - * - it then parses the config using store_aux() as validator to find - * the position on the key/value pair to replace. If it is to be unset, - * it must be found exactly once. - * - * - the config file is mmap()ed and the part before the match (if any) is - * written to the lock file, then the changed part and the rest. - * - * - the config file is removed and the lock file rename()d to it. - * - */ -int git_config_set_multivar(const char* key, const char* value, - const char* value_regex, int multi_replace) -{ - int i, dot; - int fd = -1, in_fd; - int ret; - char* config_filename; - struct lock_file *lock = NULL; - const char* last_dot = strrchr(key, '.'); - - if (config_exclusive_filename) - config_filename = xstrdup(config_exclusive_filename); - else - config_filename = xstrdup(git_path("config")); - - /* - * Since "key" actually contains the section name and the real - * key name separated by a dot, we have to know where the dot is. - */ - - if (last_dot == NULL) { - error("key does not contain a section: %s", key); - ret = 2; - goto out_free; - } - store.baselen = last_dot - key; - - store.multi_replace = multi_replace; - - /* - * Validate the key and while at it, lower case it for matching. - */ - store.key = xmalloc(strlen(key) + 1); - dot = 0; - for (i = 0; key[i]; i++) { - unsigned char c = key[i]; - if (c == '.') - dot = 1; - /* Leave the extended basename untouched.. */ - if (!dot || i > store.baselen) { - if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) { - error("invalid key: %s", key); - free(store.key); - ret = 1; - goto out_free; - } - c = tolower(c); - } else if (c == '\n') { - error("invalid key (newline): %s", key); - free(store.key); - ret = 1; - goto out_free; - } - store.key[i] = c; - } - store.key[i] = 0; - - /* - * The lock serves a purpose in addition to locking: the new - * contents of .git/config will be written into it. - */ - lock = xcalloc(sizeof(struct lock_file), 1); - fd = hold_lock_file_for_update(lock, config_filename, 0); - if (fd < 0) { - error("could not lock config file %s", config_filename); - free(store.key); - ret = -1; - goto out_free; - } - - /* - * If .git/config does not exist yet, write a minimal version. - */ - in_fd = open(config_filename, O_RDONLY); - if ( in_fd < 0 ) { - free(store.key); - - if ( ENOENT != errno ) { - error("opening %s: %s", config_filename, - strerror(errno)); - ret = 3; /* same as "invalid config file" */ - goto out_free; - } - /* if nothing to unset, error out */ - if (value == NULL) { - ret = 5; - goto out_free; - } - - store.key = (char*)key; - if (!store_write_section(fd, key) || - !store_write_pair(fd, key, value)) - goto write_err_out; - } else { - struct stat st; - char* contents; - size_t contents_sz, copy_begin, copy_end; - int i, new_line = 0; - - if (value_regex == NULL) - store.value_regex = NULL; - else { - if (value_regex[0] == '!') { - store.do_not_match = 1; - value_regex++; - } else - store.do_not_match = 0; - - store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); - if (regcomp(store.value_regex, value_regex, - REG_EXTENDED)) { - error("invalid pattern: %s", value_regex); - free(store.value_regex); - ret = 6; - goto out_free; - } - } - - store.offset[0] = 0; - store.state = START; - store.seen = 0; - - /* - * After this, store.offset will contain the *end* offset - * of the last match, or remain at 0 if no match was found. - * As a side effect, we make sure to transform only a valid - * existing config file. - */ - if (git_config_from_file(store_aux, config_filename, NULL)) { - error("invalid config file %s", config_filename); - free(store.key); - if (store.value_regex != NULL) { - regfree(store.value_regex); - free(store.value_regex); - } - ret = 3; - goto out_free; - } - - free(store.key); - if (store.value_regex != NULL) { - regfree(store.value_regex); - free(store.value_regex); - } - - /* if nothing to unset, or too many matches, error out */ - if ((store.seen == 0 && value == NULL) || - (store.seen > 1 && multi_replace == 0)) { - ret = 5; - goto out_free; - } - - fstat(in_fd, &st); - contents_sz = xsize_t(st.st_size); - contents = xmmap(NULL, contents_sz, PROT_READ, - MAP_PRIVATE, in_fd, 0); - close(in_fd); - - if (store.seen == 0) - store.seen = 1; - - for (i = 0, copy_begin = 0; i < store.seen; i++) { - if (store.offset[i] == 0) { - store.offset[i] = copy_end = contents_sz; - } else if (store.state != KEY_SEEN) { - copy_end = store.offset[i]; - } else - copy_end = find_beginning_of_line( - contents, contents_sz, - store.offset[i]-2, &new_line); - - if (copy_end > 0 && contents[copy_end-1] != '\n') - new_line = 1; - - /* write the first part of the config */ - if (copy_end > copy_begin) { - if (write_in_full(fd, contents + copy_begin, - copy_end - copy_begin) < - copy_end - copy_begin) - goto write_err_out; - if (new_line && - write_in_full(fd, "\n", 1) != 1) - goto write_err_out; - } - copy_begin = store.offset[i]; - } - - /* write the pair (value == NULL means unset) */ - if (value != NULL) { - if (store.state == START) { - if (!store_write_section(fd, key)) - goto write_err_out; - } - if (!store_write_pair(fd, key, value)) - goto write_err_out; - } - - /* write the rest of the config */ - if (copy_begin < contents_sz) - if (write_in_full(fd, contents + copy_begin, - contents_sz - copy_begin) < - contents_sz - copy_begin) - goto write_err_out; - - munmap(contents, contents_sz); - } - - if (commit_lock_file(lock) < 0) { - error("could not commit config file %s", config_filename); - ret = 4; - goto out_free; - } - - /* - * lock is committed, so don't try to roll it back below. - * NOTE: Since lockfile.c keeps a linked list of all created - * lock_file structures, it isn't safe to free(lock). It's - * better to just leave it hanging around. - */ - lock = NULL; - ret = 0; - -out_free: - if (lock) - rollback_lock_file(lock); - free(config_filename); - return ret; - -write_err_out: - ret = write_error(lock->filename); - goto out_free; - -} - -static int section_name_match (const char *buf, const char *name) -{ - int i = 0, j = 0, dot = 0; - for (; buf[i] && buf[i] != ']'; i++) { - if (!dot && isspace(buf[i])) { - dot = 1; - if (name[j++] != '.') - break; - for (i++; isspace(buf[i]); i++) - ; /* do nothing */ - if (buf[i] != '"') - break; - continue; - } - if (buf[i] == '\\' && dot) - i++; - else if (buf[i] == '"' && dot) { - for (i++; isspace(buf[i]); i++) - ; /* do_nothing */ - break; - } - if (buf[i] != name[j++]) - break; - } - return (buf[i] == ']' && name[j] == 0); -} - -/* if new_name == NULL, the section is removed instead */ -int git_config_rename_section(const char *old_name, const char *new_name) -{ - int ret = 0, remove = 0; - char *config_filename; - struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); - int out_fd; - char buf[1024]; - - if (config_exclusive_filename) - config_filename = xstrdup(config_exclusive_filename); - else - config_filename = xstrdup(git_path("config")); - out_fd = hold_lock_file_for_update(lock, config_filename, 0); - if (out_fd < 0) { - ret = error("could not lock config file %s", config_filename); - goto out; - } - - if (!(config_file = fopen(config_filename, "rb"))) { - /* no config file means nothing to rename, no error */ - goto unlock_and_out; - } - - while (fgets(buf, sizeof(buf), config_file)) { - int i; - int length; - for (i = 0; buf[i] && isspace(buf[i]); i++) - ; /* do nothing */ - if (buf[i] == '[') { - /* it's a section */ - if (section_name_match (&buf[i+1], old_name)) { - ret++; - if (new_name == NULL) { - remove = 1; - continue; - } - store.baselen = strlen(new_name); - if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename); - goto out; - } - continue; - } - remove = 0; - } - if (remove) - continue; - length = strlen(buf); - if (write_in_full(out_fd, buf, length) != length) { - ret = write_error(lock->filename); - goto out; - } - } - fclose(config_file); - unlock_and_out: - if (commit_lock_file(lock) < 0) - ret = error("could not commit config file %s", config_filename); - out: - free(config_filename); - return ret; -} - -/* - * Call this to report error for your variable that should not - * get a boolean value (i.e. "[my] var" means "true"). - */ -int config_error_nonbool(const char *var) -{ - return error("Missing value for '%s'", var); -} diff --git a/config.c.cmake b/config.c.cmake new file mode 100644 index 0000000..9f19492 --- /dev/null +++ b/config.c.cmake @@ -0,0 +1,1193 @@ +/* + * GIT - The information manager from hell + * + * Copyright (C) Linus Torvalds, 2005 + * Copyright (C) Johannes Schindelin, 2005 + * + */ +#include "cache.h" +#include "exec_cmd.h" + +#define MAXNAME (256) + +static FILE *config_file; +static const char *config_file_name; +static int config_linenr; +static int config_file_eof; +static int zlib_compression_seen; + +const char *config_exclusive_filename = NULL; + +static int get_next_char(void) +{ + int c; + FILE *f; + + c = '\n'; + if ((f = config_file) != NULL) { + c = fgetc(f); + if (c == '\r') { + /* DOS like systems */ + c = fgetc(f); + if (c != '\n') { + ungetc(c, f); + c = '\r'; + } + } + if (c == '\n') + config_linenr++; + if (c == EOF) { + config_file_eof = 1; + c = '\n'; + } + } + return c; +} + +static char *parse_value(void) +{ + static char value[1024]; + int quote = 0, comment = 0, len = 0, space = 0; + + for (;;) { + int c = get_next_char(); + if (len >= sizeof(value)) + return NULL; + if (c == '\n') { + if (quote) + return NULL; + value[len] = 0; + return value; + } + if (comment) + continue; + if (isspace(c) && !quote) { + space = 1; + continue; + } + if (!quote) { + if (c == ';' || c == '#') { + comment = 1; + continue; + } + } + if (space) { + if (len) + value[len++] = ' '; + space = 0; + } + if (c == '\\') { + c = get_next_char(); + switch (c) { + case '\n': + continue; + case 't': + c = '\t'; + break; + case 'b': + c = '\b'; + break; + case 'n': + c = '\n'; + break; + /* Some characters escape as themselves */ + case '\\': case '"': + break; + /* Reject unknown escape sequences */ + default: + return NULL; + } + value[len++] = c; + continue; + } + if (c == '"') { + quote = 1-quote; + continue; + } + value[len++] = c; + } +} + +static inline int iskeychar(int c) +{ + return isalnum(c) || c == '-'; +} + +static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) +{ + int c; + char *value; + + /* Get the full name */ + for (;;) { + c = get_next_char(); + if (config_file_eof) + break; + if (!iskeychar(c)) + break; + name[len++] = tolower(c); + if (len >= MAXNAME) + return -1; + } + name[len] = 0; + while (c == ' ' || c == '\t') + c = get_next_char(); + + value = NULL; + if (c != '\n') { + if (c != '=') + return -1; + value = parse_value(); + if (!value) + return -1; + } + return fn(name, value, data); +} + +static int get_extended_base_var(char *name, int baselen, int c) +{ + do { + if (c == '\n') + return -1; + c = get_next_char(); + } while (isspace(c)); + + /* We require the format to be '[base "extension"]' */ + if (c != '"') + return -1; + name[baselen++] = '.'; + + for (;;) { + int c = get_next_char(); + if (c == '\n') + return -1; + if (c == '"') + break; + if (c == '\\') { + c = get_next_char(); + if (c == '\n') + return -1; + } + name[baselen++] = c; + if (baselen > MAXNAME / 2) + return -1; + } + + /* Final ']' */ + if (get_next_char() != ']') + return -1; + return baselen; +} + +static int get_base_var(char *name) +{ + int baselen = 0; + + for (;;) { + int c = get_next_char(); + if (config_file_eof) + return -1; + if (c == ']') + return baselen; + if (isspace(c)) + return get_extended_base_var(name, baselen, c); + if (!iskeychar(c) && c != '.') + return -1; + if (baselen > MAXNAME / 2) + return -1; + name[baselen++] = tolower(c); + } +} + +static int git_parse_file(config_fn_t fn, void *data) +{ + int comment = 0; + int baselen = 0; + static char var[MAXNAME]; + + for (;;) { + int c = get_next_char(); + if (c == '\n') { + if (config_file_eof) + return 0; + comment = 0; + continue; + } + if (comment || isspace(c)) + continue; + if (c == '#' || c == ';') { + comment = 1; + continue; + } + if (c == '[') { + baselen = get_base_var(var); + if (baselen <= 0) + break; + var[baselen++] = '.'; + var[baselen] = 0; + continue; + } + if (!isalpha(c)) + break; + var[baselen] = tolower(c); + if (get_value(fn, data, var, baselen+1) < 0) + break; + } + die("bad config file line %d in %s", config_linenr, config_file_name); +} + +static int parse_unit_factor(const char *end, unsigned long *val) +{ + if (!*end) + return 1; + else if (!strcasecmp(end, "k")) { + *val *= 1024; + return 1; + } + else if (!strcasecmp(end, "m")) { + *val *= 1024 * 1024; + return 1; + } + else if (!strcasecmp(end, "g")) { + *val *= 1024 * 1024 * 1024; + return 1; + } + return 0; +} + +int git_parse_long(const char *value, long *ret) +{ + if (value && *value) { + char *end; + long val = strtol(value, &end, 0); + unsigned long factor = 1; + if (!parse_unit_factor(end, &factor)) + return 0; + *ret = val * factor; + return 1; + } + return 0; +} + +int git_parse_ulong(const char *value, unsigned long *ret) +{ + if (value && *value) { + char *end; + unsigned long val = strtoul(value, &end, 0); + if (!parse_unit_factor(end, &val)) + return 0; + *ret = val; + return 1; + } + return 0; +} + +static void die_bad_config(const char *name) +{ + if (config_file_name) + die("bad config value for '%s' in %s", name, config_file_name); + die("bad config value for '%s'", name); +} + +int git_config_int(const char *name, const char *value) +{ + long ret; + if (!git_parse_long(value, &ret)) + die_bad_config(name); + return ret; +} + +unsigned long git_config_ulong(const char *name, const char *value) +{ + unsigned long ret; + if (!git_parse_ulong(value, &ret)) + die_bad_config(name); + return ret; +} + +int git_config_bool_or_int(const char *name, const char *value, int *is_bool) +{ + *is_bool = 1; + if (!value) + return 1; + if (!*value) + return 0; + if (!strcasecmp(value, "true") || !strcasecmp(value, "yes")) + return 1; + if (!strcasecmp(value, "false") || !strcasecmp(value, "no")) + return 0; + *is_bool = 0; + return git_config_int(name, value); +} + +int git_config_bool(const char *name, const char *value) +{ + int discard; + return !!git_config_bool_or_int(name, value, &discard); +} + +int git_config_string(const char **dest, const char *var, const char *value) +{ + if (!value) + return config_error_nonbool(var); + *dest = xstrdup(value); + return 0; +} + +static int git_default_core_config(const char *var, const char *value) +{ + /* This needs a better name */ + if (!strcmp(var, "core.filemode")) { + trust_executable_bit = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.trustctime")) { + trust_ctime = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.quotepath")) { + quote_path_fully = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.symlinks")) { + has_symlinks = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.ignorecase")) { + ignore_case = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.bare")) { + is_bare_repository_cfg = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.ignorestat")) { + assume_unchanged = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.prefersymlinkrefs")) { + prefer_symlink_refs = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.logallrefupdates")) { + log_all_ref_updates = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.warnambiguousrefs")) { + warn_ambiguous_refs = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.loosecompression")) { + int level = git_config_int(var, value); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die("bad zlib compression level %d", level); + zlib_compression_level = level; + zlib_compression_seen = 1; + return 0; + } + + if (!strcmp(var, "core.compression")) { + int level = git_config_int(var, value); + if (level == -1) + level = Z_DEFAULT_COMPRESSION; + else if (level < 0 || level > Z_BEST_COMPRESSION) + die("bad zlib compression level %d", level); + core_compression_level = level; + core_compression_seen = 1; + if (!zlib_compression_seen) + zlib_compression_level = level; + return 0; + } + + if (!strcmp(var, "core.packedgitwindowsize")) { + int pgsz_x2 = getpagesize() * 2; + packed_git_window_size = git_config_int(var, value); + + /* This value must be multiple of (pagesize * 2) */ + packed_git_window_size /= pgsz_x2; + if (packed_git_window_size < 1) + packed_git_window_size = 1; + packed_git_window_size *= pgsz_x2; + return 0; + } + + if (!strcmp(var, "core.packedgitlimit")) { + packed_git_limit = git_config_int(var, value); + return 0; + } + + if (!strcmp(var, "core.deltabasecachelimit")) { + delta_base_cache_limit = git_config_int(var, value); + return 0; + } + + if (!strcmp(var, "core.autocrlf")) { + if (value && !strcasecmp(value, "input")) { + auto_crlf = -1; + return 0; + } + auto_crlf = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.safecrlf")) { + if (value && !strcasecmp(value, "warn")) { + safe_crlf = SAFE_CRLF_WARN; + return 0; + } + safe_crlf = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.pager")) + return git_config_string(&pager_program, var, value); + + if (!strcmp(var, "core.editor")) + return git_config_string(&editor_program, var, value); + + if (!strcmp(var, "core.excludesfile")) + return git_config_string(&excludes_file, var, value); + + if (!strcmp(var, "core.whitespace")) { + if (!value) + return config_error_nonbool(var); + whitespace_rule_cfg = parse_whitespace_rule(value); + return 0; + } + + if (!strcmp(var, "core.fsyncobjectfiles")) { + fsync_object_files = git_config_bool(var, value); + return 0; + } + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + +static int git_default_user_config(const char *var, const char *value) +{ + if (!strcmp(var, "user.name")) { + if (!value) + return config_error_nonbool(var); + strlcpy(git_default_name, value, sizeof(git_default_name)); + if (git_default_email[0]) + user_ident_explicitly_given = 1; + return 0; + } + + if (!strcmp(var, "user.email")) { + if (!value) + return config_error_nonbool(var); + strlcpy(git_default_email, value, sizeof(git_default_email)); + if (git_default_name[0]) + user_ident_explicitly_given = 1; + return 0; + } + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + +static int git_default_i18n_config(const char *var, const char *value) +{ + if (!strcmp(var, "i18n.commitencoding")) + return git_config_string(&git_commit_encoding, var, value); + + if (!strcmp(var, "i18n.logoutputencoding")) + return git_config_string(&git_log_output_encoding, var, value); + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + +static int git_default_branch_config(const char *var, const char *value) +{ + if (!strcmp(var, "branch.autosetupmerge")) { + if (value && !strcasecmp(value, "always")) { + git_branch_track = BRANCH_TRACK_ALWAYS; + return 0; + } + git_branch_track = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "branch.autosetuprebase")) { + if (!value) + return config_error_nonbool(var); + else if (!strcmp(value, "never")) + autorebase = AUTOREBASE_NEVER; + else if (!strcmp(value, "local")) + autorebase = AUTOREBASE_LOCAL; + else if (!strcmp(value, "remote")) + autorebase = AUTOREBASE_REMOTE; + else if (!strcmp(value, "always")) + autorebase = AUTOREBASE_ALWAYS; + else + return error("Malformed value for %s", var); + return 0; + } + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + +int git_default_config(const char *var, const char *value, void *dummy) +{ + if (!prefixcmp(var, "core.")) + return git_default_core_config(var, value); + + if (!prefixcmp(var, "user.")) + return git_default_user_config(var, value); + + if (!prefixcmp(var, "i18n.")) + return git_default_i18n_config(var, value); + + if (!prefixcmp(var, "branch.")) + return git_default_branch_config(var, value); + + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { + pager_use_color = git_config_bool(var,value); + return 0; + } + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + +int git_config_from_file(config_fn_t fn, const char *filename, void *data) +{ + int ret; + FILE *f = fopen(filename, "r"); + + ret = -1; + if (f) { + config_file = f; + config_file_name = filename; + config_linenr = 1; + config_file_eof = 0; + ret = git_parse_file(fn, data); + fclose(f); + config_file_name = NULL; + } + return ret; +} + +const char *git_etc_gitconfig(void) +{ + static const char *system_wide; + if (!system_wide) + system_wide = system_path("@ETC_GITCONFIG@"); + return system_wide; +} + +static int git_env_bool(const char *k, int def) +{ + const char *v = getenv(k); + return v ? git_config_bool(k, v) : def; +} + +int git_config_system(void) +{ + return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); +} + +int git_config_global(void) +{ + return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0); +} + +int git_config(config_fn_t fn, void *data) +{ + int ret = 0; + char *repo_config = NULL; + const char *home = NULL; + + /* $GIT_CONFIG makes git read _only_ the given config file, + * $GIT_CONFIG_LOCAL will make it process it in addition to the + * global config file, the same way it would the per-repository + * config file otherwise. */ + if (config_exclusive_filename) + return git_config_from_file(fn, config_exclusive_filename, data); + if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) + ret += git_config_from_file(fn, git_etc_gitconfig(), + data); + + home = getenv("HOME"); + if (git_config_global() && home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + if (!access(user_config, R_OK)) + ret += git_config_from_file(fn, user_config, data); + free(user_config); + } + + repo_config = xstrdup(git_path("config")); + ret += git_config_from_file(fn, repo_config, data); + free(repo_config); + return ret; +} + +/* + * Find all the stuff for git_config_set() below. + */ + +#define MAX_MATCHES 512 + +static struct { + int baselen; + char* key; + int do_not_match; + regex_t* value_regex; + int multi_replace; + size_t offset[MAX_MATCHES]; + enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; + int seen; +} store; + +static int matches(const char* key, const char* value) +{ + return !strcmp(key, store.key) && + (store.value_regex == NULL || + (store.do_not_match ^ + !regexec(store.value_regex, value, 0, NULL, 0))); +} + +static int store_aux(const char* key, const char* value, void *cb) +{ + const char *ep; + size_t section_len; + + switch (store.state) { + case KEY_SEEN: + if (matches(key, value)) { + if (store.seen == 1 && store.multi_replace == 0) { + warning("%s has multiple values", key); + } else if (store.seen >= MAX_MATCHES) { + error("too many matches for %s", key); + return 1; + } + + store.offset[store.seen] = ftell(config_file); + store.seen++; + } + break; + case SECTION_SEEN: + /* + * What we are looking for is in store.key (both + * section and var), and its section part is baselen + * long. We found key (again, both section and var). + * We would want to know if this key is in the same + * section as what we are looking for. We already + * know we are in the same section as what should + * hold store.key. + */ + ep = strrchr(key, '.'); + section_len = ep - key; + + if ((section_len != store.baselen) || + memcmp(key, store.key, section_len+1)) { + store.state = SECTION_END_SEEN; + break; + } + + /* + * Do not increment matches: this is no match, but we + * just made sure we are in the desired section. + */ + store.offset[store.seen] = ftell(config_file); + /* fallthru */ + case SECTION_END_SEEN: + case START: + if (matches(key, value)) { + store.offset[store.seen] = ftell(config_file); + store.state = KEY_SEEN; + store.seen++; + } else { + if (strrchr(key, '.') - key == store.baselen && + !strncmp(key, store.key, store.baselen)) { + store.state = SECTION_SEEN; + store.offset[store.seen] = ftell(config_file); + } + } + } + return 0; +} + +static int write_error(const char *filename) +{ + error("failed to write new configuration file %s", filename); + + /* Same error code as "failed to rename". */ + return 4; +} + +static int store_write_section(int fd, const char* key) +{ + const char *dot; + int i, success; + struct strbuf sb; + + strbuf_init(&sb, 0); + dot = memchr(key, '.', store.baselen); + if (dot) { + strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key); + for (i = dot - key + 1; i < store.baselen; i++) { + if (key[i] == '"' || key[i] == '\\') + strbuf_addch(&sb, '\\'); + strbuf_addch(&sb, key[i]); + } + strbuf_addstr(&sb, "\"]\n"); + } else { + strbuf_addf(&sb, "[%.*s]\n", store.baselen, key); + } + + success = write_in_full(fd, sb.buf, sb.len) == sb.len; + strbuf_release(&sb); + + return success; +} + +static int store_write_pair(int fd, const char* key, const char* value) +{ + int i, success; + int length = strlen(key + store.baselen + 1); + const char *quote = ""; + struct strbuf sb; + + /* + * Check to see if the value needs to be surrounded with a dq pair. + * Note that problematic characters are always backslash-quoted; this + * check is about not losing leading or trailing SP and strings that + * follow beginning-of-comment characters (i.e. ';' and '#') by the + * configuration parser. + */ + if (value[0] == ' ') + quote = "\""; + for (i = 0; value[i]; i++) + if (value[i] == ';' || value[i] == '#') + quote = "\""; + if (i && value[i - 1] == ' ') + quote = "\""; + + strbuf_init(&sb, 0); + strbuf_addf(&sb, "\t%.*s = %s", + length, key + store.baselen + 1, quote); + + for (i = 0; value[i]; i++) + switch (value[i]) { + case '\n': + strbuf_addstr(&sb, "\\n"); + break; + case '\t': + strbuf_addstr(&sb, "\\t"); + break; + case '"': + case '\\': + strbuf_addch(&sb, '\\'); + default: + strbuf_addch(&sb, value[i]); + break; + } + strbuf_addf(&sb, "%s\n", quote); + + success = write_in_full(fd, sb.buf, sb.len) == sb.len; + strbuf_release(&sb); + + return success; +} + +static ssize_t find_beginning_of_line(const char* contents, size_t size, + size_t offset_, int* found_bracket) +{ + size_t equal_offset = size, bracket_offset = size; + ssize_t offset; + +contline: + for (offset = offset_-2; offset > 0 + && contents[offset] != '\n'; offset--) + switch (contents[offset]) { + case '=': equal_offset = offset; break; + case ']': bracket_offset = offset; break; + } + if (offset > 0 && contents[offset-1] == '\\') { + offset_ = offset; + goto contline; + } + if (bracket_offset < equal_offset) { + *found_bracket = 1; + offset = bracket_offset+1; + } else + offset++; + + return offset; +} + +int git_config_set(const char* key, const char* value) +{ + return git_config_set_multivar(key, value, NULL, 0); +} + +/* + * If value==NULL, unset in (remove from) config, + * if value_regex!=NULL, disregard key/value pairs where value does not match. + * if multi_replace==0, nothing, or only one matching key/value is replaced, + * else all matching key/values (regardless how many) are removed, + * before the new pair is written. + * + * Returns 0 on success. + * + * This function does this: + * + * - it locks the config file by creating ".git/config.lock" + * + * - it then parses the config using store_aux() as validator to find + * the position on the key/value pair to replace. If it is to be unset, + * it must be found exactly once. + * + * - the config file is mmap()ed and the part before the match (if any) is + * written to the lock file, then the changed part and the rest. + * + * - the config file is removed and the lock file rename()d to it. + * + */ +int git_config_set_multivar(const char* key, const char* value, + const char* value_regex, int multi_replace) +{ + int i, dot; + int fd = -1, in_fd; + int ret; + char* config_filename; + struct lock_file *lock = NULL; + const char* last_dot = strrchr(key, '.'); + + if (config_exclusive_filename) + config_filename = xstrdup(config_exclusive_filename); + else + config_filename = xstrdup(git_path("config")); + + /* + * Since "key" actually contains the section name and the real + * key name separated by a dot, we have to know where the dot is. + */ + + if (last_dot == NULL) { + error("key does not contain a section: %s", key); + ret = 2; + goto out_free; + } + store.baselen = last_dot - key; + + store.multi_replace = multi_replace; + + /* + * Validate the key and while at it, lower case it for matching. + */ + store.key = xmalloc(strlen(key) + 1); + dot = 0; + for (i = 0; key[i]; i++) { + unsigned char c = key[i]; + if (c == '.') + dot = 1; + /* Leave the extended basename untouched.. */ + if (!dot || i > store.baselen) { + if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) { + error("invalid key: %s", key); + free(store.key); + ret = 1; + goto out_free; + } + c = tolower(c); + } else if (c == '\n') { + error("invalid key (newline): %s", key); + free(store.key); + ret = 1; + goto out_free; + } + store.key[i] = c; + } + store.key[i] = 0; + + /* + * The lock serves a purpose in addition to locking: the new + * contents of .git/config will be written into it. + */ + lock = xcalloc(sizeof(struct lock_file), 1); + fd = hold_lock_file_for_update(lock, config_filename, 0); + if (fd < 0) { + error("could not lock config file %s", config_filename); + free(store.key); + ret = -1; + goto out_free; + } + + /* + * If .git/config does not exist yet, write a minimal version. + */ + in_fd = open(config_filename, O_RDONLY); + if ( in_fd < 0 ) { + free(store.key); + + if ( ENOENT != errno ) { + error("opening %s: %s", config_filename, + strerror(errno)); + ret = 3; /* same as "invalid config file" */ + goto out_free; + } + /* if nothing to unset, error out */ + if (value == NULL) { + ret = 5; + goto out_free; + } + + store.key = (char*)key; + if (!store_write_section(fd, key) || + !store_write_pair(fd, key, value)) + goto write_err_out; + } else { + struct stat st; + char* contents; + size_t contents_sz, copy_begin, copy_end; + int i, new_line = 0; + + if (value_regex == NULL) + store.value_regex = NULL; + else { + if (value_regex[0] == '!') { + store.do_not_match = 1; + value_regex++; + } else + store.do_not_match = 0; + + store.value_regex = (regex_t*)xmalloc(sizeof(regex_t)); + if (regcomp(store.value_regex, value_regex, + REG_EXTENDED)) { + error("invalid pattern: %s", value_regex); + free(store.value_regex); + ret = 6; + goto out_free; + } + } + + store.offset[0] = 0; + store.state = START; + store.seen = 0; + + /* + * After this, store.offset will contain the *end* offset + * of the last match, or remain at 0 if no match was found. + * As a side effect, we make sure to transform only a valid + * existing config file. + */ + if (git_config_from_file(store_aux, config_filename, NULL)) { + error("invalid config file %s", config_filename); + free(store.key); + if (store.value_regex != NULL) { + regfree(store.value_regex); + free(store.value_regex); + } + ret = 3; + goto out_free; + } + + free(store.key); + if (store.value_regex != NULL) { + regfree(store.value_regex); + free(store.value_regex); + } + + /* if nothing to unset, or too many matches, error out */ + if ((store.seen == 0 && value == NULL) || + (store.seen > 1 && multi_replace == 0)) { + ret = 5; + goto out_free; + } + + fstat(in_fd, &st); + contents_sz = xsize_t(st.st_size); + contents = xmmap(NULL, contents_sz, PROT_READ, + MAP_PRIVATE, in_fd, 0); + close(in_fd); + + if (store.seen == 0) + store.seen = 1; + + for (i = 0, copy_begin = 0; i < store.seen; i++) { + if (store.offset[i] == 0) { + store.offset[i] = copy_end = contents_sz; + } else if (store.state != KEY_SEEN) { + copy_end = store.offset[i]; + } else + copy_end = find_beginning_of_line( + contents, contents_sz, + store.offset[i]-2, &new_line); + + if (copy_end > 0 && contents[copy_end-1] != '\n') + new_line = 1; + + /* write the first part of the config */ + if (copy_end > copy_begin) { + if (write_in_full(fd, contents + copy_begin, + copy_end - copy_begin) < + copy_end - copy_begin) + goto write_err_out; + if (new_line && + write_in_full(fd, "\n", 1) != 1) + goto write_err_out; + } + copy_begin = store.offset[i]; + } + + /* write the pair (value == NULL means unset) */ + if (value != NULL) { + if (store.state == START) { + if (!store_write_section(fd, key)) + goto write_err_out; + } + if (!store_write_pair(fd, key, value)) + goto write_err_out; + } + + /* write the rest of the config */ + if (copy_begin < contents_sz) + if (write_in_full(fd, contents + copy_begin, + contents_sz - copy_begin) < + contents_sz - copy_begin) + goto write_err_out; + + munmap(contents, contents_sz); + } + + if (commit_lock_file(lock) < 0) { + error("could not commit config file %s", config_filename); + ret = 4; + goto out_free; + } + + /* + * lock is committed, so don't try to roll it back below. + * NOTE: Since lockfile.c keeps a linked list of all created + * lock_file structures, it isn't safe to free(lock). It's + * better to just leave it hanging around. + */ + lock = NULL; + ret = 0; + +out_free: + if (lock) + rollback_lock_file(lock); + free(config_filename); + return ret; + +write_err_out: + ret = write_error(lock->filename); + goto out_free; + +} + +static int section_name_match (const char *buf, const char *name) +{ + int i = 0, j = 0, dot = 0; + for (; buf[i] && buf[i] != ']'; i++) { + if (!dot && isspace(buf[i])) { + dot = 1; + if (name[j++] != '.') + break; + for (i++; isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] != '"') + break; + continue; + } + if (buf[i] == '\\' && dot) + i++; + else if (buf[i] == '"' && dot) { + for (i++; isspace(buf[i]); i++) + ; /* do_nothing */ + break; + } + if (buf[i] != name[j++]) + break; + } + return (buf[i] == ']' && name[j] == 0); +} + +/* if new_name == NULL, the section is removed instead */ +int git_config_rename_section(const char *old_name, const char *new_name) +{ + int ret = 0, remove = 0; + char *config_filename; + struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); + int out_fd; + char buf[1024]; + + if (config_exclusive_filename) + config_filename = xstrdup(config_exclusive_filename); + else + config_filename = xstrdup(git_path("config")); + out_fd = hold_lock_file_for_update(lock, config_filename, 0); + if (out_fd < 0) { + ret = error("could not lock config file %s", config_filename); + goto out; + } + + if (!(config_file = fopen(config_filename, "rb"))) { + /* no config file means nothing to rename, no error */ + goto unlock_and_out; + } + + while (fgets(buf, sizeof(buf), config_file)) { + int i; + int length; + for (i = 0; buf[i] && isspace(buf[i]); i++) + ; /* do nothing */ + if (buf[i] == '[') { + /* it's a section */ + if (section_name_match (&buf[i+1], old_name)) { + ret++; + if (new_name == NULL) { + remove = 1; + continue; + } + store.baselen = strlen(new_name); + if (!store_write_section(out_fd, new_name)) { + ret = write_error(lock->filename); + goto out; + } + continue; + } + remove = 0; + } + if (remove) + continue; + length = strlen(buf); + if (write_in_full(out_fd, buf, length) != length) { + ret = write_error(lock->filename); + goto out; + } + } + fclose(config_file); + unlock_and_out: + if (commit_lock_file(lock) < 0) + ret = error("could not commit config file %s", config_filename); + out: + free(config_filename); + return ret; +} + +/* + * Call this to report error for your variable that should not + * get a boolean value (i.e. "[my] var" means "true"). + */ +int config_error_nonbool(const char *var) +{ + return error("Missing value for '%s'", var); +} diff --git a/exec_cmd.c b/exec_cmd.c deleted file mode 100644 index ce6741e..0000000 --- a/exec_cmd.c +++ /dev/null @@ -1,132 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" -#include "quote.h" -#define MAX_ARGS 32 - -extern char **environ; -static const char *argv_exec_path; -static const char *argv0_path; - -const char *system_path(const char *path) -{ - if (!is_absolute_path(path) && argv0_path) { - struct strbuf d = STRBUF_INIT; - strbuf_addf(&d, "%s/%s", argv0_path, path); - path = strbuf_detach(&d, NULL); - } - return path; -} - -void git_set_argv0_path(const char *path) -{ - argv0_path = path; -} - -void git_set_argv_exec_path(const char *exec_path) -{ - argv_exec_path = exec_path; -} - - -/* Returns the highest-priority, location to look for git programs. */ -const char *git_exec_path(void) -{ - const char *env; - - if (argv_exec_path) - return argv_exec_path; - - env = getenv(EXEC_PATH_ENVIRONMENT); - if (env && *env) { - return env; - } - - return system_path(GIT_EXEC_PATH); -} - -static void add_path(struct strbuf *out, const char *path) -{ - if (path && *path) { - if (is_absolute_path(path)) - strbuf_addstr(out, path); - else - strbuf_addstr(out, make_nonrelative_path(path)); - - strbuf_addch(out, PATH_SEP); - } -} - -void setup_path(void) -{ - const char *old_path = getenv("PATH"); - struct strbuf new_path; - - strbuf_init(&new_path, 0); - - add_path(&new_path, argv_exec_path); - add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); - add_path(&new_path, system_path(GIT_EXEC_PATH)); - add_path(&new_path, argv0_path); - - if (old_path) - strbuf_addstr(&new_path, old_path); - else - strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); - - setenv("PATH", new_path.buf, 1); - - strbuf_release(&new_path); -} - -const char **prepare_git_cmd(const char **argv) -{ - int argc; - const char **nargv; - - for (argc = 0; argv[argc]; argc++) - ; /* just counting */ - nargv = xmalloc(sizeof(*nargv) * (argc + 2)); - - nargv[0] = "git"; - for (argc = 0; argv[argc]; argc++) - nargv[argc + 1] = argv[argc]; - nargv[argc + 1] = NULL; - return nargv; -} - -int execv_git_cmd(const char **argv) { - const char **nargv = prepare_git_cmd(argv); - trace_argv_printf(nargv, "trace: exec:"); - - /* execvp() can only ever return if it fails */ - execvp("git", (char **)nargv); - - trace_printf("trace: exec failed: %s\n", strerror(errno)); - - free(nargv); - return -1; -} - - -int execl_git_cmd(const char *cmd,...) -{ - int argc; - const char *argv[MAX_ARGS + 1]; - const char *arg; - va_list param; - - va_start(param, cmd); - argv[0] = cmd; - argc = 1; - while (argc < MAX_ARGS) { - arg = argv[argc++] = va_arg(param, char *); - if (!arg) - break; - } - va_end(param); - if (MAX_ARGS <= argc) - return error("too many args to run %s", cmd); - - argv[argc] = NULL; - return execv_git_cmd(argv); -} diff --git a/exec_cmd.c.cmake b/exec_cmd.c.cmake new file mode 100644 index 0000000..f06ec3d --- /dev/null +++ b/exec_cmd.c.cmake @@ -0,0 +1,132 @@ +#include "cache.h" +#include "exec_cmd.h" +#include "quote.h" +#define MAX_ARGS 32 + +extern char **environ; +static const char *argv_exec_path; +static const char *argv0_path; + +const char *system_path(const char *path) +{ + if (!is_absolute_path(path) && argv0_path) { + struct strbuf d = STRBUF_INIT; + strbuf_addf(&d, "%s/%s", argv0_path, path); + path = strbuf_detach(&d, NULL); + } + return path; +} + +void git_set_argv0_path(const char *path) +{ + argv0_path = path; +} + +void git_set_argv_exec_path(const char *exec_path) +{ + argv_exec_path = exec_path; +} + + +/* Returns the highest-priority, location to look for git programs. */ +const char *git_exec_path(void) +{ + const char *env; + + if (argv_exec_path) + return argv_exec_path; + + env = getenv(EXEC_PATH_ENVIRONMENT); + if (env && *env) { + return env; + } + + return system_path("@GIT_EXEC_PATH@"); +} + +static void add_path(struct strbuf *out, const char *path) +{ + if (path && *path) { + if (is_absolute_path(path)) + strbuf_addstr(out, path); + else + strbuf_addstr(out, make_nonrelative_path(path)); + + strbuf_addch(out, PATH_SEP); + } +} + +void setup_path(void) +{ + const char *old_path = getenv("PATH"); + struct strbuf new_path; + + strbuf_init(&new_path, 0); + + add_path(&new_path, argv_exec_path); + add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); + add_path(&new_path, system_path("@GIT_EXEC_PATH@")); + add_path(&new_path, argv0_path); + + if (old_path) + strbuf_addstr(&new_path, old_path); + else + strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + + setenv("PATH", new_path.buf, 1); + + strbuf_release(&new_path); +} + +const char **prepare_git_cmd(const char **argv) +{ + int argc; + const char **nargv; + + for (argc = 0; argv[argc]; argc++) + ; /* just counting */ + nargv = xmalloc(sizeof(*nargv) * (argc + 2)); + + nargv[0] = "git"; + for (argc = 0; argv[argc]; argc++) + nargv[argc + 1] = argv[argc]; + nargv[argc + 1] = NULL; + return nargv; +} + +int execv_git_cmd(const char **argv) { + const char **nargv = prepare_git_cmd(argv); + trace_argv_printf(nargv, "trace: exec:"); + + /* execvp() can only ever return if it fails */ + execvp("git", (char **)nargv); + + trace_printf("trace: exec failed: %s\n", strerror(errno)); + + free(nargv); + return -1; +} + + +int execl_git_cmd(const char *cmd,...) +{ + int argc; + const char *argv[MAX_ARGS + 1]; + const char *arg; + va_list param; + + va_start(param, cmd); + argv[0] = cmd; + argc = 1; + while (argc < MAX_ARGS) { + arg = argv[argc++] = va_arg(param, char *); + if (!arg) + break; + } + va_end(param); + if (MAX_ARGS <= argc) + return error("too many args to run %s", cmd); + + argv[argc] = NULL; + return execv_git_cmd(argv); +} diff --git a/git.c b/git.c deleted file mode 100644 index 905acc2..0000000 --- a/git.c +++ /dev/null @@ -1,511 +0,0 @@ -#include "builtin.h" -#include "exec_cmd.h" -#include "cache.h" -#include "quote.h" - -const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; - -const char git_more_info_string[] = - "See 'git help COMMAND' for more information on a specific command."; - -static int use_pager = -1; -struct pager_config { - const char *cmd; - int val; -}; - -static int pager_command_config(const char *var, const char *value, void *data) -{ - struct pager_config *c = data; - if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) - c->val = git_config_bool(var, value); - return 0; -} - -/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ -int check_pager_config(const char *cmd) -{ - struct pager_config c; - c.cmd = cmd; - c.val = -1; - git_config(pager_command_config, &c); - return c.val; -} - -static void commit_pager_choice(void) { - switch (use_pager) { - case 0: - setenv("GIT_PAGER", "cat", 1); - break; - case 1: - setup_pager(); - break; - default: - break; - } -} - -static int handle_options(const char*** argv, int* argc, int* envchanged) -{ - int handled = 0; - - while (*argc > 0) { - const char *cmd = (*argv)[0]; - if (cmd[0] != '-') - break; - - /* - * For legacy reasons, the "version" and "help" - * commands can be written with "--" prepended - * to make them look like flags. - */ - if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version")) - break; - - /* - * Check remaining flags. - */ - if (!prefixcmp(cmd, "--exec-path")) { - cmd += 11; - if (*cmd == '=') - git_set_argv_exec_path(cmd + 1); - else { - puts(git_exec_path()); - exit(0); - } - } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { - use_pager = 1; - } else if (!strcmp(cmd, "--no-pager")) { - use_pager = 0; - if (envchanged) - *envchanged = 1; - } else if (!strcmp(cmd, "--git-dir")) { - if (*argc < 2) { - fprintf(stderr, "No directory given for --git-dir.\n" ); - usage(git_usage_string); - } - setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1); - if (envchanged) - *envchanged = 1; - (*argv)++; - (*argc)--; - handled++; - } else if (!prefixcmp(cmd, "--git-dir=")) { - setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); - if (envchanged) - *envchanged = 1; - } else if (!strcmp(cmd, "--work-tree")) { - if (*argc < 2) { - fprintf(stderr, "No directory given for --work-tree.\n" ); - usage(git_usage_string); - } - setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1); - if (envchanged) - *envchanged = 1; - (*argv)++; - (*argc)--; - } else if (!prefixcmp(cmd, "--work-tree=")) { - setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1); - if (envchanged) - *envchanged = 1; - } else if (!strcmp(cmd, "--bare")) { - static char git_dir[PATH_MAX+1]; - is_bare_repository_cfg = 1; - setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0); - if (envchanged) - *envchanged = 1; - } else { - fprintf(stderr, "Unknown option: %s\n", cmd); - usage(git_usage_string); - } - - (*argv)++; - (*argc)--; - handled++; - } - return handled; -} - -static int handle_alias(int *argcp, const char ***argv) -{ - int envchanged = 0, ret = 0, saved_errno = errno; - const char *subdir; - int count, option_count; - const char** new_argv; - const char *alias_command; - char *alias_string; - int unused_nongit; - - subdir = setup_git_directory_gently(&unused_nongit); - - alias_command = (*argv)[0]; - alias_string = alias_lookup(alias_command); - if (alias_string) { - if (alias_string[0] == '!') { - if (*argcp > 1) { - struct strbuf buf; - - strbuf_init(&buf, PATH_MAX); - strbuf_addstr(&buf, alias_string); - sq_quote_argv(&buf, (*argv) + 1, PATH_MAX); - free(alias_string); - alias_string = buf.buf; - } - trace_printf("trace: alias to shell cmd: %s => %s\n", - alias_command, alias_string + 1); - ret = system(alias_string + 1); - if (ret >= 0 && WIFEXITED(ret) && - WEXITSTATUS(ret) != 127) - exit(WEXITSTATUS(ret)); - die("Failed to run '%s' when expanding alias '%s'\n", - alias_string + 1, alias_command); - } - count = split_cmdline(alias_string, &new_argv); - option_count = handle_options(&new_argv, &count, &envchanged); - if (envchanged) - die("alias '%s' changes environment variables\n" - "You can use '!git' in the alias to do this.", - alias_command); - memmove(new_argv - option_count, new_argv, - count * sizeof(char *)); - new_argv -= option_count; - - if (count < 1) - die("empty alias for %s", alias_command); - - if (!strcmp(alias_command, new_argv[0])) - die("recursive alias: %s", alias_command); - - trace_argv_printf(new_argv, - "trace: alias expansion: %s =>", - alias_command); - - new_argv = xrealloc(new_argv, sizeof(char*) * - (count + *argcp + 1)); - /* insert after command name */ - memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); - new_argv[count+*argcp] = NULL; - - *argv = new_argv; - *argcp += count - 1; - - ret = 1; - } - - if (subdir) - chdir(subdir); - - errno = saved_errno; - - return ret; -} - -const char git_version_string[] = GIT_VERSION; - -#define RUN_SETUP (1<<0) -#define USE_PAGER (1<<1) -/* - * require working tree to be present -- anything uses this needs - * RUN_SETUP for reading from the configuration file. - */ -#define NEED_WORK_TREE (1<<2) - -struct cmd_struct { - const char *cmd; - int (*fn)(int, const char **, const char *); - int option; -}; - -static int run_command(struct cmd_struct *p, int argc, const char **argv) -{ - int status; - struct stat st; - const char *prefix; - - prefix = NULL; - if (p->option & RUN_SETUP) - prefix = setup_git_directory(); - - if (use_pager == -1 && p->option & RUN_SETUP) - use_pager = check_pager_config(p->cmd); - if (use_pager == -1 && p->option & USE_PAGER) - use_pager = 1; - commit_pager_choice(); - - if (p->option & NEED_WORK_TREE) - setup_work_tree(); - - trace_argv_printf(argv, "trace: built-in: git"); - - status = p->fn(argc, argv, prefix); - if (status) - return status & 0xff; - - /* Somebody closed stdout? */ - if (fstat(fileno(stdout), &st)) - return 0; - /* Ignore write errors for pipes and sockets.. */ - if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) - return 0; - - /* Check for ENOSPC and EIO errors.. */ - if (fflush(stdout)) - die("write failure on standard output: %s", strerror(errno)); - if (ferror(stdout)) - die("unknown write failure on standard output"); - if (fclose(stdout)) - die("close failed on standard output: %s", strerror(errno)); - return 0; -} - -static void handle_internal_command(int argc, const char **argv) -{ - const char *cmd = argv[0]; - static struct cmd_struct commands[] = { - { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - { "annotate", cmd_annotate, RUN_SETUP }, - { "apply", cmd_apply }, - { "archive", cmd_archive }, - { "blame", cmd_blame, RUN_SETUP }, - { "branch", cmd_branch, RUN_SETUP }, - { "bundle", cmd_bundle }, - { "cat-file", cmd_cat_file, RUN_SETUP }, - { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, - { "checkout-index", cmd_checkout_index, - RUN_SETUP | NEED_WORK_TREE}, - { "check-ref-format", cmd_check_ref_format }, - { "check-attr", cmd_check_attr, RUN_SETUP }, - { "cherry", cmd_cherry, RUN_SETUP }, - { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, - { "clone", cmd_clone }, - { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, - { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, - { "commit-tree", cmd_commit_tree, RUN_SETUP }, - { "config", cmd_config }, - { "count-objects", cmd_count_objects, RUN_SETUP }, - { "describe", cmd_describe, RUN_SETUP }, - { "diff", cmd_diff }, - { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, - { "diff-index", cmd_diff_index, RUN_SETUP }, - { "diff-tree", cmd_diff_tree, RUN_SETUP }, - { "fast-export", cmd_fast_export, RUN_SETUP }, - { "fetch", cmd_fetch, RUN_SETUP }, - { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, - { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, - { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, - { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, - { "format-patch", cmd_format_patch, RUN_SETUP }, - { "fsck", cmd_fsck, RUN_SETUP }, - { "fsck-objects", cmd_fsck, RUN_SETUP }, - { "gc", cmd_gc, RUN_SETUP }, - { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, - { "help", cmd_help }, -#ifndef NO_CURL - { "http-fetch", cmd_http_fetch, RUN_SETUP }, -#endif - { "init", cmd_init_db }, - { "init-db", cmd_init_db }, - { "log", cmd_log, RUN_SETUP | USE_PAGER }, - { "ls-files", cmd_ls_files, RUN_SETUP }, - { "ls-tree", cmd_ls_tree, RUN_SETUP }, - { "ls-remote", cmd_ls_remote }, - { "mailinfo", cmd_mailinfo }, - { "mailsplit", cmd_mailsplit }, - { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, - { "merge-base", cmd_merge_base, RUN_SETUP }, - { "merge-file", cmd_merge_file }, - { "merge-ours", cmd_merge_ours, RUN_SETUP }, - { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, - { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, - { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, - { "name-rev", cmd_name_rev, RUN_SETUP }, - { "pack-objects", cmd_pack_objects, RUN_SETUP }, - { "peek-remote", cmd_ls_remote }, - { "pickaxe", cmd_blame, RUN_SETUP }, - { "prune", cmd_prune, RUN_SETUP }, - { "prune-packed", cmd_prune_packed, RUN_SETUP }, - { "push", cmd_push, RUN_SETUP }, - { "read-tree", cmd_read_tree, RUN_SETUP }, - { "reflog", cmd_reflog, RUN_SETUP }, - { "remote", cmd_remote, RUN_SETUP }, - { "repo-config", cmd_config }, - { "rerere", cmd_rerere, RUN_SETUP }, - { "reset", cmd_reset, RUN_SETUP }, - { "rev-list", cmd_rev_list, RUN_SETUP }, - { "rev-parse", cmd_rev_parse }, - { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, - { "rm", cmd_rm, RUN_SETUP }, - { "send-pack", cmd_send_pack, RUN_SETUP }, - { "shortlog", cmd_shortlog, USE_PAGER }, - { "show-branch", cmd_show_branch, RUN_SETUP }, - { "show", cmd_show, RUN_SETUP | USE_PAGER }, - { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, - { "stripspace", cmd_stripspace }, - { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, - { "tag", cmd_tag, RUN_SETUP }, - { "tar-tree", cmd_tar_tree }, - { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, - { "update-index", cmd_update_index, RUN_SETUP }, - { "update-ref", cmd_update_ref, RUN_SETUP }, - { "upload-archive", cmd_upload_archive }, - { "verify-tag", cmd_verify_tag, RUN_SETUP }, - { "version", cmd_version }, - { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, - { "write-tree", cmd_write_tree, RUN_SETUP }, - { "verify-pack", cmd_verify_pack }, - { "show-ref", cmd_show_ref, RUN_SETUP }, - { "pack-refs", cmd_pack_refs, RUN_SETUP }, - }; - int i; - static const char ext[] = STRIP_EXTENSION; - - if (sizeof(ext) > 1) { - i = strlen(argv[0]) - strlen(ext); - if (i > 0 && !strcmp(argv[0] + i, ext)) { - char *argv0 = xstrdup(argv[0]); - argv[0] = cmd = argv0; - argv0[i] = '\0'; - } - } - - /* Turn "git cmd --help" into "git help cmd" */ - if (argc > 1 && !strcmp(argv[1], "--help")) { - argv[1] = argv[0]; - argv[0] = cmd = "help"; - } - - for (i = 0; i < ARRAY_SIZE(commands); i++) { - struct cmd_struct *p = commands+i; - if (strcmp(p->cmd, cmd)) - continue; - exit(run_command(p, argc, argv)); - } -} - -static void execv_dashed_external(const char **argv) -{ - struct strbuf cmd; - const char *tmp; - - strbuf_init(&cmd, 0); - strbuf_addf(&cmd, "git-%s", argv[0]); - - /* - * argv[0] must be the git command, but the argv array - * belongs to the caller, and may be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ - tmp = argv[0]; - argv[0] = cmd.buf; - - trace_argv_printf(argv, "trace: exec:"); - - /* execvp() can only ever return if it fails */ - execvp(cmd.buf, (char **)argv); - - trace_printf("trace: exec failed: %s\n", strerror(errno)); - - argv[0] = tmp; - - strbuf_release(&cmd); -} - - -int main(int argc, const char **argv) -{ - const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help"; - char *slash = (char *)cmd + strlen(cmd); - int done_alias = 0; - - /* - * Take the basename of argv[0] as the command - * name, and the dirname as the default exec_path - * if we don't have anything better. - */ - do - --slash; - while (cmd <= slash && !is_dir_sep(*slash)); - if (cmd <= slash) { - *slash++ = 0; - git_set_argv0_path(cmd); - cmd = slash; - } - - /* - * "git-xxxx" is the same as "git xxxx", but we obviously: - * - * - cannot take flags in between the "git" and the "xxxx". - * - cannot execute it externally (since it would just do - * the same thing over again) - * - * So we just directly call the internal command handler, and - * die if that one cannot handle it. - */ - if (!prefixcmp(cmd, "git-")) { - cmd += 4; - argv[0] = cmd; - handle_internal_command(argc, argv); - die("cannot handle %s internally", cmd); - } - - /* Look for flags.. */ - argv++; - argc--; - handle_options(&argv, &argc, NULL); - commit_pager_choice(); - if (argc > 0) { - if (!prefixcmp(argv[0], "--")) - argv[0] += 2; - } else { - /* The user didn't specify a command; give them help */ - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); - exit(1); - } - cmd = argv[0]; - - /* - * We use PATH to find git commands, but we prepend some higher - * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH - * environment, and the $(gitexecdir) from the Makefile at build - * time. - */ - setup_path(); - - while (1) { - /* See if it's an internal command */ - handle_internal_command(argc, argv); - - /* .. then try the external ones */ - execv_dashed_external(argv); - - /* It could be an alias -- this works around the insanity - * of overriding "git log" with "git show" by having - * alias.log = show - */ - if (done_alias || !handle_alias(&argc, &argv)) - break; - done_alias = 1; - } - - if (errno == ENOENT) { - if (done_alias) { - fprintf(stderr, "Expansion of alias '%s' failed; " - "'%s' is not a git-command\n", - cmd, argv[0]); - exit(1); - } - argv[0] = help_unknown_cmd(cmd); - handle_internal_command(argc, argv); - execv_dashed_external(argv); - } - - fprintf(stderr, "Failed to run command '%s': %s\n", - cmd, strerror(errno)); - - return 1; -} diff --git a/git.c.cmake b/git.c.cmake new file mode 100644 index 0000000..bfdc9fa --- /dev/null +++ b/git.c.cmake @@ -0,0 +1,511 @@ +#include "builtin.h" +#include "exec_cmd.h" +#include "cache.h" +#include "quote.h" + +const char git_usage_string[] = + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; + +const char git_more_info_string[] = + "See 'git help COMMAND' for more information on a specific command."; + +static int use_pager = -1; +struct pager_config { + const char *cmd; + int val; +}; + +static int pager_command_config(const char *var, const char *value, void *data) +{ + struct pager_config *c = data; + if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) + c->val = git_config_bool(var, value); + return 0; +} + +/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ +int check_pager_config(const char *cmd) +{ + struct pager_config c; + c.cmd = cmd; + c.val = -1; + git_config(pager_command_config, &c); + return c.val; +} + +static void commit_pager_choice(void) { + switch (use_pager) { + case 0: + setenv("GIT_PAGER", "cat", 1); + break; + case 1: + setup_pager(); + break; + default: + break; + } +} + +static int handle_options(const char*** argv, int* argc, int* envchanged) +{ + int handled = 0; + + while (*argc > 0) { + const char *cmd = (*argv)[0]; + if (cmd[0] != '-') + break; + + /* + * For legacy reasons, the "version" and "help" + * commands can be written with "--" prepended + * to make them look like flags. + */ + if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version")) + break; + + /* + * Check remaining flags. + */ + if (!prefixcmp(cmd, "--exec-path")) { + cmd += 11; + if (*cmd == '=') + git_set_argv_exec_path(cmd + 1); + else { + puts(git_exec_path()); + exit(0); + } + } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { + use_pager = 1; + } else if (!strcmp(cmd, "--no-pager")) { + use_pager = 0; + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--git-dir")) { + if (*argc < 2) { + fprintf(stderr, "No directory given for --git-dir.\n" ); + usage(git_usage_string); + } + setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; + (*argv)++; + (*argc)--; + handled++; + } else if (!prefixcmp(cmd, "--git-dir=")) { + setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--work-tree")) { + if (*argc < 2) { + fprintf(stderr, "No directory given for --work-tree.\n" ); + usage(git_usage_string); + } + setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1); + if (envchanged) + *envchanged = 1; + (*argv)++; + (*argc)--; + } else if (!prefixcmp(cmd, "--work-tree=")) { + setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1); + if (envchanged) + *envchanged = 1; + } else if (!strcmp(cmd, "--bare")) { + static char git_dir[PATH_MAX+1]; + is_bare_repository_cfg = 1; + setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0); + if (envchanged) + *envchanged = 1; + } else { + fprintf(stderr, "Unknown option: %s\n", cmd); + usage(git_usage_string); + } + + (*argv)++; + (*argc)--; + handled++; + } + return handled; +} + +static int handle_alias(int *argcp, const char ***argv) +{ + int envchanged = 0, ret = 0, saved_errno = errno; + const char *subdir; + int count, option_count; + const char** new_argv; + const char *alias_command; + char *alias_string; + int unused_nongit; + + subdir = setup_git_directory_gently(&unused_nongit); + + alias_command = (*argv)[0]; + alias_string = alias_lookup(alias_command); + if (alias_string) { + if (alias_string[0] == '!') { + if (*argcp > 1) { + struct strbuf buf; + + strbuf_init(&buf, PATH_MAX); + strbuf_addstr(&buf, alias_string); + sq_quote_argv(&buf, (*argv) + 1, PATH_MAX); + free(alias_string); + alias_string = buf.buf; + } + trace_printf("trace: alias to shell cmd: %s => %s\n", + alias_command, alias_string + 1); + ret = system(alias_string + 1); + if (ret >= 0 && WIFEXITED(ret) && + WEXITSTATUS(ret) != 127) + exit(WEXITSTATUS(ret)); + die("Failed to run '%s' when expanding alias '%s'\n", + alias_string + 1, alias_command); + } + count = split_cmdline(alias_string, &new_argv); + option_count = handle_options(&new_argv, &count, &envchanged); + if (envchanged) + die("alias '%s' changes environment variables\n" + "You can use '!git' in the alias to do this.", + alias_command); + memmove(new_argv - option_count, new_argv, + count * sizeof(char *)); + new_argv -= option_count; + + if (count < 1) + die("empty alias for %s", alias_command); + + if (!strcmp(alias_command, new_argv[0])) + die("recursive alias: %s", alias_command); + + trace_argv_printf(new_argv, + "trace: alias expansion: %s =>", + alias_command); + + new_argv = xrealloc(new_argv, sizeof(char*) * + (count + *argcp + 1)); + /* insert after command name */ + memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); + new_argv[count+*argcp] = NULL; + + *argv = new_argv; + *argcp += count - 1; + + ret = 1; + } + + if (subdir) + chdir(subdir); + + errno = saved_errno; + + return ret; +} + +const char git_version_string[] = "@GIT_VERSION@"; + +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) +/* + * require working tree to be present -- anything uses this needs + * RUN_SETUP for reading from the configuration file. + */ +#define NEED_WORK_TREE (1<<2) + +struct cmd_struct { + const char *cmd; + int (*fn)(int, const char **, const char *); + int option; +}; + +static int run_command(struct cmd_struct *p, int argc, const char **argv) +{ + int status; + struct stat st; + const char *prefix; + + prefix = NULL; + if (p->option & RUN_SETUP) + prefix = setup_git_directory(); + + if (use_pager == -1 && p->option & RUN_SETUP) + use_pager = check_pager_config(p->cmd); + if (use_pager == -1 && p->option & USE_PAGER) + use_pager = 1; + commit_pager_choice(); + + if (p->option & NEED_WORK_TREE) + setup_work_tree(); + + trace_argv_printf(argv, "trace: built-in: git"); + + status = p->fn(argc, argv, prefix); + if (status) + return status & 0xff; + + /* Somebody closed stdout? */ + if (fstat(fileno(stdout), &st)) + return 0; + /* Ignore write errors for pipes and sockets.. */ + if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) + return 0; + + /* Check for ENOSPC and EIO errors.. */ + if (fflush(stdout)) + die("write failure on standard output: %s", strerror(errno)); + if (ferror(stdout)) + die("unknown write failure on standard output"); + if (fclose(stdout)) + die("close failed on standard output: %s", strerror(errno)); + return 0; +} + +static void handle_internal_command(int argc, const char **argv) +{ + const char *cmd = argv[0]; + static struct cmd_struct commands[] = { + { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE }, + { "annotate", cmd_annotate, RUN_SETUP }, + { "apply", cmd_apply }, + { "archive", cmd_archive }, + { "blame", cmd_blame, RUN_SETUP }, + { "branch", cmd_branch, RUN_SETUP }, + { "bundle", cmd_bundle }, + { "cat-file", cmd_cat_file, RUN_SETUP }, + { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE }, + { "checkout-index", cmd_checkout_index, + RUN_SETUP | NEED_WORK_TREE}, + { "check-ref-format", cmd_check_ref_format }, + { "check-attr", cmd_check_attr, RUN_SETUP }, + { "cherry", cmd_cherry, RUN_SETUP }, + { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, + { "clone", cmd_clone }, + { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE }, + { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE }, + { "commit-tree", cmd_commit_tree, RUN_SETUP }, + { "config", cmd_config }, + { "count-objects", cmd_count_objects, RUN_SETUP }, + { "describe", cmd_describe, RUN_SETUP }, + { "diff", cmd_diff }, + { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, + { "diff-index", cmd_diff_index, RUN_SETUP }, + { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fast-export", cmd_fast_export, RUN_SETUP }, + { "fetch", cmd_fetch, RUN_SETUP }, + { "fetch-pack", cmd_fetch_pack, RUN_SETUP }, + { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, + { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, + { "format-patch", cmd_format_patch, RUN_SETUP }, + { "fsck", cmd_fsck, RUN_SETUP }, + { "fsck-objects", cmd_fsck, RUN_SETUP }, + { "gc", cmd_gc, RUN_SETUP }, + { "get-tar-commit-id", cmd_get_tar_commit_id }, + { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, + { "help", cmd_help }, +#ifndef NO_CURL + { "http-fetch", cmd_http_fetch, RUN_SETUP }, +#endif + { "init", cmd_init_db }, + { "init-db", cmd_init_db }, + { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, + { "ls-remote", cmd_ls_remote }, + { "mailinfo", cmd_mailinfo }, + { "mailsplit", cmd_mailsplit }, + { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, + { "merge-base", cmd_merge_base, RUN_SETUP }, + { "merge-file", cmd_merge_file }, + { "merge-ours", cmd_merge_ours, RUN_SETUP }, + { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, + { "name-rev", cmd_name_rev, RUN_SETUP }, + { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "peek-remote", cmd_ls_remote }, + { "pickaxe", cmd_blame, RUN_SETUP }, + { "prune", cmd_prune, RUN_SETUP }, + { "prune-packed", cmd_prune_packed, RUN_SETUP }, + { "push", cmd_push, RUN_SETUP }, + { "read-tree", cmd_read_tree, RUN_SETUP }, + { "reflog", cmd_reflog, RUN_SETUP }, + { "remote", cmd_remote, RUN_SETUP }, + { "repo-config", cmd_config }, + { "rerere", cmd_rerere, RUN_SETUP }, + { "reset", cmd_reset, RUN_SETUP }, + { "rev-list", cmd_rev_list, RUN_SETUP }, + { "rev-parse", cmd_rev_parse }, + { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, + { "rm", cmd_rm, RUN_SETUP }, + { "send-pack", cmd_send_pack, RUN_SETUP }, + { "shortlog", cmd_shortlog, USE_PAGER }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, + { "stripspace", cmd_stripspace }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tag", cmd_tag, RUN_SETUP }, + { "tar-tree", cmd_tar_tree }, + { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, + { "update-index", cmd_update_index, RUN_SETUP }, + { "update-ref", cmd_update_ref, RUN_SETUP }, + { "upload-archive", cmd_upload_archive }, + { "verify-tag", cmd_verify_tag, RUN_SETUP }, + { "version", cmd_version }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "write-tree", cmd_write_tree, RUN_SETUP }, + { "verify-pack", cmd_verify_pack }, + { "show-ref", cmd_show_ref, RUN_SETUP }, + { "pack-refs", cmd_pack_refs, RUN_SETUP }, + }; + int i; + static const char ext[] = STRIP_EXTENSION; + + if (sizeof(ext) > 1) { + i = strlen(argv[0]) - strlen(ext); + if (i > 0 && !strcmp(argv[0] + i, ext)) { + char *argv0 = xstrdup(argv[0]); + argv[0] = cmd = argv0; + argv0[i] = '\0'; + } + } + + /* Turn "git cmd --help" into "git help cmd" */ + if (argc > 1 && !strcmp(argv[1], "--help")) { + argv[1] = argv[0]; + argv[0] = cmd = "help"; + } + + for (i = 0; i < ARRAY_SIZE(commands); i++) { + struct cmd_struct *p = commands+i; + if (strcmp(p->cmd, cmd)) + continue; + exit(run_command(p, argc, argv)); + } +} + +static void execv_dashed_external(const char **argv) +{ + struct strbuf cmd; + const char *tmp; + + strbuf_init(&cmd, 0); + strbuf_addf(&cmd, "git-%s", argv[0]); + + /* + * argv[0] must be the git command, but the argv array + * belongs to the caller, and may be reused in + * subsequent loop iterations. Save argv[0] and + * restore it on error. + */ + tmp = argv[0]; + argv[0] = cmd.buf; + + trace_argv_printf(argv, "trace: exec:"); + + /* execvp() can only ever return if it fails */ + execvp(cmd.buf, (char **)argv); + + trace_printf("trace: exec failed: %s\n", strerror(errno)); + + argv[0] = tmp; + + strbuf_release(&cmd); +} + + +int main(int argc, const char **argv) +{ + const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help"; + char *slash = (char *)cmd + strlen(cmd); + int done_alias = 0; + + /* + * Take the basename of argv[0] as the command + * name, and the dirname as the default exec_path + * if we don't have anything better. + */ + do + --slash; + while (cmd <= slash && !is_dir_sep(*slash)); + if (cmd <= slash) { + *slash++ = 0; + git_set_argv0_path(cmd); + cmd = slash; + } + + /* + * "git-xxxx" is the same as "git xxxx", but we obviously: + * + * - cannot take flags in between the "git" and the "xxxx". + * - cannot execute it externally (since it would just do + * the same thing over again) + * + * So we just directly call the internal command handler, and + * die if that one cannot handle it. + */ + if (!prefixcmp(cmd, "git-")) { + cmd += 4; + argv[0] = cmd; + handle_internal_command(argc, argv); + die("cannot handle %s internally", cmd); + } + + /* Look for flags.. */ + argv++; + argc--; + handle_options(&argv, &argc, NULL); + commit_pager_choice(); + if (argc > 0) { + if (!prefixcmp(argv[0], "--")) + argv[0] += 2; + } else { + /* The user didn't specify a command; give them help */ + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + printf("\n%s\n", git_more_info_string); + exit(1); + } + cmd = argv[0]; + + /* + * We use PATH to find git commands, but we prepend some higher + * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH + * environment, and the $(gitexecdir) from the Makefile at build + * time. + */ + setup_path(); + + while (1) { + /* See if it's an internal command */ + handle_internal_command(argc, argv); + + /* .. then try the external ones */ + execv_dashed_external(argv); + + /* It could be an alias -- this works around the insanity + * of overriding "git log" with "git show" by having + * alias.log = show + */ + if (done_alias || !handle_alias(&argc, &argv)) + break; + done_alias = 1; + } + + if (errno == ENOENT) { + if (done_alias) { + fprintf(stderr, "Expansion of alias '%s' failed; " + "'%s' is not a git-command\n", + cmd, argv[0]); + exit(1); + } + argv[0] = help_unknown_cmd(cmd); + handle_internal_command(argc, argv); + execv_dashed_external(argv); + } + + fprintf(stderr, "Failed to run command '%s': %s\n", + cmd, strerror(errno)); + + return 1; +} diff --git a/http.c b/http.c deleted file mode 100644 index ed59b79..0000000 --- a/http.c +++ /dev/null @@ -1,643 +0,0 @@ -#include "http.h" - -int data_received; -int active_requests = 0; - -#ifdef USE_CURL_MULTI -static int max_requests = -1; -static CURLM *curlm; -#endif -#ifndef NO_CURL_EASY_DUPHANDLE -static CURL *curl_default; -#endif -char curl_errorstr[CURL_ERROR_SIZE]; - -static int curl_ssl_verify = -1; -static const char *ssl_cert = NULL; -#if LIBCURL_VERSION_NUM >= 0x070902 -static const char *ssl_key = NULL; -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 -static const char *ssl_capath = NULL; -#endif -static const char *ssl_cainfo = NULL; -static long curl_low_speed_limit = -1; -static long curl_low_speed_time = -1; -static int curl_ftp_no_epsv = 0; -static char *curl_http_proxy = NULL; - -static struct curl_slist *pragma_header; - -static struct active_request_slot *active_queue_head = NULL; - -size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) -{ - size_t size = eltsize * nmemb; - struct buffer *buffer = buffer_; - - if (size > buffer->buf.len - buffer->posn) - size = buffer->buf.len - buffer->posn; - memcpy(ptr, buffer->buf.buf + buffer->posn, size); - buffer->posn += size; - - return size; -} - -size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) -{ - size_t size = eltsize * nmemb; - struct strbuf *buffer = buffer_; - - strbuf_add(buffer, ptr, size); - data_received++; - return size; -} - -size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) -{ - data_received++; - return eltsize * nmemb; -} - -static void finish_active_slot(struct active_request_slot *slot); - -#ifdef USE_CURL_MULTI -static void process_curl_messages(void) -{ - int num_messages; - struct active_request_slot *slot; - CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages); - - while (curl_message != NULL) { - if (curl_message->msg == CURLMSG_DONE) { - int curl_result = curl_message->data.result; - slot = active_queue_head; - while (slot != NULL && - slot->curl != curl_message->easy_handle) - slot = slot->next; - if (slot != NULL) { - curl_multi_remove_handle(curlm, slot->curl); - slot->curl_result = curl_result; - finish_active_slot(slot); - } else { - fprintf(stderr, "Received DONE message for unknown request!\n"); - } - } else { - fprintf(stderr, "Unknown CURL message received: %d\n", - (int)curl_message->msg); - } - curl_message = curl_multi_info_read(curlm, &num_messages); - } -} -#endif - -static int http_options(const char *var, const char *value, void *cb) -{ - if (!strcmp("http.sslverify", var)) { - if (curl_ssl_verify == -1) { - curl_ssl_verify = git_config_bool(var, value); - } - return 0; - } - - if (!strcmp("http.sslcert", var)) { - if (ssl_cert == NULL) - return git_config_string(&ssl_cert, var, value); - return 0; - } -#if LIBCURL_VERSION_NUM >= 0x070902 - if (!strcmp("http.sslkey", var)) { - if (ssl_key == NULL) - return git_config_string(&ssl_key, var, value); - return 0; - } -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (!strcmp("http.sslcapath", var)) { - if (ssl_capath == NULL) - return git_config_string(&ssl_capath, var, value); - return 0; - } -#endif - if (!strcmp("http.sslcainfo", var)) { - if (ssl_cainfo == NULL) - return git_config_string(&ssl_cainfo, var, value); - return 0; - } - -#ifdef USE_CURL_MULTI - if (!strcmp("http.maxrequests", var)) { - if (max_requests == -1) - max_requests = git_config_int(var, value); - return 0; - } -#endif - - if (!strcmp("http.lowspeedlimit", var)) { - if (curl_low_speed_limit == -1) - curl_low_speed_limit = (long)git_config_int(var, value); - return 0; - } - if (!strcmp("http.lowspeedtime", var)) { - if (curl_low_speed_time == -1) - curl_low_speed_time = (long)git_config_int(var, value); - return 0; - } - - if (!strcmp("http.noepsv", var)) { - curl_ftp_no_epsv = git_config_bool(var, value); - return 0; - } - if (!strcmp("http.proxy", var)) { - if (curl_http_proxy == NULL) { - if (!value) - return config_error_nonbool(var); - curl_http_proxy = xstrdup(value); - } - return 0; - } - - /* Fall back on the default ones */ - return git_default_config(var, value, cb); -} - -static CURL* get_curl_handle(void) -{ - CURL* result = curl_easy_init(); - - if (!curl_ssl_verify) { - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0); - curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0); - } else { - /* Verify authenticity of the peer's certificate */ - curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1); - /* The name in the cert must match whom we tried to connect */ - curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2); - } - -#if LIBCURL_VERSION_NUM >= 0x070907 - curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); -#endif - - if (ssl_cert != NULL) - curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); -#if LIBCURL_VERSION_NUM >= 0x070902 - if (ssl_key != NULL) - curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - if (ssl_capath != NULL) - curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); -#endif - if (ssl_cainfo != NULL) - curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); - - if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { - curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, - curl_low_speed_limit); - curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME, - curl_low_speed_time); - } - - curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); - - if (getenv("GIT_CURL_VERBOSE")) - curl_easy_setopt(result, CURLOPT_VERBOSE, 1); - - curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT); - - if (curl_ftp_no_epsv) - curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); - - if (curl_http_proxy) - curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); - - return result; -} - -void http_init(struct remote *remote) -{ - char *low_speed_limit; - char *low_speed_time; - - curl_global_init(CURL_GLOBAL_ALL); - - if (remote && remote->http_proxy) - curl_http_proxy = xstrdup(remote->http_proxy); - - pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); - -#ifdef USE_CURL_MULTI - { - char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); - if (http_max_requests != NULL) - max_requests = atoi(http_max_requests); - } - - curlm = curl_multi_init(); - if (curlm == NULL) { - fprintf(stderr, "Error creating curl multi handle.\n"); - exit(1); - } -#endif - - if (getenv("GIT_SSL_NO_VERIFY")) - curl_ssl_verify = 0; - - ssl_cert = getenv("GIT_SSL_CERT"); -#if LIBCURL_VERSION_NUM >= 0x070902 - ssl_key = getenv("GIT_SSL_KEY"); -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 - ssl_capath = getenv("GIT_SSL_CAPATH"); -#endif - ssl_cainfo = getenv("GIT_SSL_CAINFO"); - - low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); - if (low_speed_limit != NULL) - curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); - low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); - if (low_speed_time != NULL) - curl_low_speed_time = strtol(low_speed_time, NULL, 10); - - git_config(http_options, NULL); - - if (curl_ssl_verify == -1) - curl_ssl_verify = 1; - -#ifdef USE_CURL_MULTI - if (max_requests < 1) - max_requests = DEFAULT_MAX_REQUESTS; -#endif - - if (getenv("GIT_CURL_FTP_NO_EPSV")) - curl_ftp_no_epsv = 1; - -#ifndef NO_CURL_EASY_DUPHANDLE - curl_default = get_curl_handle(); -#endif -} - -void http_cleanup(void) -{ - struct active_request_slot *slot = active_queue_head; - - while (slot != NULL) { - struct active_request_slot *next = slot->next; - if (slot->curl != NULL) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif - curl_easy_cleanup(slot->curl); - } - free(slot); - slot = next; - } - active_queue_head = NULL; - -#ifndef NO_CURL_EASY_DUPHANDLE - curl_easy_cleanup(curl_default); -#endif - -#ifdef USE_CURL_MULTI - curl_multi_cleanup(curlm); -#endif - curl_global_cleanup(); - - curl_slist_free_all(pragma_header); - pragma_header = NULL; - - if (curl_http_proxy) { - free(curl_http_proxy); - curl_http_proxy = NULL; - } -} - -struct active_request_slot *get_active_slot(void) -{ - struct active_request_slot *slot = active_queue_head; - struct active_request_slot *newslot; - -#ifdef USE_CURL_MULTI - int num_transfers; - - /* Wait for a slot to open up if the queue is full */ - while (active_requests >= max_requests) { - curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { - process_curl_messages(); - } - } -#endif - - while (slot != NULL && slot->in_use) { - slot = slot->next; - } - if (slot == NULL) { - newslot = xmalloc(sizeof(*newslot)); - newslot->curl = NULL; - newslot->in_use = 0; - newslot->next = NULL; - - slot = active_queue_head; - if (slot == NULL) { - active_queue_head = newslot; - } else { - while (slot->next != NULL) { - slot = slot->next; - } - slot->next = newslot; - } - slot = newslot; - } - - if (slot->curl == NULL) { -#ifdef NO_CURL_EASY_DUPHANDLE - slot->curl = get_curl_handle(); -#else - slot->curl = curl_easy_duphandle(curl_default); -#endif - } - - active_requests++; - slot->in_use = 1; - slot->local = NULL; - slot->results = NULL; - slot->finished = NULL; - slot->callback_data = NULL; - slot->callback_func = NULL; - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - - return slot; -} - -int start_active_slot(struct active_request_slot *slot) -{ -#ifdef USE_CURL_MULTI - CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); - int num_transfers; - - if (curlm_result != CURLM_OK && - curlm_result != CURLM_CALL_MULTI_PERFORM) { - active_requests--; - slot->in_use = 0; - return 0; - } - - /* - * We know there must be something to do, since we just added - * something. - */ - curl_multi_perform(curlm, &num_transfers); -#endif - return 1; -} - -#ifdef USE_CURL_MULTI -struct fill_chain { - void *data; - int (*fill)(void *); - struct fill_chain *next; -}; - -static struct fill_chain *fill_cfg = NULL; - -void add_fill_function(void *data, int (*fill)(void *)) -{ - struct fill_chain *new = xmalloc(sizeof(*new)); - struct fill_chain **linkp = &fill_cfg; - new->data = data; - new->fill = fill; - new->next = NULL; - while (*linkp) - linkp = &(*linkp)->next; - *linkp = new; -} - -void fill_active_slots(void) -{ - struct active_request_slot *slot = active_queue_head; - - while (active_requests < max_requests) { - struct fill_chain *fill; - for (fill = fill_cfg; fill; fill = fill->next) - if (fill->fill(fill->data)) - break; - - if (!fill) - break; - } - - while (slot != NULL) { - if (!slot->in_use && slot->curl != NULL) { - curl_easy_cleanup(slot->curl); - slot->curl = NULL; - } - slot = slot->next; - } -} - -void step_active_slots(void) -{ - int num_transfers; - CURLMcode curlm_result; - - do { - curlm_result = curl_multi_perform(curlm, &num_transfers); - } while (curlm_result == CURLM_CALL_MULTI_PERFORM); - if (num_transfers < active_requests) { - process_curl_messages(); - fill_active_slots(); - } -} -#endif - -void run_active_slot(struct active_request_slot *slot) -{ -#ifdef USE_CURL_MULTI - long last_pos = 0; - long current_pos; - fd_set readfds; - fd_set writefds; - fd_set excfds; - int max_fd; - struct timeval select_timeout; - int finished = 0; - - slot->finished = &finished; - while (!finished) { - data_received = 0; - step_active_slots(); - - if (!data_received && slot->local != NULL) { - current_pos = ftell(slot->local); - if (current_pos > last_pos) - data_received++; - last_pos = current_pos; - } - - if (slot->in_use && !data_received) { - max_fd = 0; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&excfds); - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 50000; - select(max_fd, &readfds, &writefds, - &excfds, &select_timeout); - } - } -#else - while (slot->in_use) { - slot->curl_result = curl_easy_perform(slot->curl); - finish_active_slot(slot); - } -#endif -} - -static void closedown_active_slot(struct active_request_slot *slot) -{ - active_requests--; - slot->in_use = 0; -} - -void release_active_slot(struct active_request_slot *slot) -{ - closedown_active_slot(slot); - if (slot->curl) { -#ifdef USE_CURL_MULTI - curl_multi_remove_handle(curlm, slot->curl); -#endif - curl_easy_cleanup(slot->curl); - slot->curl = NULL; - } -#ifdef USE_CURL_MULTI - fill_active_slots(); -#endif -} - -static void finish_active_slot(struct active_request_slot *slot) -{ - closedown_active_slot(slot); - curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); - - if (slot->finished != NULL) - (*slot->finished) = 1; - - /* Store slot results so they can be read after the slot is reused */ - if (slot->results != NULL) { - slot->results->curl_result = slot->curl_result; - slot->results->http_code = slot->http_code; - } - - /* Run callback if appropriate */ - if (slot->callback_func != NULL) { - slot->callback_func(slot->callback_data); - } -} - -void finish_all_active_slots(void) -{ - struct active_request_slot *slot = active_queue_head; - - while (slot != NULL) - if (slot->in_use) { - run_active_slot(slot); - slot = active_queue_head; - } else { - slot = slot->next; - } -} - -static inline int needs_quote(int ch) -{ - if (((ch >= 'A') && (ch <= 'Z')) - || ((ch >= 'a') && (ch <= 'z')) - || ((ch >= '0') && (ch <= '9')) - || (ch == '/') - || (ch == '-') - || (ch == '.')) - return 0; - return 1; -} - -static inline int hex(int v) -{ - if (v < 10) return '0' + v; - else return 'A' + v - 10; -} - -static char *quote_ref_url(const char *base, const char *ref) -{ - const char *cp; - char *dp, *qref; - int len, baselen, ch; - - baselen = strlen(base); - len = baselen + 2; /* '/' after base and terminating NUL */ - for (cp = ref; (ch = *cp) != 0; cp++, len++) - if (needs_quote(ch)) - len += 2; /* extra two hex plus replacement % */ - qref = xmalloc(len); - memcpy(qref, base, baselen); - dp = qref + baselen; - *(dp++) = '/'; - for (cp = ref; (ch = *cp) != 0; cp++) { - if (needs_quote(ch)) { - *dp++ = '%'; - *dp++ = hex((ch >> 4) & 0xF); - *dp++ = hex(ch & 0xF); - } - else - *dp++ = ch; - } - *dp = 0; - - return qref; -} - -int http_fetch_ref(const char *base, struct ref *ref) -{ - char *url; - struct strbuf buffer = STRBUF_INIT; - struct active_request_slot *slot; - struct slot_results results; - int ret; - - url = quote_ref_url(base, ref->name); - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result == CURLE_OK) { - strbuf_rtrim(&buffer); - if (buffer.len == 40) - ret = get_sha1_hex(buffer.buf, ref->old_sha1); - else if (!prefixcmp(buffer.buf, "ref: ")) { - ref->symref = xstrdup(buffer.buf + 5); - ret = 0; - } else - ret = 1; - } else { - ret = error("Couldn't get %s for %s\n%s", - url, ref->name, curl_errorstr); - } - } else { - ret = error("Unable to start request"); - } - - strbuf_release(&buffer); - free(url); - return ret; -} diff --git a/http.c.cmake b/http.c.cmake new file mode 100644 index 0000000..fd8b424 --- /dev/null +++ b/http.c.cmake @@ -0,0 +1,643 @@ +#include "http.h" + +int data_received; +int active_requests = 0; + +#ifdef USE_CURL_MULTI +static int max_requests = -1; +static CURLM *curlm; +#endif +#ifndef NO_CURL_EASY_DUPHANDLE +static CURL *curl_default; +#endif +char curl_errorstr[CURL_ERROR_SIZE]; + +static int curl_ssl_verify = -1; +static const char *ssl_cert = NULL; +#if LIBCURL_VERSION_NUM >= 0x070902 +static const char *ssl_key = NULL; +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 +static const char *ssl_capath = NULL; +#endif +static const char *ssl_cainfo = NULL; +static long curl_low_speed_limit = -1; +static long curl_low_speed_time = -1; +static int curl_ftp_no_epsv = 0; +static char *curl_http_proxy = NULL; + +static struct curl_slist *pragma_header; + +static struct active_request_slot *active_queue_head = NULL; + +size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct buffer *buffer = buffer_; + + if (size > buffer->buf.len - buffer->posn) + size = buffer->buf.len - buffer->posn; + memcpy(ptr, buffer->buf.buf + buffer->posn, size); + buffer->posn += size; + + return size; +} + +size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) +{ + size_t size = eltsize * nmemb; + struct strbuf *buffer = buffer_; + + strbuf_add(buffer, ptr, size); + data_received++; + return size; +} + +size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf) +{ + data_received++; + return eltsize * nmemb; +} + +static void finish_active_slot(struct active_request_slot *slot); + +#ifdef USE_CURL_MULTI +static void process_curl_messages(void) +{ + int num_messages; + struct active_request_slot *slot; + CURLMsg *curl_message = curl_multi_info_read(curlm, &num_messages); + + while (curl_message != NULL) { + if (curl_message->msg == CURLMSG_DONE) { + int curl_result = curl_message->data.result; + slot = active_queue_head; + while (slot != NULL && + slot->curl != curl_message->easy_handle) + slot = slot->next; + if (slot != NULL) { + curl_multi_remove_handle(curlm, slot->curl); + slot->curl_result = curl_result; + finish_active_slot(slot); + } else { + fprintf(stderr, "Received DONE message for unknown request!\n"); + } + } else { + fprintf(stderr, "Unknown CURL message received: %d\n", + (int)curl_message->msg); + } + curl_message = curl_multi_info_read(curlm, &num_messages); + } +} +#endif + +static int http_options(const char *var, const char *value, void *cb) +{ + if (!strcmp("http.sslverify", var)) { + if (curl_ssl_verify == -1) { + curl_ssl_verify = git_config_bool(var, value); + } + return 0; + } + + if (!strcmp("http.sslcert", var)) { + if (ssl_cert == NULL) + return git_config_string(&ssl_cert, var, value); + return 0; + } +#if LIBCURL_VERSION_NUM >= 0x070902 + if (!strcmp("http.sslkey", var)) { + if (ssl_key == NULL) + return git_config_string(&ssl_key, var, value); + return 0; + } +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + if (!strcmp("http.sslcapath", var)) { + if (ssl_capath == NULL) + return git_config_string(&ssl_capath, var, value); + return 0; + } +#endif + if (!strcmp("http.sslcainfo", var)) { + if (ssl_cainfo == NULL) + return git_config_string(&ssl_cainfo, var, value); + return 0; + } + +#ifdef USE_CURL_MULTI + if (!strcmp("http.maxrequests", var)) { + if (max_requests == -1) + max_requests = git_config_int(var, value); + return 0; + } +#endif + + if (!strcmp("http.lowspeedlimit", var)) { + if (curl_low_speed_limit == -1) + curl_low_speed_limit = (long)git_config_int(var, value); + return 0; + } + if (!strcmp("http.lowspeedtime", var)) { + if (curl_low_speed_time == -1) + curl_low_speed_time = (long)git_config_int(var, value); + return 0; + } + + if (!strcmp("http.noepsv", var)) { + curl_ftp_no_epsv = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.proxy", var)) { + if (curl_http_proxy == NULL) { + if (!value) + return config_error_nonbool(var); + curl_http_proxy = xstrdup(value); + } + return 0; + } + + /* Fall back on the default ones */ + return git_default_config(var, value, cb); +} + +static CURL* get_curl_handle(void) +{ + CURL* result = curl_easy_init(); + + if (!curl_ssl_verify) { + curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 0); + } else { + /* Verify authenticity of the peer's certificate */ + curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 1); + /* The name in the cert must match whom we tried to connect */ + curl_easy_setopt(result, CURLOPT_SSL_VERIFYHOST, 2); + } + +#if LIBCURL_VERSION_NUM >= 0x070907 + curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); +#endif + + if (ssl_cert != NULL) + curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); +#if LIBCURL_VERSION_NUM >= 0x070902 + if (ssl_key != NULL) + curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + if (ssl_capath != NULL) + curl_easy_setopt(result, CURLOPT_CAPATH, ssl_capath); +#endif + if (ssl_cainfo != NULL) + curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); + curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); + + if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { + curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, + curl_low_speed_limit); + curl_easy_setopt(result, CURLOPT_LOW_SPEED_TIME, + curl_low_speed_time); + } + + curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1); + + if (getenv("GIT_CURL_VERBOSE")) + curl_easy_setopt(result, CURLOPT_VERBOSE, 1); + + curl_easy_setopt(result, CURLOPT_USERAGENT, "@GIT_USER_AGENT@" ); + + if (curl_ftp_no_epsv) + curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); + + if (curl_http_proxy) + curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); + + return result; +} + +void http_init(struct remote *remote) +{ + char *low_speed_limit; + char *low_speed_time; + + curl_global_init(CURL_GLOBAL_ALL); + + if (remote && remote->http_proxy) + curl_http_proxy = xstrdup(remote->http_proxy); + + pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); + +#ifdef USE_CURL_MULTI + { + char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); + if (http_max_requests != NULL) + max_requests = atoi(http_max_requests); + } + + curlm = curl_multi_init(); + if (curlm == NULL) { + fprintf(stderr, "Error creating curl multi handle.\n"); + exit(1); + } +#endif + + if (getenv("GIT_SSL_NO_VERIFY")) + curl_ssl_verify = 0; + + ssl_cert = getenv("GIT_SSL_CERT"); +#if LIBCURL_VERSION_NUM >= 0x070902 + ssl_key = getenv("GIT_SSL_KEY"); +#endif +#if LIBCURL_VERSION_NUM >= 0x070908 + ssl_capath = getenv("GIT_SSL_CAPATH"); +#endif + ssl_cainfo = getenv("GIT_SSL_CAINFO"); + + low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); + if (low_speed_limit != NULL) + curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); + low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); + if (low_speed_time != NULL) + curl_low_speed_time = strtol(low_speed_time, NULL, 10); + + git_config(http_options, NULL); + + if (curl_ssl_verify == -1) + curl_ssl_verify = 1; + +#ifdef USE_CURL_MULTI + if (max_requests < 1) + max_requests = DEFAULT_MAX_REQUESTS; +#endif + + if (getenv("GIT_CURL_FTP_NO_EPSV")) + curl_ftp_no_epsv = 1; + +#ifndef NO_CURL_EASY_DUPHANDLE + curl_default = get_curl_handle(); +#endif +} + +void http_cleanup(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (slot != NULL) { + struct active_request_slot *next = slot->next; + if (slot->curl != NULL) { +#ifdef USE_CURL_MULTI + curl_multi_remove_handle(curlm, slot->curl); +#endif + curl_easy_cleanup(slot->curl); + } + free(slot); + slot = next; + } + active_queue_head = NULL; + +#ifndef NO_CURL_EASY_DUPHANDLE + curl_easy_cleanup(curl_default); +#endif + +#ifdef USE_CURL_MULTI + curl_multi_cleanup(curlm); +#endif + curl_global_cleanup(); + + curl_slist_free_all(pragma_header); + pragma_header = NULL; + + if (curl_http_proxy) { + free(curl_http_proxy); + curl_http_proxy = NULL; + } +} + +struct active_request_slot *get_active_slot(void) +{ + struct active_request_slot *slot = active_queue_head; + struct active_request_slot *newslot; + +#ifdef USE_CURL_MULTI + int num_transfers; + + /* Wait for a slot to open up if the queue is full */ + while (active_requests >= max_requests) { + curl_multi_perform(curlm, &num_transfers); + if (num_transfers < active_requests) { + process_curl_messages(); + } + } +#endif + + while (slot != NULL && slot->in_use) { + slot = slot->next; + } + if (slot == NULL) { + newslot = xmalloc(sizeof(*newslot)); + newslot->curl = NULL; + newslot->in_use = 0; + newslot->next = NULL; + + slot = active_queue_head; + if (slot == NULL) { + active_queue_head = newslot; + } else { + while (slot->next != NULL) { + slot = slot->next; + } + slot->next = newslot; + } + slot = newslot; + } + + if (slot->curl == NULL) { +#ifdef NO_CURL_EASY_DUPHANDLE + slot->curl = get_curl_handle(); +#else + slot->curl = curl_easy_duphandle(curl_default); +#endif + } + + active_requests++; + slot->in_use = 1; + slot->local = NULL; + slot->results = NULL; + slot->finished = NULL; + slot->callback_data = NULL; + slot->callback_func = NULL; + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); + curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, NULL); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + + return slot; +} + +int start_active_slot(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + CURLMcode curlm_result = curl_multi_add_handle(curlm, slot->curl); + int num_transfers; + + if (curlm_result != CURLM_OK && + curlm_result != CURLM_CALL_MULTI_PERFORM) { + active_requests--; + slot->in_use = 0; + return 0; + } + + /* + * We know there must be something to do, since we just added + * something. + */ + curl_multi_perform(curlm, &num_transfers); +#endif + return 1; +} + +#ifdef USE_CURL_MULTI +struct fill_chain { + void *data; + int (*fill)(void *); + struct fill_chain *next; +}; + +static struct fill_chain *fill_cfg = NULL; + +void add_fill_function(void *data, int (*fill)(void *)) +{ + struct fill_chain *new = xmalloc(sizeof(*new)); + struct fill_chain **linkp = &fill_cfg; + new->data = data; + new->fill = fill; + new->next = NULL; + while (*linkp) + linkp = &(*linkp)->next; + *linkp = new; +} + +void fill_active_slots(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (active_requests < max_requests) { + struct fill_chain *fill; + for (fill = fill_cfg; fill; fill = fill->next) + if (fill->fill(fill->data)) + break; + + if (!fill) + break; + } + + while (slot != NULL) { + if (!slot->in_use && slot->curl != NULL) { + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + } + slot = slot->next; + } +} + +void step_active_slots(void) +{ + int num_transfers; + CURLMcode curlm_result; + + do { + curlm_result = curl_multi_perform(curlm, &num_transfers); + } while (curlm_result == CURLM_CALL_MULTI_PERFORM); + if (num_transfers < active_requests) { + process_curl_messages(); + fill_active_slots(); + } +} +#endif + +void run_active_slot(struct active_request_slot *slot) +{ +#ifdef USE_CURL_MULTI + long last_pos = 0; + long current_pos; + fd_set readfds; + fd_set writefds; + fd_set excfds; + int max_fd; + struct timeval select_timeout; + int finished = 0; + + slot->finished = &finished; + while (!finished) { + data_received = 0; + step_active_slots(); + + if (!data_received && slot->local != NULL) { + current_pos = ftell(slot->local); + if (current_pos > last_pos) + data_received++; + last_pos = current_pos; + } + + if (slot->in_use && !data_received) { + max_fd = 0; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&excfds); + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + select(max_fd, &readfds, &writefds, + &excfds, &select_timeout); + } + } +#else + while (slot->in_use) { + slot->curl_result = curl_easy_perform(slot->curl); + finish_active_slot(slot); + } +#endif +} + +static void closedown_active_slot(struct active_request_slot *slot) +{ + active_requests--; + slot->in_use = 0; +} + +void release_active_slot(struct active_request_slot *slot) +{ + closedown_active_slot(slot); + if (slot->curl) { +#ifdef USE_CURL_MULTI + curl_multi_remove_handle(curlm, slot->curl); +#endif + curl_easy_cleanup(slot->curl); + slot->curl = NULL; + } +#ifdef USE_CURL_MULTI + fill_active_slots(); +#endif +} + +static void finish_active_slot(struct active_request_slot *slot) +{ + closedown_active_slot(slot); + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); + + if (slot->finished != NULL) + (*slot->finished) = 1; + + /* Store slot results so they can be read after the slot is reused */ + if (slot->results != NULL) { + slot->results->curl_result = slot->curl_result; + slot->results->http_code = slot->http_code; + } + + /* Run callback if appropriate */ + if (slot->callback_func != NULL) { + slot->callback_func(slot->callback_data); + } +} + +void finish_all_active_slots(void) +{ + struct active_request_slot *slot = active_queue_head; + + while (slot != NULL) + if (slot->in_use) { + run_active_slot(slot); + slot = active_queue_head; + } else { + slot = slot->next; + } +} + +static inline int needs_quote(int ch) +{ + if (((ch >= 'A') && (ch <= 'Z')) + || ((ch >= 'a') && (ch <= 'z')) + || ((ch >= '0') && (ch <= '9')) + || (ch == '/') + || (ch == '-') + || (ch == '.')) + return 0; + return 1; +} + +static inline int hex(int v) +{ + if (v < 10) return '0' + v; + else return 'A' + v - 10; +} + +static char *quote_ref_url(const char *base, const char *ref) +{ + const char *cp; + char *dp, *qref; + int len, baselen, ch; + + baselen = strlen(base); + len = baselen + 2; /* '/' after base and terminating NUL */ + for (cp = ref; (ch = *cp) != 0; cp++, len++) + if (needs_quote(ch)) + len += 2; /* extra two hex plus replacement % */ + qref = xmalloc(len); + memcpy(qref, base, baselen); + dp = qref + baselen; + *(dp++) = '/'; + for (cp = ref; (ch = *cp) != 0; cp++) { + if (needs_quote(ch)) { + *dp++ = '%'; + *dp++ = hex((ch >> 4) & 0xF); + *dp++ = hex(ch & 0xF); + } + else + *dp++ = ch; + } + *dp = 0; + + return qref; +} + +int http_fetch_ref(const char *base, struct ref *ref) +{ + char *url; + struct strbuf buffer = STRBUF_INIT; + struct active_request_slot *slot; + struct slot_results results; + int ret; + + url = quote_ref_url(base, ref->name); + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result == CURLE_OK) { + strbuf_rtrim(&buffer); + if (buffer.len == 40) + ret = get_sha1_hex(buffer.buf, ref->old_sha1); + else if (!prefixcmp(buffer.buf, "ref: ")) { + ref->symref = xstrdup(buffer.buf + 5); + ret = 0; + } else + ret = 1; + } else { + ret = error("Couldn't get %s for %s\n%s", + url, ref->name, curl_errorstr); + } + } else { + ret = error("Unable to start request"); + } + + strbuf_release(&buffer); + free(url); + return ret; +}