int setup_rerere(struct string_list *merge_rr, int flags) { int fd; git_rerere_config(); if (!is_rerere_enabled()) return -1; if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE)) rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE); merge_rr_path = git_pathdup("MERGE_RR"); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, LOCK_DIE_ON_ERROR); read_rr(merge_rr); return fd; }
int git_config_set_multivar(const char *key, const char *value, const char *value_regex, int multi_replace) { const char *config_filename; char *buf = NULL; int ret; if (config_exclusive_filename) config_filename = config_exclusive_filename; else config_filename = buf = git_pathdup("config"); ret = git_config_set_multivar_in_file(config_filename, key, value, value_regex, multi_replace); free(buf); return ret; }
int git_get_config(const char *key, char *buffer, int size) { char *local, *global, *globalxdg; const char *home, *system; struct config_buf buf; buf.buf=buffer; buf.size=size; buf.seen = 0; buf.key = key; home = get_windows_home_directory(); if (home) { global = xstrdup(mkpath("%s/.gitconfig", home)); globalxdg = xstrdup(mkpath("%s/.config/git/config", home)); } else { global = NULL; globalxdg = NULL; } system = git_etc_gitconfig(); local = git_pathdup("config"); if ( !buf.seen) git_config_with_options(get_config, &buf, local, NULL, 1); if (!buf.seen && global) git_config_with_options(get_config, &buf, global, NULL, 1); if (!buf.seen && globalxdg) git_config_with_options(get_config, &buf, globalxdg, NULL, 1); if (!buf.seen && system) git_config_with_options(get_config, &buf, system, NULL, 1); if(local) free(local); if(global) free(global); if (globalxdg) free(globalxdg); return !buf.seen; }
int git_get_config(const char *key, char *buffer, int size, char *git_path) { char *local,*global,*system_wide,*p; struct config_buf buf; buf.buf=buffer; buf.size=size; buf.seen = 0; buf.key = key; local=global=system_wide=NULL; //local = config_exclusive_filename; if (!local) { const char *home = get_windows_home_directory(); local=p= git_pathdup("config"); if(git_path&&strlen(git_path)) { local=xstrdup(mkpath("%s/%s", git_path, p)); free(p); } if (git_config_global() && home) global = xstrdup(mkpath("%s/.gitconfig", home)); if (git_config_system()) system_wide = git_etc_gitconfig(); } if ( !buf.seen) git_config_from_file(get_config, local, &buf); if (!buf.seen && global) git_config_from_file(get_config, global, &buf); if (!buf.seen && system_wide) git_config_from_file(get_config, system_wide, &buf); if(local) free(local); if(global) free(global); //if(system_wide) // free(system_wide); return !buf.seen; }
/* * Returns 0 if there was no previous error and gc can proceed, 1 if * gc should not proceed due to an error in the last run. Prints a * message and returns -1 if an error occured while reading gc.log */ static int report_last_gc_error(void) { struct strbuf sb = STRBUF_INIT; int ret = 0; ssize_t len; struct stat st; char *gc_log_path = git_pathdup("gc.log"); if (stat(gc_log_path, &st)) { if (errno == ENOENT) goto done; ret = error_errno(_("cannot stat '%s'"), gc_log_path); goto done; } if (st.st_mtime < gc_log_expire_time) goto done; len = strbuf_read_file(&sb, gc_log_path, 0); if (len < 0) ret = error_errno(_("cannot read '%s'"), gc_log_path); else if (len > 0) { /* * A previous gc failed. Report the error, and don't * bother with an automatic gc run since it is likely * to fail in the same way. */ warning(_("The last gc run reported the following. " "Please correct the root cause\n" "and remove %s.\n" "Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), gc_log_path, sb.buf); ret = 1; } strbuf_release(&sb); done: free(gc_log_path); return ret; }
static int edit_and_replace(const char *object_ref, int force, int raw) { char *tmpfile; enum object_type type; struct object_id old_oid, new_oid, prev; struct strbuf ref = STRBUF_INIT; if (get_oid(object_ref, &old_oid) < 0) return error("Not a valid object name: '%s'", object_ref); type = oid_object_info(the_repository, &old_oid, NULL); if (type < 0) return error("unable to get object type for %s", oid_to_hex(&old_oid)); if (check_ref_valid(&old_oid, &prev, &ref, force)) { strbuf_release(&ref); return -1; } strbuf_release(&ref); tmpfile = git_pathdup("REPLACE_EDITOBJ"); if (export_object(&old_oid, type, raw, tmpfile)) { free(tmpfile); return -1; } if (launch_editor(tmpfile, NULL, NULL) < 0) { free(tmpfile); return error("editing object file failed"); } if (import_object(&new_oid, type, raw, tmpfile)) { free(tmpfile); return -1; } free(tmpfile); if (!oidcmp(&old_oid, &new_oid)) return error("new object is the same as the old one: '%s'", oid_to_hex(&old_oid)); return replace_object_oid(object_ref, &old_oid, "replacement", &new_oid, force); }
int git_config(config_fn_t fn, void *data) { int ret = 0, found = 0; char *repo_config = NULL; const char *home = NULL; /* Setting $GIT_CONFIG makes git read _only_ the given config file. */ 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); found += 1; } 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); found += 1; } free(user_config); } repo_config = git_pathdup("config"); if (!access(repo_config, R_OK)) { ret += git_config_from_file(fn, repo_config, data); found += 1; } free(repo_config); ret += git_config_from_parameters(fn, data); if (config_parameters) found += 1; return ret == 0 ? found : ret; }
static void setup_git_env(void) { git_dir = getenv(GIT_DIR_ENVIRONMENT); if (!git_dir) git_dir = read_gitfile_gently(DEFAULT_GIT_DIR_ENVIRONMENT); if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; git_object_dir = getenv(DB_ENVIRONMENT); if (!git_object_dir) { git_object_dir = xmalloc(strlen(git_dir) + 9); sprintf(git_object_dir, "%s/objects", git_dir); } git_refs_dir = xmalloc(strlen(git_dir) + 6); sprintf(git_refs_dir, "%s/refs", git_dir); git_index_file = getenv(INDEX_ENVIRONMENT); if (!git_index_file) { git_index_file = xmalloc(strlen(git_dir) + 7); sprintf(git_index_file, "%s/index", git_dir); } git_graft_file = getenv(GRAFT_ENVIRONMENT); if (!git_graft_file) git_graft_file = git_pathdup("info/grafts"); }
static void prepare_note_data(const unsigned char *object, struct note_data *d, const unsigned char *old_note) { if (d->use_editor || !d->given) { int fd; struct strbuf buf = STRBUF_INIT; /* write the template message before editing: */ d->edit_path = git_pathdup("NOTES_EDITMSG"); fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) die_errno(_("could not create file '%s'"), d->edit_path); if (d->given) write_or_die(fd, d->buf.buf, d->buf.len); else if (old_note) copy_obj_to_fd(fd, old_note); strbuf_addch(&buf, '\n'); strbuf_add_commented_lines(&buf, "\n", strlen("\n")); strbuf_add_commented_lines(&buf, _(note_template), strlen(_(note_template))); strbuf_addch(&buf, '\n'); write_or_die(fd, buf.buf, buf.len); write_commented_object(fd, object); close(fd); strbuf_release(&buf); strbuf_reset(&d->buf); if (launch_editor(d->edit_path, &d->buf, NULL)) { die(_("please supply the note contents using either -m or -F option")); } strbuf_stripspace(&d->buf, 1); } }
int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; char *value; given_config_file = getenv(CONFIG_ENVIRONMENT); argc = parse_options(argc, argv, prefix, builtin_config_options, builtin_config_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) { error("only one config file at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } if (use_global_config) { char *user_config = NULL; char *xdg_config = NULL; home_config_paths(&user_config, &xdg_config, "config"); if (access(user_config, R_OK) && !access(xdg_config, R_OK)) given_config_file = xdg_config; else if (user_config) given_config_file = user_config; else die("$HOME not set"); } else if (use_system_config) given_config_file = git_etc_gitconfig(); else if (use_local_config) given_config_file = git_pathdup("config"); else if (given_config_file) { if (!is_absolute_path(given_config_file) && prefix) given_config_file = xstrdup(prefix_filename(prefix, strlen(prefix), given_config_file)); } if (respect_includes == -1) respect_includes = !given_config_file; if (end_null) { term = '\0'; delim = '\n'; key_delim = '\n'; } if (HAS_MULTI_BITS(types)) { error("only one type at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } if (get_color_slot) actions |= ACTION_GET_COLOR; if (get_colorbool_slot) actions |= ACTION_GET_COLORBOOL; if ((get_color_slot || get_colorbool_slot) && types) { error("--get-color and variable type are incoherent"); usage_with_options(builtin_config_usage, builtin_config_options); } if (HAS_MULTI_BITS(actions)) { error("only one action at a time."); usage_with_options(builtin_config_usage, builtin_config_options); } if (actions == 0) switch (argc) { case 1: actions = ACTION_GET; break; case 2: actions = ACTION_SET; break; case 3: actions = ACTION_SET_ALL; break; default: usage_with_options(builtin_config_usage, builtin_config_options); } if (actions == ACTION_LIST) { check_argc(argc, 0, 0); if (git_config_with_options(show_all_config, NULL, given_config_file, respect_includes) < 0) { if (given_config_file) die_errno("unable to read config file '%s'", given_config_file); else die("error processing config file(s)"); } } else if (actions == ACTION_EDIT) { check_argc(argc, 0, 0); if (!given_config_file && nongit) die("not in a git directory"); git_config(git_default_config, NULL); launch_editor(given_config_file ? given_config_file : git_path("config"), NULL, NULL); } else if (actions == ACTION_SET) { int ret; check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); ret = git_config_set_in_file(given_config_file, argv[0], value); if (ret == CONFIG_NOTHING_SET) error("cannot overwrite multiple values with a single value\n" " Use a regexp, --add or --replace-all to change %s.", argv[0]); return ret; } else if (actions == ACTION_SET_ALL) { check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, argv[2], 0); } else if (actions == ACTION_ADD) { check_argc(argc, 2, 2); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, "^$", 0); } else if (actions == ACTION_REPLACE_ALL) { check_argc(argc, 2, 3); value = normalize_value(argv[0], argv[1]); return git_config_set_multivar_in_file(given_config_file, argv[0], value, argv[2], 1); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); return get_value(argv[0], argv[1]); } else if (actions == ACTION_GET_ALL) { do_all = 1; check_argc(argc, 1, 2); return get_value(argv[0], argv[1]); } else if (actions == ACTION_GET_REGEXP) { show_keys = 1; use_key_regexp = 1; do_all = 1; check_argc(argc, 1, 2); return get_value(argv[0], argv[1]); } else if (actions == ACTION_UNSET) { check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file(given_config_file, argv[0], NULL, argv[1], 0); else return git_config_set_in_file(given_config_file, argv[0], NULL); } else if (actions == ACTION_UNSET_ALL) { check_argc(argc, 1, 2); return git_config_set_multivar_in_file(given_config_file, argv[0], NULL, argv[1], 1); } else if (actions == ACTION_RENAME_SECTION) { int ret; check_argc(argc, 2, 2); ret = git_config_rename_section_in_file(given_config_file, argv[0], argv[1]); if (ret < 0) return ret; if (ret == 0) die("No such section!"); } else if (actions == ACTION_REMOVE_SECTION) { int ret; check_argc(argc, 1, 1); ret = git_config_rename_section_in_file(given_config_file, argv[0], NULL); if (ret < 0) return ret; if (ret == 0) die("No such section!"); } else if (actions == ACTION_GET_COLOR) { get_color(argv[0]); } else if (actions == ACTION_GET_COLORBOOL) { if (argc == 1) color_stdout_is_tty = git_config_bool("command line", argv[0]); return get_colorbool(argc != 0); } return 0; }
static int get_value(const char *key_, const char *regex_) { int ret = -1; char *global = NULL, *xdg = NULL, *repo_config = NULL; const char *system_wide = NULL, *local; struct config_include_data inc = CONFIG_INCLUDE_INIT; config_fn_t fn; void *data; local = given_config_file; if (!local) { local = repo_config = git_pathdup("config"); if (git_config_system()) system_wide = git_etc_gitconfig(); home_config_paths(&global, &xdg, "config"); } if (use_key_regexp) { char *tl; /* * NEEDSWORK: this naive pattern lowercasing obviously does not * work for more complex patterns like "^[^.]*Foo.*bar". * Perhaps we should deprecate this altogether someday. */ key = xstrdup(key_); for (tl = key + strlen(key) - 1; tl >= key && *tl != '.'; tl--) *tl = tolower(*tl); for (tl = key; *tl && *tl != '.'; tl++) *tl = tolower(*tl); key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); free(key); goto free_strings; } } else { if (git_config_parse_key(key_, &key, NULL)) goto free_strings; } if (regex_) { if (regex_[0] == '!') { do_not_match = 1; regex_++; } regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); goto free_strings; } } fn = show_config; data = NULL; if (respect_includes) { inc.fn = fn; inc.data = data; fn = git_config_include; data = &inc; } if (do_all && system_wide) git_config_from_file(fn, system_wide, data); if (do_all && xdg) git_config_from_file(fn, xdg, data); if (do_all && global) git_config_from_file(fn, global, data); if (do_all) git_config_from_file(fn, local, data); git_config_from_parameters(fn, data); if (!do_all && !seen) git_config_from_file(fn, local, data); if (!do_all && !seen && global) git_config_from_file(fn, global, data); if (!do_all && !seen && xdg) git_config_from_file(fn, xdg, data); if (!do_all && !seen && system_wide) git_config_from_file(fn, system_wide, data); free(key); if (regexp) { regfree(regexp); free(regexp); } if (do_all) ret = !seen; else ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; free_strings: free(repo_config); free(global); free(xdg); return ret; }
int create_symref(const char *ref_target, const char *refs_heads_master, const char *logmsg) { const char *lockpath; char ref[1000]; int fd, len, written; char *git_HEAD = git_pathdup("%s", ref_target); unsigned char old_sha1[20], new_sha1[20]; if (logmsg && read_ref(ref_target, old_sha1)) hashclr(old_sha1); if (safe_create_leading_directories(git_HEAD) < 0) return error("unable to create directory for %s", git_HEAD); #ifndef NO_SYMLINK_HEAD if (prefer_symlink_refs) { unlink(git_HEAD); if (!symlink(refs_heads_master, git_HEAD)) goto done; fprintf(stderr, "no symlink - falling back to symbolic ref\n"); } #endif len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); if (sizeof(ref) <= len) { error("refname too long: %s", refs_heads_master); goto error_free_return; } lockpath = mkpath("%s.lock", git_HEAD); fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) { error("Unable to open %s for writing", lockpath); goto error_free_return; } written = write_in_full(fd, ref, len); if (close(fd) != 0 || written != len) { error("Unable to write to %s", lockpath); goto error_unlink_return; } if (rename(lockpath, git_HEAD) < 0) { error("Unable to create %s", git_HEAD); goto error_unlink_return; } if (adjust_shared_perm(git_HEAD)) { error("Unable to fix permissions on %s", lockpath); error_unlink_return: unlink_or_warn(lockpath); error_free_return: free(git_HEAD); return -1; } #ifndef NO_SYMLINK_HEAD done: #endif if (logmsg && !read_ref(refs_heads_master, new_sha1)) log_ref_write(ref_target, old_sha1, new_sha1, logmsg); free(git_HEAD); return 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 fd = -1, in_fd; int ret; char *config_filename; struct lock_file *lock = NULL; if (config_exclusive_filename) config_filename = xstrdup(config_exclusive_filename); else config_filename = git_pathdup("config"); /* parse-key returns negative; flip the sign to feed exit(3) */ ret = 0 - git_config_parse_key(key, &store.key, &store.baselen); if (ret) goto out_free; store.multi_replace = multi_replace; /* * 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: %s", config_filename, strerror(errno)); free(store.key); ret = CONFIG_NO_LOCK; 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 = CONFIG_INVALID_FILE; /* same as "invalid config file" */ goto out_free; } /* if nothing to unset, error out */ if (value == NULL) { ret = CONFIG_NOTHING_SET; 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 = CONFIG_INVALID_PATTERN; 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 = CONFIG_INVALID_FILE; 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 = CONFIG_NOTHING_SET; 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_str_in_full(fd, "\n") != 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 = CONFIG_NO_WRITE; 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 void create_tag(const unsigned char *object, const char *tag, struct strbuf *buf, struct create_tag_options *opt, unsigned char *prev, unsigned char *result) { enum object_type type; char header_buf[1024]; int header_len; char *path = NULL; type = sha1_object_info(object, NULL); if (type <= OBJ_NONE) die(_("bad object type.")); header_len = snprintf(header_buf, sizeof(header_buf), "object %s\n" "type %s\n" "tag %s\n" "tagger %s\n\n", sha1_to_hex(object), typename(type), tag, git_committer_info(IDENT_STRICT)); if (header_len > sizeof(header_buf) - 1) die(_("tag header too big.")); if (!opt->message_given) { int fd; /* write the template message before editing: */ path = git_pathdup("TAG_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) die_errno(_("could not create file '%s'"), path); if (!is_null_sha1(prev)) { write_tag_body(fd, prev); } else { struct strbuf buf = STRBUF_INIT; strbuf_addch(&buf, '\n'); if (opt->cleanup_mode == CLEANUP_ALL) strbuf_commented_addf(&buf, _(tag_template), tag, comment_line_char); else strbuf_commented_addf(&buf, _(tag_template_nocleanup), tag, comment_line_char); write_or_die(fd, buf.buf, buf.len); strbuf_release(&buf); } close(fd); if (launch_editor(path, buf, NULL)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } } if (opt->cleanup_mode != CLEANUP_NONE) strbuf_stripspace(buf, opt->cleanup_mode == CLEANUP_ALL); if (!opt->message_given && !buf->len) die(_("no tag message?")); strbuf_insert(buf, 0, header_buf, header_len); if (build_tag_object(buf, opt->sign, result) < 0) { if (path) fprintf(stderr, _("The tag message has been left in %s\n"), path); exit(128); } if (path) { unlink_or_warn(path); free(path); } }
/* return NULL on success, else hostname running the gc */ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) { static struct lock_file lock; static char locking_host[128]; char my_host[128]; struct strbuf sb = STRBUF_INIT; struct stat st; uintmax_t pid; FILE *fp; int fd, should_exit; if (pidfile) /* already locked */ return NULL; if (gethostname(my_host, sizeof(my_host))) strcpy(my_host, "unknown"); fd = hold_lock_file_for_update(&lock, git_path("gc.pid"), LOCK_DIE_ON_ERROR); if (!force) { fp = fopen(git_path("gc.pid"), "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = fp != NULL && !fstat(fileno(fp), &st) && /* * 12 hour limit is very generous as gc should * never take that long. On the other hand we * don't really need a strict limit here, * running gc --auto one day late is not a big * problem. --force can be used in manual gc * after the user verifies that no gc is * running. */ time(NULL) - st.st_mtime <= 12 * 3600 && fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); if (fp != NULL) fclose(fp); if (should_exit) { if (fd >= 0) rollback_lock_file(&lock); *ret_pid = pid; return locking_host; } } strbuf_addf(&sb, "%"PRIuMAX" %s", (uintmax_t) getpid(), my_host); write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); pidfile = git_pathdup("gc.pid"); sigchain_push_common(remove_pidfile_on_signal); atexit(remove_pidfile); return NULL; }
/* * 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 = git_pathdup("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; }
/* 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 = git_pathdup("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; char *output = buf; for (i = 0; buf[i] && isspace(buf[i]); i++) ; /* do nothing */ if (buf[i] == '[') { /* it's a section */ int offset = section_name_match(&buf[i], old_name); if (offset > 0) { 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; } /* * We wrote out the new section, with * a newline, now skip the old * section's length */ output += offset + i; if (strlen(output) > 0) { /* * More content means there's * a declaration to put on the * next line; indent with a * tab */ output -= 1; output[0] = '\t'; } } remove = 0; } if (remove) continue; length = strlen(output); if (write_in_full(out_fd, output, 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; }
int git_get_config(const char *key, char *buffer, int size) { const char *home, *system, *programdata; struct config_buf buf; struct git_config_source config_source = { 0 }; struct config_options opts = { 0 }; opts.respect_includes = 1; buf.buf=buffer; buf.size=size; buf.seen = 0; buf.key = key; if (have_git_dir()) { opts.git_dir = get_git_dir(); char* local = git_pathdup("config"); config_source.file = local; config_with_options(get_config, &buf, &config_source, &opts); free(local); if (buf.seen) return !buf.seen; } home = get_windows_home_directory(); if (home) { char* global = xstrdup(mkpath("%s/.gitconfig", home)); if (global) { config_source.file = global; config_with_options(get_config, &buf, &config_source, &opts); free(global); if (buf.seen) return !buf.seen; } char* globalxdg = xstrdup(mkpath("%s/.config/git/config", home)); if (globalxdg) { config_source.file = globalxdg; config_with_options(get_config, &buf, &config_source, &opts); free(globalxdg); if (buf.seen) return !buf.seen; } } system = git_etc_gitconfig(); if (system) { config_source.file = system; config_with_options(get_config, &buf, &config_source, &opts); if (buf.seen) return !buf.seen; } programdata = git_program_data_config(); if (programdata) { config_source.file = programdata; config_with_options(get_config, &buf, &config_source, &opts); } return !buf.seen; }
static int do_pick_commit(void) { unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; if (no_commit) { /* * We do not intend to commit immediately. We just want to * merge the differences in, so let's compute the tree * that represents the "current" state for merge-recursive * to work on. */ if (write_cache_as_tree(head, 0, NULL)) die ("Your index file is unmerged."); } else { if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); if (index_differs_from("HEAD", 0)) die_dirty_index(me); } discard_cache(); if (!commit->parents) { if (action == REVERT) die ("Cannot revert a root commit"); parent = NULL; } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; struct commit_list *p; if (!mainline) die("Commit %s is a merge but no -m option was given.", sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; cnt != mainline && p; cnt++) p = p->next; if (cnt != mainline || !p) die("Commit %s does not have parent %d", sha1_to_hex(commit->object.sha1), mainline); parent = p->item; } else if (0 < mainline) die("Mainline was specified but commit %s is not a merge.", sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; if (allow_ff && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) die("%s: cannot parse parent commit %s", me, sha1_to_hex(parent->object.sha1)); if (get_message(commit->buffer, &msg) != 0) die("Cannot get commit message for %s", sha1_to_hex(commit->object.sha1)); /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" * on top of the current HEAD if we are cherry-pick. Or the * reverse of it if we are revert. */ defmsg = git_pathdup("MERGE_MSG"); if (action == REVERT) { base = commit; base_label = msg.label; next = parent; next_label = msg.parent_label; strbuf_addstr(&msgbuf, "Revert \""); strbuf_addstr(&msgbuf, msg.subject); strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents->next) { strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } strbuf_addstr(&msgbuf, ".\n"); } else { base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; set_author_ident_env(msg.message); add_message_to_msg(&msgbuf, msg.message); if (no_replay) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } } if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, defmsg); else { int res; struct commit_list *common = NULL; struct commit_list *remotes = NULL; write_message(&msgbuf, defmsg); commit_list_insert(base, &common); commit_list_insert(next, &remotes); res = try_merge_command(strategy, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); if (res) { fprintf(stderr, "Automatic %s with strategy %s failed.%s\n", me, strategy, help_msg()); rerere(allow_rerere_auto); exit(1); } } free_message(&msg); /* * * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original * author date and name. * If we are revert, or if our cherry-pick results in a hand merge, * we had better say that the current user is responsible for that. */ if (!no_commit) { /* 6 is max possible length of our args array including NULL */ const char *args[6]; int res; int i = 0; args[i++] = "commit"; args[i++] = "-n"; if (signoff) args[i++] = "-s"; if (!edit) { args[i++] = "-F"; args[i++] = defmsg; } args[i] = NULL; res = run_command_v_opt(args, RUN_GIT_CMD); free(defmsg); return res; } free(defmsg); return 0; }
static void create_note(const unsigned char *object, struct msg_arg *msg, int append_only, const unsigned char *prev, unsigned char *result) { char *path = NULL; if (msg->use_editor || !msg->given) { int fd; /* write the template message before editing: */ path = git_pathdup("NOTES_EDITMSG"); fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (fd < 0) die_errno("could not create file '%s'", path); if (msg->given) write_or_die(fd, msg->buf.buf, msg->buf.len); else if (prev && !append_only) write_note_data(fd, prev); write_or_die(fd, note_template, strlen(note_template)); write_commented_object(fd, object); close(fd); strbuf_reset(&(msg->buf)); if (launch_editor(path, &(msg->buf), NULL)) { die("Please supply the note contents using either -m" \ " or -F option"); } stripspace(&(msg->buf), 1); } if (prev && append_only) { /* Append buf to previous note contents */ unsigned long size; enum object_type type; char *prev_buf = read_sha1_file(prev, &type, &size); strbuf_grow(&(msg->buf), size + 1); if (msg->buf.len && prev_buf && size) strbuf_insert(&(msg->buf), 0, "\n", 1); if (prev_buf && size) strbuf_insert(&(msg->buf), 0, prev_buf, size); free(prev_buf); } if (!msg->buf.len) { fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); hashclr(result); } else { if (write_sha1_file(msg->buf.buf, msg->buf.len, blob_type, result)) { error("unable to write note object"); if (path) error("The note contents has been left in %s", path); exit(128); } } if (path) { unlink_or_warn(path); free(path); } }
static int get_value(const char *key_, const char *regex_) { int ret = -1; char *global = NULL, *repo_config = NULL; const char *system_wide = NULL, *local; local = config_exclusive_filename; if (!local) { const char *home = getenv("HOME"); local = repo_config = git_pathdup("config"); if (home) global = xstrdup(mkpath("%s/.gitconfig", home)); if (git_config_system()) system_wide = git_etc_gitconfig(); } if (use_key_regexp) { char *tl; /* * NEEDSWORK: this naive pattern lowercasing obviously does not * work for more complex patterns like "^[^.]*Foo.*bar". * Perhaps we should deprecate this altogether someday. */ key = xstrdup(key_); for (tl = key + strlen(key) - 1; tl >= key && *tl != '.'; tl--) *tl = tolower(*tl); for (tl = key; *tl && *tl != '.'; tl++) *tl = tolower(*tl); key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); free(key); goto free_strings; } } else { if (git_config_parse_key(key_, &key, NULL)) goto free_strings; } if (regex_) { if (regex_[0] == '!') { do_not_match = 1; regex_++; } regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); goto free_strings; } } if (do_all && system_wide) git_config_from_file(show_config, system_wide, NULL); if (do_all && global) git_config_from_file(show_config, global, NULL); if (do_all) git_config_from_file(show_config, local, NULL); git_config_from_parameters(show_config, NULL); if (!do_all && !seen) git_config_from_file(show_config, local, NULL); if (!do_all && !seen && global) git_config_from_file(show_config, global, NULL); if (!do_all && !seen && system_wide) git_config_from_file(show_config, system_wide, NULL); free(key); if (regexp) { regfree(regexp); free(regexp); } if (do_all) ret = !seen; else ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; free_strings: free(repo_config); free(global); return ret; }
static int do_pick_commit(struct commit *commit, struct replay_opts *opts) { unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res, unborn = 0, allow; if (opts->no_commit) { /* * We do not intend to commit immediately. We just want to * merge the differences in, so let's compute the tree * that represents the "current" state for merge-recursive * to work on. */ if (write_cache_as_tree(head, 0, NULL)) die (_("Your index file is unmerged.")); } else { unborn = get_sha1("HEAD", head); if (unborn) hashcpy(head, EMPTY_TREE_SHA1_BIN); if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0)) return error_dirty_index(opts); } discard_cache(); if (!commit->parents) { parent = NULL; } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; struct commit_list *p; if (!opts->mainline) return error(_("Commit %s is a merge but no -m option was given."), sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; cnt != opts->mainline && p; cnt++) p = p->next; if (cnt != opts->mainline || !p) return error(_("Commit %s does not have parent %d"), sha1_to_hex(commit->object.sha1), opts->mainline); parent = p->item; } else if (0 < opts->mainline) return error(_("Mainline was specified but commit %s is not a merge."), sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; if (opts->allow_ff && ((parent && !hashcmp(parent->object.sha1, head)) || (!parent && unborn))) return fast_forward_to(commit->object.sha1, head, unborn, opts); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or "cherry-pick", the second %s a SHA1 */ return error(_("%s: cannot parse parent commit %s"), action_name(opts), sha1_to_hex(parent->object.sha1)); if (get_message(commit, &msg) != 0) return error(_("Cannot get commit message for %s"), sha1_to_hex(commit->object.sha1)); /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" * on top of the current HEAD if we are cherry-pick. Or the * reverse of it if we are revert. */ defmsg = git_pathdup("MERGE_MSG"); if (opts->action == REPLAY_REVERT) { base = commit; base_label = msg.label; next = parent; next_label = msg.parent_label; strbuf_addstr(&msgbuf, "Revert \""); strbuf_addstr(&msgbuf, msg.subject); strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents && commit->parents->next) { strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } strbuf_addstr(&msgbuf, ".\n"); } else { const char *p; base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; /* * Append the commit log message to msgbuf; it starts * after the tree, parent, author, committer * information followed by "\n\n". */ p = strstr(msg.message, "\n\n"); if (p) { p += 2; strbuf_addstr(&msgbuf, p); } if (opts->record_origin) { if (!has_conforming_footer(&msgbuf, NULL, 0)) strbuf_addch(&msgbuf, '\n'); strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } } if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REPLAY_REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf, opts); write_message(&msgbuf, defmsg); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; write_message(&msgbuf, defmsg); commit_list_insert(base, &common); commit_list_insert(next, &remotes); res = try_merge_command(opts->strategy, opts->xopts_nr, opts->xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } /* * If the merge was clean or if it failed due to conflict, we write * CHERRY_PICK_HEAD for the subsequent invocation of commit to use. * However, if the merge did not even start, then we don't want to * write it at all. */ if (opts->action == REPLAY_PICK && !opts->no_commit && (res == 0 || res == 1)) write_cherry_pick_head(commit, "CHERRY_PICK_HEAD"); if (opts->action == REPLAY_REVERT && ((opts->no_commit && res == 0) || res == 1)) write_cherry_pick_head(commit, "REVERT_HEAD"); if (res) { error(opts->action == REPLAY_REVERT ? _("could not revert %s... %s") : _("could not apply %s... %s"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); print_advice(res == 1, opts); rerere(opts->allow_rerere_auto); goto leave; } allow = allow_empty(opts, commit); if (allow < 0) { res = allow; goto leave; } if (!opts->no_commit) res = run_git_commit(defmsg, opts, allow); leave: free_message(&msg); free(defmsg); return res; }
/* return NULL on success, else hostname running the gc */ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) { struct lock_file lock = LOCK_INIT; char my_host[HOST_NAME_MAX + 1]; struct strbuf sb = STRBUF_INIT; struct stat st; uintmax_t pid; FILE *fp; int fd; char *pidfile_path; if (is_tempfile_active(pidfile)) /* already locked */ return NULL; if (xgethostname(my_host, sizeof(my_host))) xsnprintf(my_host, sizeof(my_host), "unknown"); pidfile_path = git_pathdup("gc.pid"); fd = hold_lock_file_for_update(&lock, pidfile_path, LOCK_DIE_ON_ERROR); if (!force) { static char locking_host[HOST_NAME_MAX + 1]; static char *scan_fmt; int should_exit; if (!scan_fmt) scan_fmt = xstrfmt("%s %%%ds", "%"SCNuMAX, HOST_NAME_MAX); fp = fopen(pidfile_path, "r"); memset(locking_host, 0, sizeof(locking_host)); should_exit = fp != NULL && !fstat(fileno(fp), &st) && /* * 12 hour limit is very generous as gc should * never take that long. On the other hand we * don't really need a strict limit here, * running gc --auto one day late is not a big * problem. --force can be used in manual gc * after the user verifies that no gc is * running. */ time(NULL) - st.st_mtime <= 12 * 3600 && fscanf(fp, scan_fmt, &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); if (fp != NULL) fclose(fp); if (should_exit) { if (fd >= 0) rollback_lock_file(&lock); *ret_pid = pid; free(pidfile_path); return locking_host; } } strbuf_addf(&sb, "%"PRIuMAX" %s", (uintmax_t) getpid(), my_host); write_in_full(fd, sb.buf, sb.len); strbuf_release(&sb); commit_lock_file(&lock); pidfile = register_tempfile(pidfile_path); free(pidfile_path); return NULL; }
/* 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 = git_pathdup("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; }
static int do_pick_commit(void) { unsigned char head[20]; struct commit *base, *next, *parent; const char *base_label, *next_label; struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; int res; if (no_commit) { /* * We do not intend to commit immediately. We just want to * merge the differences in, so let's compute the tree * that represents the "current" state for merge-recursive * to work on. */ if (write_cache_as_tree(head, 0, NULL)) die ("Your index file is unmerged."); } else { if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); if (index_differs_from("HEAD", 0)) die_dirty_index(me); } discard_cache(); if (!commit->parents) { if (action == REVERT) die ("Cannot revert a root commit"); parent = NULL; } else if (commit->parents->next) { /* Reverting or cherry-picking a merge commit */ int cnt; struct commit_list *p; if (!mainline) die("Commit %s is a merge but no -m option was given.", sha1_to_hex(commit->object.sha1)); for (cnt = 1, p = commit->parents; cnt != mainline && p; cnt++) p = p->next; if (cnt != mainline || !p) die("Commit %s does not have parent %d", sha1_to_hex(commit->object.sha1), mainline); parent = p->item; } else if (0 < mainline) die("Mainline was specified but commit %s is not a merge.", sha1_to_hex(commit->object.sha1)); else parent = commit->parents->item; if (allow_ff && parent && !hashcmp(parent->object.sha1, head)) return fast_forward_to(commit->object.sha1, head); if (parent && parse_commit(parent) < 0) die("%s: cannot parse parent commit %s", me, sha1_to_hex(parent->object.sha1)); if (get_message(commit->buffer, &msg) != 0) die("Cannot get commit message for %s", sha1_to_hex(commit->object.sha1)); /* * "commit" is an existing commit. We would want to apply * the difference it introduces since its first parent "prev" * on top of the current HEAD if we are cherry-pick. Or the * reverse of it if we are revert. */ defmsg = git_pathdup("MERGE_MSG"); if (action == REVERT) { base = commit; base_label = msg.label; next = parent; next_label = msg.parent_label; strbuf_addstr(&msgbuf, "Revert \""); strbuf_addstr(&msgbuf, msg.subject); strbuf_addstr(&msgbuf, "\"\n\nThis reverts commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); if (commit->parents->next) { strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); strbuf_addstr(&msgbuf, sha1_to_hex(parent->object.sha1)); } strbuf_addstr(&msgbuf, ".\n"); } else { base = parent; base_label = msg.parent_label; next = commit; next_label = msg.label; add_message_to_msg(&msgbuf, msg.message); if (no_replay) { strbuf_addstr(&msgbuf, "(cherry picked from commit "); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } if (!no_commit) write_cherry_pick_head(); } if (!strategy || !strcmp(strategy, "recursive") || action == REVERT) { res = do_recursive_merge(base, next, base_label, next_label, head, &msgbuf); write_message(&msgbuf, defmsg); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; write_message(&msgbuf, defmsg); commit_list_insert(base, &common); commit_list_insert(next, &remotes); res = try_merge_command(strategy, xopts_nr, xopts, common, sha1_to_hex(head), remotes); free_commit_list(common); free_commit_list(remotes); } if (res) { error("could not %s %s... %s", action == REVERT ? "revert" : "apply", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), msg.subject); print_advice(); rerere(allow_rerere_auto); } else { if (!no_commit) res = run_git_commit(defmsg); } free_message(&msg); free(defmsg); return res; }
/* * N-way merge "len" trees. Returns 0 on success, -1 on failure to manipulate the * resulting index, -2 on failure to reflect the changes to the work tree. * * CE_ADDED, CE_UNPACKED and CE_NEW_SKIP_WORKTREE are used internally */ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) { int i, ret; static struct cache_entry *dfc; struct exclude_list el; if (len > MAX_UNPACK_TREES) die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); memset(&state, 0, sizeof(state)); state.base_dir = ""; state.force = 1; state.quiet = 1; state.refresh_cache = 1; state.istate = &o->result; memset(&el, 0, sizeof(el)); if (!core_apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout) { char *sparse = git_pathdup("info/sparse-checkout"); if (add_excludes_from_file_to_list(sparse, "", 0, &el, 0) < 0) o->skip_sparse_checkout = 1; else o->el = ⪙ free(sparse); } memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; o->result.timestamp.sec = o->src_index->timestamp.sec; o->result.timestamp.nsec = o->src_index->timestamp.nsec; o->result.version = o->src_index->version; o->result.split_index = o->src_index->split_index; if (o->result.split_index) o->result.split_index->refcount++; hashcpy(o->result.sha1, o->src_index->sha1); o->merge_size = len; mark_all_ce_unused(o->src_index); /* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries */ if (!o->skip_sparse_checkout) mark_new_skip_worktree(o->el, o->src_index, 0, CE_NEW_SKIP_WORKTREE); if (!dfc) dfc = xcalloc(1, cache_entry_size(0)); o->df_conflict_entry = dfc; if (len) { const char *prefix = o->prefix ? o->prefix : ""; struct traverse_info info; setup_traverse_info(&info, prefix); info.fn = unpack_callback; info.data = o; info.show_all_errors = o->show_all_errors; info.pathspec = o->pathspec; if (o->prefix) { /* * Unpack existing index entries that sort before the * prefix the tree is spliced into. Note that o->merge * is always true in this case. */ while (1) { struct cache_entry *ce = next_cache_entry(o); if (!ce) break; if (ce_in_traverse_path(ce, &info)) break; if (unpack_index_entry(ce, o) < 0) goto return_failed; } } if (traverse_trees(len, t, &info) < 0) goto return_failed; } /* Any left-over entries in the index? */ if (o->merge) { while (1) { struct cache_entry *ce = next_cache_entry(o); if (!ce) break; if (unpack_index_entry(ce, o) < 0) goto return_failed; } } mark_all_ce_unused(o->src_index); if (o->trivial_merges_only && o->nontrivial_merge) { ret = unpack_failed(o, "Merge requires file-level merging"); goto done; } if (!o->skip_sparse_checkout) { int empty_worktree = 1; /* * Sparse checkout loop #2: set NEW_SKIP_WORKTREE on entries not in loop #1 * If the will have NEW_SKIP_WORKTREE, also set CE_SKIP_WORKTREE * so apply_sparse_checkout() won't attempt to remove it from worktree */ mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE); ret = 0; for (i = 0; i < o->result.cache_nr; i++) { struct cache_entry *ce = o->result.cache[i]; /* * Entries marked with CE_ADDED in merged_entry() do not have * verify_absent() check (the check is effectively disabled * because CE_NEW_SKIP_WORKTREE is set unconditionally). * * Do the real check now because we have had * correct CE_NEW_SKIP_WORKTREE */ if (ce->ce_flags & CE_ADDED && verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) { if (!o->show_all_errors) goto return_failed; ret = -1; } if (apply_sparse_checkout(&o->result, ce, o)) { if (!o->show_all_errors) goto return_failed; ret = -1; } if (!ce_skip_worktree(ce)) empty_worktree = 0; } if (ret < 0) goto return_failed; /* * Sparse checkout is meant to narrow down checkout area * but it does not make sense to narrow down to empty working * tree. This is usually a mistake in sparse checkout rules. * Do not allow users to do that. */ if (o->result.cache_nr && empty_worktree) { ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory"); goto done; } } o->src_index = NULL; ret = check_updates(o) ? (-2) : 0; if (o->dst_index) { if (!ret) { if (!o->result.cache_tree) o->result.cache_tree = cache_tree(); if (!cache_tree_fully_valid(o->result.cache_tree)) cache_tree_update(&o->result, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); } discard_index(o->dst_index); *o->dst_index = o->result; } else { discard_index(&o->result); } done: clear_exclude_list(&el); return ret; return_failed: if (o->show_all_errors) display_error_msgs(o); mark_all_ce_unused(o->src_index); ret = unpack_failed(o, NULL); if (o->exiting_early) ret = 0; goto done; }
/* * Check a single unreachable object */ static void check_unreachable_object(struct object *obj) { /* * Missing unreachable object? Ignore it. It's not like * we miss it (since it can't be reached), nor do we want * to complain about it being unreachable (since it does * not exist). */ if (!(obj->flags & HAS_OBJ)) return; /* * Unreachable object that exists? Show it if asked to, * since this is something that is prunable. */ if (show_unreachable) { printf("unreachable %s %s\n", printable_type(obj), describe_object(obj)); return; } /* * "!USED" means that nothing at all points to it, including * other unreachable objects. In other words, it's the "tip" * of some set of unreachable objects, usually a commit that * got dropped. * * Such starting points are more interesting than some random * set of unreachable objects, so we show them even if the user * hasn't asked for _all_ unreachable objects. If you have * deleted a branch by mistake, this is a prime candidate to * start looking at, for example. */ if (!(obj->flags & USED)) { if (show_dangling) printf("dangling %s %s\n", printable_type(obj), describe_object(obj)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", describe_object(obj)); FILE *f; if (safe_create_leading_directories_const(filename)) { error("Could not create lost-found"); free(filename); return; } f = xfopen(filename, "w"); if (obj->type == OBJ_BLOB) { if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno("Could not write '%s'", filename); } else fprintf(f, "%s\n", describe_object(obj)); if (fclose(f)) die_errno("Could not finish '%s'", filename); free(filename); } return; } /* * Otherwise? It's there, it's unreachable, and some other unreachable * object points to it. Ignore it - it's not interesting, and we showed * all the interesting cases above. */ }