void print_commit(struct commit *commit, struct rev_info *revs) { struct commitinfo *info; int cols = revs->graph ? 3 : 2; struct strbuf graphbuf = STRBUF_INIT; struct strbuf msgbuf = STRBUF_INIT; if (ctx.repo->enable_log_filecount) cols++; if (ctx.repo->enable_log_linecount) cols++; if (revs->graph) { /* Advance graph until current commit */ while (!graph_next_line(revs->graph, &graphbuf)) { /* Print graph segment in otherwise empty table row */ html("<tr class='nohover'><td class='commitgraph'>"); html(graphbuf.buf); htmlf("</td><td colspan='%d' /></tr>\n", cols); strbuf_setlen(&graphbuf, 0); } /* Current commit's graph segment is now ready in graphbuf */ } info = cgit_parse_commit(commit); htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : ""); if (revs->graph) { /* Print graph segment for current commit */ html("<td class='commitgraph'>"); html(graphbuf.buf); html("</td>"); strbuf_setlen(&graphbuf, 0); } else { html("<td>"); cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); html("</td>"); } htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : ""); if (ctx.qry.showmsg) { /* line-wrap long commit subjects instead of truncating them */ size_t subject_len = strlen(info->subject); if (subject_len > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { /* symbol for signaling line-wrap (in PAGE_ENCODING) */ const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 }; int i = ctx.cfg.max_msg_len - strlen(wrap_symbol); /* Rewind i to preceding space character */ while (i > 0 && !isspace(info->subject[i])) --i; if (!i) /* Oops, zero spaces. Reset i */ i = ctx.cfg.max_msg_len - strlen(wrap_symbol); /* add remainder starting at i to msgbuf */ strbuf_add(&msgbuf, info->subject + i, subject_len - i); strbuf_trim(&msgbuf); strbuf_add(&msgbuf, "\n\n", 2); /* Place wrap_symbol at position i in info->subject */ strcpy(info->subject + i, wrap_symbol); } } cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); show_commit_decorations(commit); html("</td><td>"); html_txt(info->author); if (revs->graph) { html("</td><td>"); cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); } if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) { files = 0; add_lines = 0; rem_lines = 0; cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); } if (ctx.repo->enable_log_filecount) htmlf("</td><td>%d", files); if (ctx.repo->enable_log_linecount) htmlf("</td><td>-%d/+%d", rem_lines, add_lines); html("</td></tr>\n"); if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */ html("<tr class='nohover'>"); if (ctx.qry.showmsg) { /* Concatenate commit message + notes in msgbuf */ if (info->msg && *(info->msg)) { strbuf_addstr(&msgbuf, info->msg); strbuf_addch(&msgbuf, '\n'); } format_note(NULL, commit->object.sha1, &msgbuf, PAGE_ENCODING, NOTES_SHOW_HEADER | NOTES_INDENT); strbuf_addch(&msgbuf, '\n'); strbuf_ltrim(&msgbuf); } if (revs->graph) { int lines = 0; /* Calculate graph padding */ if (ctx.qry.showmsg) { /* Count #lines in commit message + notes */ const char *p = msgbuf.buf; lines = 1; while ((p = strchr(p, '\n'))) { p++; lines++; } } /* Print graph padding */ html("<td class='commitgraph'>"); while (lines > 0 || !graph_is_commit_finished(revs->graph)) { if (graphbuf.len) html("\n"); strbuf_setlen(&graphbuf, 0); graph_next_line(revs->graph, &graphbuf); html(graphbuf.buf); lines--; } html("</td>\n"); } else html("<td/>"); /* Empty 'Age' column */ /* Print msgbuf into remainder of table row */ htmlf("<td colspan='%d'%s>\n", cols, ctx.qry.showmsg ? " class='logmsg'" : ""); html_txt(msgbuf.buf); html("</td></tr>\n"); } strbuf_release(&msgbuf); strbuf_release(&graphbuf); cgit_free_commitinfo(info); }
/* * Move the iterator to the next record in the snapshot, without * respect for whether the record is actually required by the current * iteration. Adjust the fields in `iter` and return `ITER_OK` or * `ITER_DONE`. This function does not free the iterator in the case * of `ITER_DONE`. */ static int next_record(struct packed_ref_iterator *iter) { const char *p = iter->pos, *eol; strbuf_reset(&iter->refname_buf); if (iter->pos == iter->eof) return ITER_DONE; iter->base.flags = REF_ISPACKED; if (iter->eof - p < GIT_SHA1_HEXSZ + 2 || parse_oid_hex(p, &iter->oid, &p) || !isspace(*p++)) die_invalid_line(iter->snapshot->refs->path, iter->pos, iter->eof - iter->pos); eol = memchr(p, '\n', iter->eof - p); if (!eol) die_unterminated_line(iter->snapshot->refs->path, iter->pos, iter->eof - iter->pos); strbuf_add(&iter->refname_buf, p, eol - p); iter->base.refname = iter->refname_buf.buf; if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) { if (!refname_is_safe(iter->base.refname)) die("packed refname is dangerous: %s", iter->base.refname); oidclr(&iter->oid); iter->base.flags |= REF_BAD_NAME | REF_ISBROKEN; } if (iter->snapshot->peeled == PEELED_FULLY || (iter->snapshot->peeled == PEELED_TAGS && starts_with(iter->base.refname, "refs/tags/"))) iter->base.flags |= REF_KNOWS_PEELED; iter->pos = eol + 1; if (iter->pos < iter->eof && *iter->pos == '^') { p = iter->pos + 1; if (iter->eof - p < GIT_SHA1_HEXSZ + 1 || parse_oid_hex(p, &iter->peeled, &p) || *p++ != '\n') die_invalid_line(iter->snapshot->refs->path, iter->pos, iter->eof - iter->pos); iter->pos = p; /* * Regardless of what the file header said, we * definitely know the value of *this* reference. But * we suppress it if the reference is broken: */ if ((iter->base.flags & REF_ISBROKEN)) { oidclr(&iter->peeled); iter->base.flags &= ~REF_KNOWS_PEELED; } else { iter->base.flags |= REF_KNOWS_PEELED; } } else { oidclr(&iter->peeled); } return ITER_OK; }
static void start_put(struct transfer_request *request) { char *hex = oid_to_hex(&request->obj->oid); struct active_request_slot *slot; struct strbuf buf = STRBUF_INIT; enum object_type type; char hdr[50]; void *unpacked; unsigned long len; int hdrlen; ssize_t size; git_zstream stream; unpacked = read_sha1_file(request->obj->oid.hash, &type, &len); hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %lu", typename(type), len) + 1; /* Set it up */ git_deflate_init(&stream, zlib_compression_level); size = git_deflate_bound(&stream, len + hdrlen); strbuf_init(&request->buffer.buf, size); request->buffer.posn = 0; /* Compress it */ stream.next_out = (unsigned char *)request->buffer.buf.buf; stream.avail_out = size; /* First header.. */ stream.next_in = (void *)hdr; stream.avail_in = hdrlen; while (git_deflate(&stream, 0) == Z_OK) ; /* nothing */ /* Then the data itself.. */ stream.next_in = unpacked; stream.avail_in = len; while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ git_deflate_end(&stream); free(unpacked); request->buffer.buf.len = stream.total_out; strbuf_addstr(&buf, "Destination: "); append_remote_object_url(&buf, repo->url, hex, 0); request->dest = strbuf_detach(&buf, NULL); append_remote_object_url(&buf, repo->url, hex, 0); strbuf_add(&buf, request->lock->tmpfile_suffix, 41); request->url = strbuf_detach(&buf, NULL); slot = get_active_slot(); slot->callback_func = process_response; slot->callback_data = request; curl_setup_http(slot->curl, request->url, DAV_PUT, &request->buffer, fwrite_null); if (start_active_slot(slot)) { request->slot = slot; request->state = RUN_PUT; } else { request->state = ABORTED; FREE_AND_NULL(request->url); } }
/* * First, one directory to try is determined by the following algorithm. * * (0) If "strict" is given, the path is used as given and no DWIM is * done. Otherwise: * (1) "~/path" to mean path under the running user's home directory; * (2) "~user/path" to mean path under named user's home directory; * (3) "relative/path" to mean cwd relative directory; or * (4) "/absolute/path" to mean absolute directory. * * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git" * in this order. We select the first one that is a valid git repository, and * chdir() to it. If none match, or we fail to chdir, we return NULL. * * If all goes well, we return the directory we used to chdir() (but * before ~user is expanded), avoiding getcwd() resolving symbolic * links. User relative paths are also returned as they are given, * except DWIM suffixing. */ const char *enter_repo(const char *path, int strict) { static struct strbuf validated_path = STRBUF_INIT; static struct strbuf used_path = STRBUF_INIT; if (!path) return NULL; if (!strict) { static const char *suffix[] = { "/.git", "", ".git/.git", ".git", NULL, }; const char *gitfile; int len = strlen(path); int i; while ((1 < len) && (path[len-1] == '/')) len--; /* * We can handle arbitrary-sized buffers, but this remains as a * sanity check on untrusted input. */ if (PATH_MAX <= len) return NULL; strbuf_reset(&used_path); strbuf_reset(&validated_path); strbuf_add(&used_path, path, len); strbuf_add(&validated_path, path, len); if (used_path.buf[0] == '~') { char *newpath = expand_user_path(used_path.buf, 0); if (!newpath) return NULL; strbuf_attach(&used_path, newpath, strlen(newpath), strlen(newpath)); } for (i = 0; suffix[i]; i++) { struct stat st; size_t baselen = used_path.len; strbuf_addstr(&used_path, suffix[i]); if (!stat(used_path.buf, &st) && (S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) { strbuf_addstr(&validated_path, suffix[i]); break; } strbuf_setlen(&used_path, baselen); } if (!suffix[i]) return NULL; gitfile = read_gitfile(used_path.buf); if (gitfile) { strbuf_reset(&used_path); strbuf_addstr(&used_path, gitfile); } if (chdir(used_path.buf)) return NULL; path = validated_path.buf; } else { const char *gitfile = read_gitfile(path); if (gitfile) path = gitfile; if (chdir(path)) return NULL; } if (is_git_directory(".")) { set_git_dir("."); check_repository_format(); return path; } return NULL; }
void pp_user_info(const struct pretty_print_context *pp, const char *what, struct strbuf *sb, const char *line, const char *encoding) { char *date; int namelen; unsigned long time; int tz; if (pp->fmt == CMIT_FMT_ONELINE) return; date = strchr(line, '>'); if (!date) return; namelen = ++date - line; time = strtoul(date, &date, 10); tz = strtol(date, NULL, 10); if (pp->fmt == CMIT_FMT_EMAIL) { char *name_tail = strchr(line, '<'); int display_name_length; int final_line; if (!name_tail) return; while (line < name_tail && isspace(name_tail[-1])) name_tail--; display_name_length = name_tail - line; strbuf_addstr(sb, "From: "); if (!has_rfc822_specials(line, display_name_length)) { add_rfc2047(sb, line, display_name_length, encoding); } else { struct strbuf quoted = STRBUF_INIT; add_rfc822_quoted("ed, line, display_name_length); add_rfc2047(sb, quoted.buf, quoted.len, encoding); strbuf_release("ed); } for (final_line = 0; final_line < sb->len; final_line++) if (sb->buf[sb->len - final_line - 1] == '\n') break; if (namelen - display_name_length + final_line > 78) { strbuf_addch(sb, '\n'); if (!isspace(name_tail[0])) strbuf_addch(sb, ' '); } strbuf_add(sb, name_tail, namelen - display_name_length); strbuf_addch(sb, '\n'); } else { strbuf_addf(sb, "%s: %.*s%.*s\n", what, (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, " ", namelen, line); } switch (pp->fmt) { case CMIT_FMT_MEDIUM: strbuf_addf(sb, "Date: %s\n", show_date(time, tz, pp->date_mode)); break; case CMIT_FMT_EMAIL: strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822)); break; case CMIT_FMT_FULLER: strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, pp->date_mode)); break; default: /* notin' */ break; } }
static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; unsigned char result_sha1[20]; struct notes_tree *t; struct notes_merge_options o; int do_merge = 0, do_commit = 0, do_abort = 0; int verbosity = 0, result; const char *strategy = NULL; struct option options[] = { OPT_GROUP("General options"), OPT__VERBOSITY(&verbosity), OPT_GROUP("Merge options"), OPT_STRING('s', "strategy", &strategy, "strategy", "resolve notes conflicts using the given strategy " "(manual/ours/theirs/union/cat_sort_uniq)"), OPT_GROUP("Committing unmerged notes"), { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, "finalize notes merge by committing unmerged notes", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_GROUP("Aborting notes merge resolution"), { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, "abort notes merge", PARSE_OPT_NOARG | PARSE_OPT_NONEG }, OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_notes_merge_usage, 0); if (strategy || do_commit + do_abort == 0) do_merge = 1; if (do_merge + do_commit + do_abort != 1) { error("cannot mix --commit, --abort or -s/--strategy"); usage_with_options(git_notes_merge_usage, options); } if (do_merge && argc != 1) { error("Must specify a notes ref to merge"); usage_with_options(git_notes_merge_usage, options); } else if (!do_merge && argc) { error("too many parameters"); usage_with_options(git_notes_merge_usage, options); } init_notes_merge_options(&o); o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; if (do_abort) return merge_abort(&o); if (do_commit) return merge_commit(&o); o.local_ref = default_notes_ref(); strbuf_addstr(&remote_ref, argv[0]); expand_notes_ref(&remote_ref); o.remote_ref = remote_ref.buf; if (strategy) { if (!strcmp(strategy, "manual")) o.strategy = NOTES_MERGE_RESOLVE_MANUAL; else if (!strcmp(strategy, "ours")) o.strategy = NOTES_MERGE_RESOLVE_OURS; else if (!strcmp(strategy, "theirs")) o.strategy = NOTES_MERGE_RESOLVE_THEIRS; else if (!strcmp(strategy, "union")) o.strategy = NOTES_MERGE_RESOLVE_UNION; else if (!strcmp(strategy, "cat_sort_uniq")) o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; else { error("Unknown -s/--strategy: %s", strategy); usage_with_options(git_notes_merge_usage, options); } } t = init_notes_check("merge"); strbuf_addf(&msg, "notes: Merged notes from %s into %s", remote_ref.buf, default_notes_ref()); strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ result = notes_merge(&o, t, result_sha1); if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ /* Update default notes ref with new commit */ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, 0, DIE_ON_ERR); else { /* Merge has unresolved conflicts */ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, 0, DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die("Failed to store link to current notes ref (%s)", default_notes_ref()); printf("Automatic notes merge failed. Fix conflicts in %s and " "commit the result with 'git notes merge --commit', or " "abort the merge with 'git notes merge --abort'.\n", git_path(NOTES_MERGE_WORKTREE)); } free_notes(t); strbuf_release(&remote_ref); strbuf_release(&msg); return result < 0; /* return non-zero on conflicts */ }
static int prepare_to_commit(const char *index_file, const char *prefix, struct wt_status *s) { struct stat statbuf; int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; FILE *fp; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int ident_shown = 0; if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; if (message.len) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) fprintf(stderr, "(reading log message from standard input)\n"); if (strbuf_read(&sb, 0, 0) < 0) die_errno("could not read log from standard input"); hook_arg1 = "message"; } else if (logfile) { if (strbuf_read_file(&sb, logfile, 0) < 0) die_errno("could not read log file '%s'", logfile); hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); if (!buffer || buffer[2] == '\0') die("commit has empty message"); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) die_errno("could not read MERGE_MSG"); hook_arg1 = "merge"; } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) die_errno("could not read SQUASH_MSG"); hook_arg1 = "squash"; } else if (template_file && !stat(template_file, &statbuf)) { if (strbuf_read_file(&sb, template_file, 0) < 0) die_errno("could not read '%s'", template_file); hook_arg1 = "template"; } /* * This final case does not modify the template message, * it just sets the argument to the prepare-commit-msg hook. */ else if (in_merge) hook_arg1 = "merge"; fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) die_errno("could not open '%s'", git_path(commit_editmsg)); if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, 0); if (signoff) { struct strbuf sob = STRBUF_INIT; int i; strbuf_addstr(&sob, sign_off_header); strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--) ; /* do nothing */ if (prefixcmp(sb.buf + i, sob.buf)) { if (!i || !ends_rfc2822_footer(&sb)) strbuf_addch(&sb, '\n'); strbuf_addbuf(&sb, &sob); } strbuf_release(&sob); } if (fwrite(sb.buf, 1, sb.len, fp) < sb.len) die_errno("could not write commit template"); strbuf_release(&sb); determine_author_info(); /* This checks if committer ident is explicitly given */ git_committer_info(0); if (use_editor && include_status) { char *author_ident; const char *committer_ident; if (in_merge) fprintf(fp, "#\n" "# It looks like you may be committing a MERGE.\n" "# If this is not correct, please remove the file\n" "# %s\n" "# and try again.\n" "#\n", git_path("MERGE_HEAD")); fprintf(fp, "\n" "# Please enter the commit message for your changes."); if (cleanup_mode == CLEANUP_ALL) fprintf(fp, " Lines starting\n" "# with '#' will be ignored, and an empty" " message aborts the commit.\n"); else /* CLEANUP_SPACE, that is. */ fprintf(fp, " Lines starting\n" "# with '#' will be kept; you may remove them" " yourself if you want to.\n" "# An empty message aborts the commit.\n"); if (only_include_assumed) fprintf(fp, "# %s\n", only_include_assumed); author_ident = xstrdup(fmt_name(author_name, author_email)); committer_ident = fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL")); if (strcmp(author_ident, committer_ident)) fprintf(fp, "%s" "# Author: %s\n", ident_shown++ ? "" : "#\n", author_ident); free(author_ident); if (!user_ident_sufficiently_given()) fprintf(fp, "%s" "# Committer: %s\n", ident_shown++ ? "" : "#\n", committer_ident); if (ident_shown) fprintf(fp, "#\n"); saved_color_setting = s->use_color; s->use_color = 0; commitable = run_status(fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; } else { unsigned char sha1[20]; const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) die("Cannot read index"); if (amend) parent = "HEAD^1"; if (get_sha1(parent, sha1)) commitable = !!active_nr; else commitable = index_differs_from(parent, 0); } fclose(fp); if (!commitable && !in_merge && !allow_empty && !(amend && is_a_merge(head_sha1))) { run_status(stdout, index_file, prefix, 0, s); return 0; } /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke * the editor and after we invoke run_status above. */ discard_cache(); read_cache_from(index_file); if (!active_cache_tree) active_cache_tree = cache_tree(); if (cache_tree_update(active_cache_tree, active_cache, active_nr, 0, 0) < 0) { error("Error building trees"); return 0; } if (run_hook(index_file, "prepare-commit-msg", git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { char index[PATH_MAX]; const char *env[2] = { index, NULL }; snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path(commit_editmsg), NULL, env)) { fprintf(stderr, "Please supply the message using either -m or -F option.\n"); exit(1); } } if (!no_verify && run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { return 0; } return 1; }
/* * If we feed all the commits we want to verify to this command * * $ git rev-list --objects --stdin --not --all * * and if it does not error out, that means everything reachable from * these commits locally exists and is connected to our existing refs. * Note that this does _not_ validate the individual objects. * * Returns 0 if everything is connected, non-zero otherwise. */ int check_connected(sha1_iterate_fn fn, void *cb_data, struct check_connected_options *opt) { struct child_process rev_list = CHILD_PROCESS_INIT; struct check_connected_options defaults = CHECK_CONNECTED_INIT; char commit[41]; unsigned char sha1[20]; int err = 0; struct packed_git *new_pack = NULL; struct transport *transport; size_t base_len; if (!opt) opt = &defaults; transport = opt->transport; if (fn(cb_data, sha1)) { if (opt->err_fd) close(opt->err_fd); return err; } if (transport && transport->smart_options && transport->smart_options->self_contained_and_connected && transport->pack_lockfile && strip_suffix(transport->pack_lockfile, ".keep", &base_len)) { struct strbuf idx_file = STRBUF_INIT; strbuf_add(&idx_file, transport->pack_lockfile, base_len); strbuf_addstr(&idx_file, ".idx"); new_pack = add_packed_git(idx_file.buf, idx_file.len, 1); strbuf_release(&idx_file); } if (opt->shallow_file) { argv_array_push(&rev_list.args, "--shallow-file"); argv_array_push(&rev_list.args, opt->shallow_file); } argv_array_push(&rev_list.args,"rev-list"); argv_array_push(&rev_list.args, "--objects"); argv_array_push(&rev_list.args, "--stdin"); argv_array_push(&rev_list.args, "--not"); argv_array_push(&rev_list.args, "--all"); argv_array_push(&rev_list.args, "--quiet"); if (opt->progress) argv_array_pushf(&rev_list.args, "--progress=%s", _("Checking connectivity")); rev_list.git_cmd = 1; rev_list.env = opt->env; rev_list.in = -1; rev_list.no_stdout = 1; if (opt->err_fd) rev_list.err = opt->err_fd; else rev_list.no_stderr = opt->quiet; if (start_command(&rev_list)) return error(_("Could not run 'git rev-list'")); sigchain_push(SIGPIPE, SIG_IGN); commit[40] = '\n'; do { /* * If index-pack already checked that: * - there are no dangling pointers in the new pack * - the pack is self contained * Then if the updated ref is in the new pack, then we * are sure the ref is good and not sending it to * rev-list for verification. */ if (new_pack && find_pack_entry_one(sha1, new_pack)) continue; memcpy(commit, sha1_to_hex(sha1), 40); if (write_in_full(rev_list.in, commit, 41) < 0) { if (errno != EPIPE && errno != EINVAL) error_errno(_("failed write to rev-list")); err = -1; break; } } while (!fn(cb_data, sha1)); if (close(rev_list.in)) err = error_errno(_("failed to close rev-list's stdin")); sigchain_pop(SIGPIPE); return finish_command(&rev_list) || err; }
static void end_headers(struct strbuf *hdr) { strbuf_add(hdr, "\r\n", 2); write_or_die(1, hdr->buf, hdr->len); strbuf_release(hdr); }
/* * Read a directory tree. We currently ignore anything but * directories, regular files and symlinks. That's because git * doesn't handle them at all yet. Maybe that will change some * day. * * Also, we ignore the name ".git" (even if it is not a directory). * That likely will not change. * * Returns the most significant path_treatment value encountered in the scan. */ static enum path_treatment read_directory_recursive(struct dir_struct *dir, const char *base, int baselen, int check_only, const struct path_simplify *simplify) { DIR *fdir; enum path_treatment state, subdir_state, dir_state = path_none; struct dirent *de; struct strbuf path = STRBUF_INIT; strbuf_add(&path, base, baselen); fdir = opendir(path.len ? path.buf : "."); if (!fdir) goto out; while ((de = readdir(fdir)) != NULL) { /* check how the file or directory should be treated */ state = treat_path(dir, de, &path, baselen, simplify); if (state > dir_state) dir_state = state; /* recurse into subdir if instructed by treat_path */ if (state == path_recurse) { subdir_state = read_directory_recursive(dir, path.buf, path.len, check_only, simplify); if (subdir_state > dir_state) dir_state = subdir_state; } if (check_only) { /* abort early if maximum state has been reached */ if (dir_state == path_untracked) break; /* skip the dir_add_* part */ continue; } /* add the path to the appropriate result list */ switch (state) { case path_excluded: if (dir->flags & DIR_SHOW_IGNORED) dir_add_name(dir, path.buf, path.len); else if ((dir->flags & DIR_SHOW_IGNORED_TOO) || ((dir->flags & DIR_COLLECT_IGNORED) && exclude_matches_pathspec(path.buf, path.len, simplify))) dir_add_ignored(dir, path.buf, path.len); break; case path_untracked: if (!(dir->flags & DIR_SHOW_IGNORED)) dir_add_name(dir, path.buf, path.len); break; default: break; } } closedir(fdir); out: strbuf_release(&path); return dir_state; }
void show_submodule_summary(FILE *f, const char *path, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *del, const char *add, const char *reset) { struct rev_info rev; struct commit *commit, *left = left, *right = right; struct commit_list *merge_bases, *list; const char *message = NULL; struct strbuf sb = STRBUF_INIT; static const char *format = " %m %s"; int fast_forward = 0, fast_backward = 0; if (is_null_sha1(two)) message = "(submodule deleted)"; else if (add_submodule_odb(path)) message = "(not checked out)"; else if (is_null_sha1(one)) message = "(new submodule)"; else if (!(left = lookup_commit_reference(one)) || !(right = lookup_commit_reference(two))) message = "(commits not present)"; if (!message) { init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, NULL); rev.left_right = 1; rev.first_parent_only = 1; left->object.flags |= SYMMETRIC_LEFT; add_pending_object(&rev, &left->object, path); add_pending_object(&rev, &right->object, path); merge_bases = get_merge_bases(left, right, 1); if (merge_bases) { if (merge_bases->item == left) fast_forward = 1; else if (merge_bases->item == right) fast_backward = 1; } for (list = merge_bases; list; list = list->next) { list->item->object.flags |= UNINTERESTING; add_pending_object(&rev, &list->item->object, sha1_to_hex(list->item->object.sha1)); } if (prepare_revision_walk(&rev)) message = "(revision walker failed)"; } strbuf_addf(&sb, "Submodule %s %s..", path, find_unique_abbrev(one, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV)); if (dirty_submodule) strbuf_add(&sb, "-dirty", 6); if (message) strbuf_addf(&sb, " %s\n", message); else strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : ""); fwrite(sb.buf, sb.len, 1, f); if (!message) { while ((commit = get_revision(&rev))) { struct pretty_print_context ctx = {0}; ctx.date_mode = rev.date_mode; strbuf_setlen(&sb, 0); if (commit->object.flags & SYMMETRIC_LEFT) { if (del) strbuf_addstr(&sb, del); } else if (add) strbuf_addstr(&sb, add); format_commit_message(commit, format, &sb, &ctx); if (reset) strbuf_addstr(&sb, reset); strbuf_addch(&sb, '\n'); fprintf(f, "%s", sb.buf); } clear_commit_marks(left, ~0); clear_commit_marks(right, ~0); } strbuf_release(&sb); }
/* * Wrap the text, if necessary. The variable indent is the indent for the * first line, indent2 is the indent for all other lines. * If indent is negative, assume that already -indent columns have been * consumed (and no extra indent is necessary for the first line). */ void strbuf_add_wrapped_text(struct strbuf *buf, const char *text, int indent1, int indent2, int width) { int indent, w, assume_utf8 = 1; const char *bol, *space, *start = text; size_t orig_len = buf->len; if (width <= 0) { strbuf_add_indented_text(buf, text, indent1, indent2); return; } retry: bol = text; w = indent = indent1; space = NULL; if (indent < 0) { w = -indent; space = text; } for (;;) { char c; size_t skip; while ((skip = display_mode_esc_sequence_len(text))) text += skip; c = *text; if (!c || isspace(c)) { if (w <= width || !space) { const char *start = bol; if (!c && text == start) return; if (space) start = space; else strbuf_addchars(buf, ' ', indent); strbuf_add(buf, start, text - start); if (!c) return; space = text; if (c == '\t') w |= 0x07; else if (c == '\n') { space++; if (*space == '\n') { strbuf_addch(buf, '\n'); goto new_line; } else if (!isalnum(*space)) goto new_line; else strbuf_addch(buf, ' '); } w++; text++; } else { new_line: strbuf_addch(buf, '\n'); text = bol = space + isspace(*space); space = NULL; w = indent = indent2; } continue; } if (assume_utf8) { w += utf8_width(&text, NULL); if (!text) { assume_utf8 = 0; text = start; strbuf_setlen(buf, orig_len); goto retry; } } else { w++; text++; } } }
static size_t format_commit_one(struct strbuf *sb, const char *placeholder, void *context) { struct format_commit_context *c = context; const struct commit *commit = c->commit; const char *msg = c->message; struct commit_list *p; int h1, h2; /* these are independent of the commit */ switch (placeholder[0]) { case 'C': if (placeholder[1] == '(') { const char *end = strchr(placeholder + 2, ')'); char color[COLOR_MAXLEN]; if (!end) return 0; color_parse_mem(placeholder + 2, end - (placeholder + 2), "--pretty format", color); strbuf_addstr(sb, color); return end - placeholder + 1; } if (!prefixcmp(placeholder + 1, "red")) { strbuf_addstr(sb, GIT_COLOR_RED); return 4; } else if (!prefixcmp(placeholder + 1, "green")) { strbuf_addstr(sb, GIT_COLOR_GREEN); return 6; } else if (!prefixcmp(placeholder + 1, "blue")) { strbuf_addstr(sb, GIT_COLOR_BLUE); return 5; } else if (!prefixcmp(placeholder + 1, "reset")) { strbuf_addstr(sb, GIT_COLOR_RESET); return 6; } else return 0; case 'n': /* newline */ strbuf_addch(sb, '\n'); return 1; case 'x': /* %x00 == NUL, %x0a == LF, etc. */ if (0 <= (h1 = hexval_table[0xff & placeholder[1]]) && h1 <= 16 && 0 <= (h2 = hexval_table[0xff & placeholder[2]]) && h2 <= 16) { strbuf_addch(sb, (h1<<4)|h2); return 3; } else return 0; case 'w': if (placeholder[1] == '(') { unsigned long width = 0, indent1 = 0, indent2 = 0; char *next; const char *start = placeholder + 2; const char *end = strchr(start, ')'); if (!end) return 0; if (end > start) { width = strtoul(start, &next, 10); if (*next == ',') { indent1 = strtoul(next + 1, &next, 10); if (*next == ',') { indent2 = strtoul(next + 1, &next, 10); } } if (*next != ')') return 0; } rewrap_message_tail(sb, c, width, indent1, indent2); return end - placeholder + 1; } else return 0; } /* these depend on the commit */ if (!commit->object.parsed) parse_object(commit->object.sha1); switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, sha1_to_hex(commit->object.sha1)); return 1; case 'h': /* abbreviated commit hash */ if (add_again(sb, &c->abbrev_commit_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->object.sha1, c->pretty_ctx->abbrev)); c->abbrev_commit_hash.len = sb->len - c->abbrev_commit_hash.off; return 1; case 'T': /* tree hash */ strbuf_addstr(sb, sha1_to_hex(commit->tree->object.sha1)); return 1; case 't': /* abbreviated tree hash */ if (add_again(sb, &c->abbrev_tree_hash)) return 1; strbuf_addstr(sb, find_unique_abbrev(commit->tree->object.sha1, c->pretty_ctx->abbrev)); c->abbrev_tree_hash.len = sb->len - c->abbrev_tree_hash.off; return 1; case 'P': /* parent hashes */ for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, sha1_to_hex(p->item->object.sha1)); } return 1; case 'p': /* abbreviated parent hashes */ if (add_again(sb, &c->abbrev_parent_hashes)) return 1; for (p = commit->parents; p; p = p->next) { if (p != commit->parents) strbuf_addch(sb, ' '); strbuf_addstr(sb, find_unique_abbrev( p->item->object.sha1, c->pretty_ctx->abbrev)); } c->abbrev_parent_hashes.len = sb->len - c->abbrev_parent_hashes.off; return 1; case 'm': /* left/right/bottom */ strbuf_addstr(sb, get_revision_mark(NULL, commit)); return 1; case 'd': format_decoration(sb, commit); return 1; case 'g': /* reflog info */ switch(placeholder[1]) { case 'd': /* reflog selector */ case 'D': if (c->pretty_ctx->reflog_info) get_reflog_selector(sb, c->pretty_ctx->reflog_info, c->pretty_ctx->date_mode, (placeholder[1] == 'd')); return 2; case 's': /* reflog message */ if (c->pretty_ctx->reflog_info) get_reflog_message(sb, c->pretty_ctx->reflog_info); return 2; } return 0; /* unknown %g placeholder */ case 'N': if (c->pretty_ctx->show_notes) { format_display_notes(commit->object.sha1, sb, get_log_output_encoding(), 0); return 1; } return 0; } if (placeholder[0] == 'G') { if (!c->commit_signature_parsed) parse_commit_signature(c); switch (placeholder[1]) { case 'G': if (c->signature.gpg_output) strbuf_addstr(sb, c->signature.gpg_output); break; case '?': switch (c->signature.good_bad) { case 'G': case 'B': strbuf_addch(sb, c->signature.good_bad); } break; case 'S': if (c->signature.signer) strbuf_addstr(sb, c->signature.signer); break; } return 2; } /* For the rest we have to parse the commit header. */ if (!c->commit_header_parsed) parse_commit_header(c); switch (placeholder[0]) { case 'a': /* author ... */ return format_person_part(sb, placeholder[1], msg + c->author.off, c->author.len, c->pretty_ctx->date_mode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], msg + c->committer.off, c->committer.len, c->pretty_ctx->date_mode); case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; case 'B': /* raw body */ /* message_off is always left at the initial newline */ strbuf_addstr(sb, msg + c->message_off + 1); return 1; } /* Now we need to parse the commit message. */ if (!c->commit_message_parsed) parse_commit_message(c); switch (placeholder[0]) { case 's': /* subject */ format_subject(sb, msg + c->subject_off, " "); return 1; case 'f': /* sanitized subject */ format_sanitized_subject(sb, msg + c->subject_off); return 1; case 'b': /* body */ strbuf_addstr(sb, msg + c->body_off); return 1; } return 0; /* unknown placeholder */ }
static size_t format_person_part(struct strbuf *sb, char part, const char *msg, int len, enum date_mode dmode) { /* currently all placeholders have same length */ const int placeholder_len = 2; int start, end, tz = 0; unsigned long date = 0; char *ep; const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len; char person_name[1024]; char person_mail[1024]; /* advance 'end' to point to email start delimiter */ for (end = 0; end < len && msg[end] != '<'; end++) ; /* do nothing */ /* * When end points at the '<' that we found, it should have * matching '>' later, which means 'end' must be strictly * below len - 1. */ if (end >= len - 2) goto skip; /* Seek for both name and email part */ name_start = msg; name_end = msg+end; while (name_end > name_start && isspace(*(name_end-1))) name_end--; mail_start = msg+end+1; mail_end = mail_start; while (mail_end < msg_end && *mail_end != '>') mail_end++; if (mail_end == msg_end) goto skip; end = mail_end-msg; if (part == 'N' || part == 'E') { /* mailmap lookup */ strlcpy(person_name, name_start, name_end-name_start+1); strlcpy(person_mail, mail_start, mail_end-mail_start+1); mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name)); name_start = person_name; name_end = name_start + strlen(person_name); mail_start = person_mail; mail_end = mail_start + strlen(person_mail); } if (part == 'n' || part == 'N') { /* name */ strbuf_add(sb, name_start, name_end-name_start); return placeholder_len; } if (part == 'e' || part == 'E') { /* email */ strbuf_add(sb, mail_start, mail_end-mail_start); return placeholder_len; } /* advance 'start' to point to date start delimiter */ for (start = end + 1; start < len && isspace(msg[start]); start++) ; /* do nothing */ if (start >= len) goto skip; date = strtoul(msg + start, &ep, 10); if (msg + start == ep) goto skip; if (part == 't') { /* date, UNIX timestamp */ strbuf_add(sb, msg + start, ep - (msg + start)); return placeholder_len; } /* parse tz */ for (start = ep - msg + 1; start < len && isspace(msg[start]); start++) ; /* do nothing */ if (start + 1 < len) { tz = strtoul(msg + start + 1, NULL, 10); if (msg[start] == '-') tz = -tz; } switch (part) { case 'd': /* date */ strbuf_addstr(sb, show_date(date, tz, dmode)); return placeholder_len; case 'D': /* date, RFC2822 style */ strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822)); return placeholder_len; case 'r': /* date, relative */ strbuf_addstr(sb, show_date(date, tz, DATE_RELATIVE)); return placeholder_len; case 'i': /* date, ISO 8601 */ strbuf_addstr(sb, show_date(date, tz, DATE_ISO8601)); return placeholder_len; } skip: /* * bogus commit, 'sb' cannot be updated, but we still need to * compute a valid return value. */ if (part == 'n' || part == 'e' || part == 't' || part == 'd' || part == 'D' || part == 'r' || part == 'i') return placeholder_len; return 0; /* unknown placeholder */ }
/** * Given a fd, read stuff into pushobj */ static int read_pobj(FILE * fp, struct pushobj *po) { int ii; int head = 0; char * cbuf = NULL; struct strbuf buf = STRBUF_INIT; struct strbuf sig = STRBUF_INIT; struct string_list list; pobj_release(po); memset(&list, 0, sizeof(struct string_list)); memset(po->head, 0, 40); while (strbuf_getline(&buf, fp, '\n') != EOF) { if (strncmp(buf.buf, PGP_SIGNATURE, strlen(PGP_SIGNATURE)) == 0) break; // check to see if this is the HEAD ref if (strncmp(buf.buf + 41, "HEAD", 4) == 0) { strncpy(po->head, buf.buf, 40); head = 1; continue; } cbuf = (char*)malloc(buf.len); strcpy(cbuf, buf.buf); string_list_append(cbuf, &list); } if (!head) die("pushobject did not contain HEAD ref"); po->lines = list.nr; po->refs = (char**)malloc(sizeof(char*) * list.nr); for (ii = 0; ii < list.nr; ii++) { po->refs[ii] = (char*)malloc(strlen(list.items[ii].string) + 1); strcpy(po->refs[ii], list.items[ii].string); } // rest of the stuff is signature strbuf_add(&sig, buf.buf, buf.len); strbuf_addstr(&sig, "\n"); while (strbuf_getline(&buf, fp, '\n') != EOF) { strbuf_add(&sig, buf.buf, buf.len); strbuf_addstr(&sig, "\n"); if (strncmp(buf.buf, END_PGP_SIGNATURE, strlen(END_PGP_SIGNATURE)) == 0) break; } po->signature = (char*)malloc(sig.len); strcpy(po->signature, sig.buf); memset(po->prev, 0, 40); // now the rest of the stuff is the prev pointer if (strbuf_getline(&buf, fp, '\n') != EOF) { strncpy(po->prev, buf.buf, 40); po->prev[40] = '\0'; } strbuf_release(&buf); strbuf_release(&sig); string_list_clear(&list, 0); return 1; }
static int update_one(struct cache_tree *it, struct cache_entry **cache, int entries, const char *base, int baselen, int missing_ok, int dryrun) { struct strbuf buffer; int i; if (0 <= it->entry_count && has_sha1_file(it->sha1)) return it->entry_count; /* * We first scan for subtrees and update them; we start by * marking existing subtrees -- the ones that are unmarked * should not be in the result. */ for (i = 0; i < it->subtree_nr; i++) it->down[i]->used = 0; /* * Find the subtrees and update them. */ for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; int pathlen, sublen, subcnt; path = ce->name; pathlen = ce_namelen(ce); if (pathlen <= baselen || memcmp(base, path, baselen)) break; /* at the end of this level */ slash = strchr(path + baselen, '/'); if (!slash) continue; /* * a/bbb/c (base = a/, slash = /c) * ==> * path+baselen = bbb/c, sublen = 3 */ sublen = slash - (path + baselen); sub = find_subtree(it, path + baselen, sublen, 1); if (!sub->cache_tree) sub->cache_tree = cache_tree(); subcnt = update_one(sub->cache_tree, cache + i, entries - i, path, baselen + sublen + 1, missing_ok, dryrun); if (subcnt < 0) return subcnt; i += subcnt - 1; sub->used = 1; } discard_unused_subtrees(it); /* * Then write out the tree object for this level. */ strbuf_init(&buffer, 8192); for (i = 0; i < entries; i++) { struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; int pathlen, entlen; const unsigned char *sha1; unsigned mode; path = ce->name; pathlen = ce_namelen(ce); if (pathlen <= baselen || memcmp(base, path, baselen)) break; /* at the end of this level */ slash = strchr(path + baselen, '/'); if (slash) { entlen = slash - (path + baselen); sub = find_subtree(it, path + baselen, entlen, 0); if (!sub) die("cache-tree.c: '%.*s' in '%s' not found", entlen, path + baselen, path); i += sub->cache_tree->entry_count - 1; sha1 = sub->cache_tree->sha1; mode = S_IFDIR; } else { sha1 = ce->sha1; mode = ce->ce_mode; entlen = pathlen - baselen; } if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) return error("invalid object %s", sha1_to_hex(sha1)); if (ce->ce_flags & CE_REMOVE) continue; /* entry being removed */ strbuf_grow(&buffer, entlen + 100); strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0'); strbuf_add(&buffer, sha1, 20); #if DEBUG fprintf(stderr, "cache-tree update-one %o %.*s\n", mode, entlen, path + baselen); #endif } if (dryrun) hash_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1); else if (write_sha1_file(buffer.buf, buffer.len, tree_type, it->sha1)) { strbuf_release(&buffer); return -1; } strbuf_release(&buffer); it->entry_count = i; #if DEBUG fprintf(stderr, "cache-tree update-one (%d ent, %d subtree) %s\n", it->entry_count, it->subtree_nr, sha1_to_hex(it->sha1)); #endif return i; }
void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, enum branch_track track) { struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; char *real_ref, msg[PATH_MAX + 20]; struct strbuf ref = STRBUF_INIT; int forcing = 0; int len; len = strlen(name); if (interpret_nth_last_branch(name, &ref) != len) { strbuf_reset(&ref); strbuf_add(&ref, name, len); } strbuf_splice(&ref, 0, 0, "refs/heads/", 11); if (check_ref_format(ref.buf)) die("'%s' is not a valid branch name.", name); if (resolve_ref(ref.buf, sha1, 1, NULL)) { if (!force) die("A branch named '%s' already exists.", name); else if (!is_bare_repository() && !strcmp(head, name)) die("Cannot force update the current branch."); forcing = 1; } real_ref = NULL; if (get_sha1(start_name, sha1)) die("Not a valid object name: '%s'.", start_name); switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { case 0: /* Not branching from any existing branch */ if (track == BRANCH_TRACK_EXPLICIT) die("Cannot setup tracking information; starting point is not a branch."); break; case 1: /* Unique completion -- good, only if it is a real ref */ if (track == BRANCH_TRACK_EXPLICIT && !strcmp(real_ref, "HEAD")) die("Cannot setup tracking information; starting point is not a branch."); break; default: die("Ambiguous object name: '%s'.", start_name); break; } if ((commit = lookup_commit_reference(sha1)) == NULL) die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); lock = lock_any_ref_for_update(ref.buf, NULL, 0); if (!lock) die("Failed to lock ref for update: %s.", strerror(errno)); if (reflog) log_all_ref_updates = 1; if (forcing) snprintf(msg, sizeof msg, "branch: Reset from %s", start_name); else snprintf(msg, sizeof msg, "branch: Created from %s", start_name); if (real_ref && track) setup_tracking(name, real_ref, track); if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); strbuf_release(&ref); free(real_ref); }
static void insert_one_record(struct shortlog *log, const char *author, const char *oneline) { const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; const char *mailbuf, *namebuf; size_t namelen, maillen; const char *eol; struct strbuf subject = STRBUF_INIT; struct strbuf namemailbuf = STRBUF_INIT; struct ident_split ident; if (split_ident_line(&ident, author, strlen(author))) return; namebuf = ident.name_begin; mailbuf = ident.mail_begin; namelen = ident.name_end - ident.name_begin; maillen = ident.mail_end - ident.mail_begin; map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); strbuf_add(&namemailbuf, namebuf, namelen); if (log->email) strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); item = string_list_insert(&log->list, namemailbuf.buf); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); /* Skip any leading whitespace, including any blank lines. */ while (*oneline && isspace(*oneline)) oneline++; eol = strchr(oneline, '\n'); if (!eol) eol = oneline + strlen(oneline); if (!prefixcmp(oneline, "[PATCH")) { char *eob = strchr(oneline, ']'); if (eob && (!eol || eob < eol)) oneline = eob + 1; } while (*oneline && isspace(*oneline) && *oneline != '\n') oneline++; format_subject(&subject, oneline, " "); buffer = strbuf_detach(&subject, NULL); if (dot3) { int dot3len = strlen(dot3); if (dot3len > 5) { while ((p = strstr(buffer, dot3)) != NULL) { int taillen = strlen(p) - dot3len; memcpy(p, "/.../", 5); memmove(p + 5, p + dot3len, taillen + 1); } } } string_list_append(item->util, buffer); }
static int merge(int argc, const char **argv, const char *prefix) { struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; struct object_id result_oid; struct notes_tree *t; struct notes_merge_options o; int do_merge = 0, do_commit = 0, do_abort = 0; int verbosity = 0, result; const char *strategy = NULL; struct option options[] = { OPT_GROUP(N_("General options")), OPT__VERBOSITY(&verbosity), OPT_GROUP(N_("Merge options")), OPT_STRING('s', "strategy", &strategy, N_("strategy"), N_("resolve notes conflicts using the given strategy " "(manual/ours/theirs/union/cat_sort_uniq)")), OPT_GROUP(N_("Committing unmerged notes")), { OPTION_SET_INT, 0, "commit", &do_commit, NULL, N_("finalize notes merge by committing unmerged notes"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, OPT_GROUP(N_("Aborting notes merge resolution")), { OPTION_SET_INT, 0, "abort", &do_abort, NULL, N_("abort notes merge"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1}, OPT_END() }; argc = parse_options(argc, argv, prefix, options, git_notes_merge_usage, 0); if (strategy || do_commit + do_abort == 0) do_merge = 1; if (do_merge + do_commit + do_abort != 1) { error(_("cannot mix --commit, --abort or -s/--strategy")); usage_with_options(git_notes_merge_usage, options); } if (do_merge && argc != 1) { error(_("must specify a notes ref to merge")); usage_with_options(git_notes_merge_usage, options); } else if (!do_merge && argc) { error(_("too many parameters")); usage_with_options(git_notes_merge_usage, options); } init_notes_merge_options(&o); o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; if (do_abort) return merge_abort(&o); if (do_commit) return merge_commit(&o); o.local_ref = default_notes_ref(); strbuf_addstr(&remote_ref, argv[0]); expand_loose_notes_ref(&remote_ref); o.remote_ref = remote_ref.buf; t = init_notes_check("merge", NOTES_INIT_WRITABLE); if (strategy) { if (parse_notes_merge_strategy(strategy, &o.strategy)) { error(_("unknown -s/--strategy: %s"), strategy); usage_with_options(git_notes_merge_usage, options); } } else { struct strbuf merge_key = STRBUF_INIT; const char *short_ref = NULL; if (!skip_prefix(o.local_ref, "refs/notes/", &short_ref)) die("BUG: local ref %s is outside of refs/notes/", o.local_ref); strbuf_addf(&merge_key, "notes.%s.mergeStrategy", short_ref); if (git_config_get_notes_strategy(merge_key.buf, &o.strategy)) git_config_get_notes_strategy("notes.mergeStrategy", &o.strategy); strbuf_release(&merge_key); } strbuf_addf(&msg, "notes: Merged notes from %s into %s", remote_ref.buf, default_notes_ref()); strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ result = notes_merge(&o, t, &result_oid); if (result >= 0) /* Merge resulted (trivially) in result_oid */ /* Update default notes ref with new commit */ update_ref(msg.buf, default_notes_ref(), result_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); if (wt) die(_("a notes merge into %s is already in-progress at %s"), default_notes_ref(), wt->path); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die(_("failed to store link to current notes ref (%s)"), default_notes_ref()); printf(_("Automatic notes merge failed. Fix conflicts in %s and " "commit the result with 'git notes merge --commit', or " "abort the merge with 'git notes merge --abort'.\n"), git_path(NOTES_MERGE_WORKTREE)); } free_notes(t); strbuf_release(&remote_ref); strbuf_release(&msg); return result < 0; /* return non-zero on conflicts */ }
static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *current_head, struct wt_status *s, struct strbuf *author_ident) { struct stat statbuf; struct strbuf committer_ident = STRBUF_INIT; int commitable, saved_color_setting; struct strbuf sb = STRBUF_INIT; char *buffer; const char *hook_arg1 = NULL; const char *hook_arg2 = NULL; int ident_shown = 0; int clean_message_contents = (cleanup_mode != CLEANUP_NONE); /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; if (squash_message) { /* * Insert the proper subject line before other commit * message options add their content. */ if (use_message && !strcmp(use_message, squash_message)) strbuf_addstr(&sb, "squash! "); else { struct pretty_print_context ctx = {0}; struct commit *c; c = lookup_commit_reference_by_name(squash_message); if (!c) die(_("could not lookup commit %s"), squash_message); ctx.output_encoding = get_commit_output_encoding(); format_commit_message(c, "squash! %s\n\n", &sb, &ctx); } } if (message.len) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; } else if (logfile && !strcmp(logfile, "-")) { if (isatty(0)) fprintf(stderr, _("(reading log message from standard input)\n")); if (strbuf_read(&sb, 0, 0) < 0) die_errno(_("could not read log from standard input")); hook_arg1 = "message"; } else if (logfile) { if (strbuf_read_file(&sb, logfile, 0) < 0) die_errno(_("could not read log file '%s'"), logfile); hook_arg1 = "message"; } else if (use_message) { buffer = strstr(use_message_buffer, "\n\n"); if (!use_editor && (!buffer || buffer[2] == '\0')) die(_("commit has empty message")); strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; } else if (fixup_message) { struct pretty_print_context ctx = {0}; struct commit *commit; commit = lookup_commit_reference_by_name(fixup_message); if (!commit) die(_("could not lookup commit %s"), fixup_message); ctx.output_encoding = get_commit_output_encoding(); format_commit_message(commit, "fixup! %s\n\n", &sb, &ctx); hook_arg1 = "message"; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) die_errno(_("could not read MERGE_MSG")); hook_arg1 = "merge"; } else if (!stat(git_path("SQUASH_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("SQUASH_MSG"), 0) < 0) die_errno(_("could not read SQUASH_MSG")); hook_arg1 = "squash"; } else if (template_file) { if (strbuf_read_file(&sb, template_file, 0) < 0) die_errno(_("could not read '%s'"), template_file); hook_arg1 = "template"; clean_message_contents = 0; } /* * The remaining cases don't modify the template message, but * just set the argument(s) to the prepare-commit-msg hook. */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; else if (whence == FROM_CHERRY_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } if (squash_message) { /* * If squash_commit was used for the commit subject, * then we're possibly hijacking other commit log options. * Reset the hook args to tell the real story. */ hook_arg1 = "message"; hook_arg2 = ""; } s->fp = fopen(git_path(commit_editmsg), "w"); if (s->fp == NULL) die_errno(_("could not open '%s'"), git_path(commit_editmsg)); if (clean_message_contents) stripspace(&sb, 0); if (signoff) { /* * See if we have a Conflicts: block at the end. If yes, count * its size, so we can ignore it. */ int ignore_footer = 0; int i, eol, previous = 0; const char *nl; for (i = 0; i < sb.len; i++) { nl = memchr(sb.buf + i, '\n', sb.len - i); if (nl) eol = nl - sb.buf; else eol = sb.len; if (!prefixcmp(sb.buf + previous, "\nConflicts:\n")) { ignore_footer = sb.len - previous; break; } while (i < eol) i++; previous = eol; } append_signoff(&sb, ignore_footer); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); strbuf_release(&sb); /* This checks if committer ident is explicitly given */ strbuf_addstr(&committer_ident, git_committer_info(IDENT_STRICT)); if (use_editor && include_status) { char *ai_tmp, *ci_tmp; if (whence != FROM_COMMIT) status_printf_ln(s, GIT_COLOR_NORMAL, whence == FROM_MERGE ? _("\n" "It looks like you may be committing a merge.\n" "If this is not correct, please remove the file\n" " %s\n" "and try again.\n") : _("\n" "It looks like you may be committing a cherry-pick.\n" "If this is not correct, please remove the file\n" " %s\n" "and try again.\n"), git_path(whence == FROM_MERGE ? "MERGE_HEAD" : "CHERRY_PICK_HEAD")); fprintf(s->fp, "\n"); if (cleanup_mode == CLEANUP_ALL) status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." " Lines starting\nwith '#' will be ignored, and an empty" " message aborts the commit.\n")); else /* CLEANUP_SPACE, that is. */ status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." " Lines starting\n" "with '#' will be kept; you may remove them" " yourself if you want to.\n" "An empty message aborts the commit.\n")); if (only_include_assumed) status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); ai_tmp = cut_ident_timestamp_part(author_ident->buf); ci_tmp = cut_ident_timestamp_part(committer_ident.buf); if (strcmp(author_ident->buf, committer_ident.buf)) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" "Author: %s"), ident_shown++ ? "" : "\n", author_ident->buf); if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" "Committer: %s"), ident_shown++ ? "" : "\n", committer_ident.buf); if (ident_shown) status_printf_ln(s, GIT_COLOR_NORMAL, ""); saved_color_setting = s->use_color; s->use_color = 0; commitable = run_status(s->fp, index_file, prefix, 1, s); s->use_color = saved_color_setting; *ai_tmp = ' '; *ci_tmp = ' '; } else { unsigned char sha1[20]; const char *parent = "HEAD"; if (!active_nr && read_cache() < 0) die(_("Cannot read index")); if (amend) parent = "HEAD^1"; if (get_sha1(parent, sha1)) commitable = !!active_nr; else commitable = index_differs_from(parent, 0); } strbuf_release(&committer_ident); fclose(s->fp); /* * Reject an attempt to record a non-merge empty commit without * explicit --allow-empty. In the cherry-pick case, it may be * empty due to conflict resolution, which the user should okay. */ if (!commitable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); else if (whence == FROM_CHERRY_PICK) fputs(_(empty_cherry_pick_advice), stderr); return 0; } /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke * the editor and after we invoke run_status above. */ discard_cache(); read_cache_from(index_file); if (update_main_cache_tree(0)) { error(_("Error building trees")); return 0; } if (run_hook(index_file, "prepare-commit-msg", git_path(commit_editmsg), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { char index[PATH_MAX]; const char *env[2] = { NULL }; env[0] = index; snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); if (launch_editor(git_path(commit_editmsg), NULL, env)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } } if (!no_verify && run_hook(index_file, "commit-msg", git_path(commit_editmsg), NULL)) { return 0; } return 1; }
static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size) { struct work_item *w = opt->output_priv; strbuf_add(&w->out, buf, size); }
/* * Create a newly-allocated `snapshot` of the `packed-refs` file in * its current state and return it. The return value will already have * its reference count incremented. * * A comment line of the form "# pack-refs with: " may contain zero or * more traits. We interpret the traits as follows: * * Neither `peeled` nor `fully-peeled`: * * Probably no references are peeled. But if the file contains a * peeled value for a reference, we will use it. * * `peeled`: * * References under "refs/tags/", if they *can* be peeled, *are* * peeled in this file. References outside of "refs/tags/" are * probably not peeled even if they could have been, but if we find * a peeled value for such a reference we will use it. * * `fully-peeled`: * * All references in the file that can be peeled are peeled. * Inversely (and this is more important), any references in the * file for which no peeled value is recorded is not peelable. This * trait should typically be written alongside "peeled" for * compatibility with older clients, but we do not require it * (i.e., "peeled" is a no-op if "fully-peeled" is set). * * `sorted`: * * The references in this file are known to be sorted by refname. */ static struct snapshot *create_snapshot(struct packed_ref_store *refs) { struct snapshot *snapshot = xcalloc(1, sizeof(*snapshot)); int sorted = 0; snapshot->refs = refs; acquire_snapshot(snapshot); snapshot->peeled = PEELED_NONE; if (!load_contents(snapshot)) return snapshot; /* If the file has a header line, process it: */ if (snapshot->buf < snapshot->eof && *snapshot->buf == '#') { struct strbuf tmp = STRBUF_INIT; char *p; const char *eol; struct string_list traits = STRING_LIST_INIT_NODUP; eol = memchr(snapshot->buf, '\n', snapshot->eof - snapshot->buf); if (!eol) die_unterminated_line(refs->path, snapshot->buf, snapshot->eof - snapshot->buf); strbuf_add(&tmp, snapshot->buf, eol - snapshot->buf); if (!skip_prefix(tmp.buf, "# pack-refs with:", (const char **)&p)) die_invalid_line(refs->path, snapshot->buf, snapshot->eof - snapshot->buf); string_list_split_in_place(&traits, p, ' ', -1); if (unsorted_string_list_has_string(&traits, "fully-peeled")) snapshot->peeled = PEELED_FULLY; else if (unsorted_string_list_has_string(&traits, "peeled")) snapshot->peeled = PEELED_TAGS; sorted = unsorted_string_list_has_string(&traits, "sorted"); /* perhaps other traits later as well */ /* The "+ 1" is for the LF character. */ snapshot->header_len = eol + 1 - snapshot->buf; string_list_clear(&traits, 0); strbuf_release(&tmp); } verify_buffer_safe(snapshot); if (!sorted) { sort_snapshot(snapshot); /* * Reordering the records might have moved a short one * to the end of the buffer, so verify the buffer's * safety again: */ verify_buffer_safe(snapshot); } if (mmap_strategy != MMAP_OK && snapshot->mmapped) { /* * We don't want to leave the file mmapped, so we are * forced to make a copy now: */ size_t size = snapshot->eof - (snapshot->buf + snapshot->header_len); char *buf_copy = xmalloc(size); memcpy(buf_copy, snapshot->buf + snapshot->header_len, size); clear_snapshot_buffer(snapshot); snapshot->buf = buf_copy; snapshot->eof = buf_copy + size; } return snapshot; }
void format_note(struct notes_tree *t, const unsigned char *object_sha1, struct strbuf *sb, const char *output_encoding, int flags) { static const char utf8[] = "utf-8"; const unsigned char *sha1; char *msg, *msg_p; unsigned long linelen, msglen; enum object_type type; if (!t) t = &default_notes_tree; if (!t->initialized) init_notes(t, NULL, NULL, 0); sha1 = get_note(t, object_sha1); if (!sha1) return; if (!(msg = read_sha1_file(sha1, &type, &msglen)) || !msglen || type != OBJ_BLOB) { free(msg); return; } if (output_encoding && *output_encoding && strcmp(utf8, output_encoding)) { char *reencoded = reencode_string(msg, output_encoding, utf8); if (reencoded) { free(msg); msg = reencoded; msglen = strlen(msg); } } /* we will end the annotation by a newline anyway */ if (msglen && msg[msglen - 1] == '\n') msglen--; if (flags & NOTES_SHOW_HEADER) { const char *ref = t->ref; if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) { strbuf_addstr(sb, "\nNotes:\n"); } else { if (!prefixcmp(ref, "refs/")) ref += 5; if (!prefixcmp(ref, "notes/")) ref += 6; strbuf_addf(sb, "\nNotes (%s):\n", ref); } } for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; if (flags & NOTES_INDENT) strbuf_addstr(sb, " "); strbuf_add(sb, msg_p, linelen); strbuf_addch(sb, '\n'); } free(msg); }
/* * new path should be added to combine diff * * 3 cases on how/when it should be called and behaves: * * t, !tp -> path added, all parents lack it * !t, tp -> path removed from all parents * t, tp -> path modified/added * (M for tp[i]=tp[imin], A otherwise) */ static struct combine_diff_path *emit_path(struct combine_diff_path *p, struct strbuf *base, struct diff_options *opt, int nparent, struct tree_desc *t, struct tree_desc *tp, int imin) { unsigned mode; const char *path; const unsigned char *sha1; int pathlen; int old_baselen = base->len; int i, isdir, recurse = 0, emitthis = 1; /* at least something has to be valid */ assert(t || tp); if (t) { /* path present in resulting tree */ sha1 = tree_entry_extract(t, &path, &mode); pathlen = tree_entry_len(&t->entry); isdir = S_ISDIR(mode); } else { /* * a path was removed - take path from imin parent. Also take * mode from that parent, to decide on recursion(1). * * 1) all modes for tp[i]=tp[imin] should be the same wrt * S_ISDIR, thanks to base_name_compare(). */ tree_entry_extract(&tp[imin], &path, &mode); pathlen = tree_entry_len(&tp[imin].entry); isdir = S_ISDIR(mode); sha1 = NULL; mode = 0; } if (DIFF_OPT_TST(opt, RECURSIVE) && isdir) { recurse = 1; emitthis = DIFF_OPT_TST(opt, TREE_IN_RECURSIVE); } if (emitthis) { int keep; struct combine_diff_path *pprev = p; p = path_appendnew(p, nparent, base, path, pathlen, mode, sha1); for (i = 0; i < nparent; ++i) { /* * tp[i] is valid, if present and if tp[i]==tp[imin] - * otherwise, we should ignore it. */ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); const unsigned char *sha1_i; unsigned mode_i; p->parent[i].status = !t ? DIFF_STATUS_DELETED : tpi_valid ? DIFF_STATUS_MODIFIED : DIFF_STATUS_ADDED; if (tpi_valid) { sha1_i = tp[i].entry.sha1; mode_i = tp[i].entry.mode; } else { sha1_i = NULL; mode_i = 0; } p->parent[i].mode = mode_i; hashcpy(p->parent[i].oid.hash, sha1_i ? sha1_i : null_sha1); } keep = 1; if (opt->pathchange) keep = opt->pathchange(opt, p); /* * If a path was filtered or consumed - we don't need to add it * to the list and can reuse its memory, leaving it as * pre-allocated element on the tail. * * On the other hand, if path needs to be kept, we need to * correct its .next to NULL, as it was pre-initialized to how * much memory was allocated. * * see path_appendnew() for details. */ if (!keep) p = pprev; else p->next = NULL; } if (recurse) { const unsigned char **parents_sha1; parents_sha1 = xalloca(nparent * sizeof(parents_sha1[0])); for (i = 0; i < nparent; ++i) { /* same rule as in emitthis */ int tpi_valid = tp && !(tp[i].entry.mode & S_IFXMIN_NEQ); parents_sha1[i] = tpi_valid ? tp[i].entry.sha1 : NULL; } strbuf_add(base, path, pathlen); strbuf_addch(base, '/'); p = ll_diff_tree_paths(p, sha1, parents_sha1, nparent, base, opt); xalloca_free(parents_sha1); } strbuf_setlen(base, old_baselen); return p; }