The "lockfile" API has been rebuilt on top of a new "tempfile" API.
* mh/tempfile:
credential-cache--daemon: use tempfile module
credential-cache--daemon: delete socket from main()
gc: use tempfile module to handle gc.pid file
lock_repo_for_gc(): compute the path to "gc.pid" only once
diff: use tempfile module
setup_temporary_shallow(): use tempfile module
write_shared_index(): use tempfile module
register_tempfile(): new function to handle an existing temporary file
tempfile: add several functions for creating temporary files
prepare_tempfile_object(): new function, extracted from create_tempfile()
tempfile: a new module for handling temporary files
commit_lock_file(): use get_locked_file_path()
lockfile: add accessor get_lock_file_path()
lockfile: add accessors get_lock_file_fd() and get_lock_file_fp()
create_bundle(): duplicate file descriptor to avoid closing it twice
lockfile: move documentation to lockfile.h and lockfile.c
# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
-# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
-# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
-# randomly break unless your underlying filesystem supports those sub-second
-# times (my ext3 doesn't).
+# and ctimes. Note that you need recent glibc (at least 2.2.4) for this. On
+# Linux, kernel 2.6.11 or newer is required for reliable sub-second file times
+# on file systems with exactly 1 ns or 1 s resolution. If you intend to use Git
+# on other file systems (e.g. CEPH, CIFS, NTFS, UDF), don't enable USE_NSEC. See
+# Documentation/technical/racy-git.txt for details.
#
# Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of
# "st_ctim"
# interactive shell sessions without exporting it.
unexport CDPATH
-SCRIPT_SH += git-am.sh
SCRIPT_SH += git-bisect.sh
SCRIPT_SH += git-difftool--helper.sh
SCRIPT_SH += git-filter-branch.sh
SCRIPT_SH += git-merge-one-file.sh
SCRIPT_SH += git-merge-resolve.sh
SCRIPT_SH += git-mergetool.sh
-SCRIPT_SH += git-pull.sh
SCRIPT_SH += git-quiltimport.sh
SCRIPT_SH += git-rebase.sh
SCRIPT_SH += git-remote-testgit.sh
LIB_OBJS += read-cache.o
LIB_OBJS += reflog-walk.o
LIB_OBJS += refs.o
+LIB_OBJS += ref-filter.o
LIB_OBJS += remote.o
LIB_OBJS += replace_object.o
LIB_OBJS += rerere.o
LIB_OBJS += submodule.o
LIB_OBJS += symlinks.o
LIB_OBJS += tag.o
+ LIB_OBJS += tempfile.o
LIB_OBJS += trace.o
LIB_OBJS += trailer.o
LIB_OBJS += transport.o
LIB_OBJS += zlib.o
BUILTIN_OBJS += builtin/add.o
+BUILTIN_OBJS += builtin/am.o
BUILTIN_OBJS += builtin/annotate.o
BUILTIN_OBJS += builtin/apply.o
BUILTIN_OBJS += builtin/archive.o
BUILTIN_OBJS += builtin/patch-id.o
BUILTIN_OBJS += builtin/prune-packed.o
BUILTIN_OBJS += builtin/prune.o
+BUILTIN_OBJS += builtin/pull.o
BUILTIN_OBJS += builtin/push.o
BUILTIN_OBJS += builtin/read-tree.o
BUILTIN_OBJS += builtin/receive-pack.o
BUILTIN_OBJS += builtin/verify-commit.o
BUILTIN_OBJS += builtin/verify-pack.o
BUILTIN_OBJS += builtin/verify-tag.o
+BUILTIN_OBJS += builtin/worktree.o
BUILTIN_OBJS += builtin/write-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
perl/perl.mak: perl/PM.stamp
perl/PM.stamp: FORCE
- $(QUIET_GEN)$(FIND) perl -type f -name '*.pm' | sort >$@+ && \
+ @$(FIND) perl -type f -name '*.pm' | sort >$@+ && \
{ cmp $@+ $@ >/dev/null 2>/dev/null || mv $@+ $@; } && \
$(RM) $@+
gitweb:
$(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all
-git-instaweb: git-instaweb.sh gitweb GIT-SCRIPT-DEFINES
+git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES
$(QUIET_GEN)$(cmd_munge_script) && \
chmod +x $@+ && \
mv $@+ $@
# that runs GIT-BUILD-OPTIONS, and then again to protect it
# and the first level quoting from the shell that runs "echo".
GIT-BUILD-OPTIONS: FORCE
- @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
- @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@
- @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@
- @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@
- @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
- @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
- @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@
- @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@
- @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
- @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
- @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
+ @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@+
+ @echo PERL_PATH=\''$(subst ','\'',$(PERL_PATH_SQ))'\' >>$@+
+ @echo DIFF=\''$(subst ','\'',$(subst ','\'',$(DIFF)))'\' >>$@+
+ @echo PYTHON_PATH=\''$(subst ','\'',$(PYTHON_PATH_SQ))'\' >>$@+
+ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+
+ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+
+ @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+
+ @echo USE_LIBPCRE=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE)))'\' >>$@+
+ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+
+ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+
+ @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@+
ifdef TEST_OUTPUT_DIRECTORY
- @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@
+ @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+
endif
ifdef GIT_TEST_OPTS
- @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@
+ @echo GIT_TEST_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_OPTS)))'\' >>$@+
endif
ifdef GIT_TEST_CMP
- @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@
+ @echo GIT_TEST_CMP=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_CMP)))'\' >>$@+
endif
ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
- @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
+ @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@+
endif
- @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@
- @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
+ @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@+
+ @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@+
ifdef GIT_PERF_REPEAT_COUNT
- @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@
+ @echo GIT_PERF_REPEAT_COUNT=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPEAT_COUNT)))'\' >>$@+
endif
ifdef GIT_PERF_REPO
- @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@
+ @echo GIT_PERF_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_REPO)))'\' >>$@+
endif
ifdef GIT_PERF_LARGE_REPO
- @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@
+ @echo GIT_PERF_LARGE_REPO=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_LARGE_REPO)))'\' >>$@+
endif
ifdef GIT_PERF_MAKE_OPTS
- @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@
+ @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@+
endif
ifdef TEST_GIT_INDEX_VERSION
- @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@
+ @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+
endif
+ @if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi
### Detect Python interpreter path changes
ifndef NO_PYTHON
--- /dev/null
+/*
+ * Builtin "git am"
+ *
+ * Based on git-am.sh by Junio C Hamano.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "parse-options.h"
+#include "dir.h"
+#include "run-command.h"
+#include "quote.h"
++#include "tempfile.h"
+#include "lockfile.h"
+#include "cache-tree.h"
+#include "refs.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "unpack-trees.h"
+#include "branch.h"
+#include "sequencer.h"
+#include "revision.h"
+#include "merge-recursive.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "notes-utils.h"
+#include "rerere.h"
+#include "prompt.h"
+
+/**
+ * Returns 1 if the file is empty or does not exist, 0 otherwise.
+ */
+static int is_empty_file(const char *filename)
+{
+ struct stat st;
+
+ if (stat(filename, &st) < 0) {
+ if (errno == ENOENT)
+ return 1;
+ die_errno(_("could not stat %s"), filename);
+ }
+
+ return !st.st_size;
+}
+
+/**
+ * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
+ */
+static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
+{
+ if (strbuf_getwholeline(sb, fp, '\n'))
+ return EOF;
+ if (sb->buf[sb->len - 1] == '\n') {
+ strbuf_setlen(sb, sb->len - 1);
+ if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
+ strbuf_setlen(sb, sb->len - 1);
+ }
+ return 0;
+}
+
+/**
+ * Returns the length of the first line of msg.
+ */
+static int linelen(const char *msg)
+{
+ return strchrnul(msg, '\n') - msg;
+}
+
+/**
+ * Returns true if `str` consists of only whitespace, false otherwise.
+ */
+static int str_isspace(const char *str)
+{
+ for (; *str; str++)
+ if (!isspace(*str))
+ return 0;
+
+ return 1;
+}
+
+enum patch_format {
+ PATCH_FORMAT_UNKNOWN = 0,
+ PATCH_FORMAT_MBOX,
+ PATCH_FORMAT_STGIT,
+ PATCH_FORMAT_STGIT_SERIES,
+ PATCH_FORMAT_HG
+};
+
+enum keep_type {
+ KEEP_FALSE = 0,
+ KEEP_TRUE, /* pass -k flag to git-mailinfo */
+ KEEP_NON_PATCH /* pass -b flag to git-mailinfo */
+};
+
+enum scissors_type {
+ SCISSORS_UNSET = -1,
+ SCISSORS_FALSE = 0, /* pass --no-scissors to git-mailinfo */
+ SCISSORS_TRUE /* pass --scissors to git-mailinfo */
+};
+
+enum signoff_type {
+ SIGNOFF_FALSE = 0,
+ SIGNOFF_TRUE = 1,
+ SIGNOFF_EXPLICIT /* --signoff was set on the command-line */
+};
+
+struct am_state {
+ /* state directory path */
+ char *dir;
+
+ /* current and last patch numbers, 1-indexed */
+ int cur;
+ int last;
+
+ /* commit metadata and message */
+ char *author_name;
+ char *author_email;
+ char *author_date;
+ char *msg;
+ size_t msg_len;
+
+ /* when --rebasing, records the original commit the patch came from */
+ unsigned char orig_commit[GIT_SHA1_RAWSZ];
+
+ /* number of digits in patch filename */
+ int prec;
+
+ /* various operating modes and command line options */
+ int interactive;
+ int threeway;
+ int quiet;
+ int signoff; /* enum signoff_type */
+ int utf8;
+ int keep; /* enum keep_type */
+ int message_id;
+ int scissors; /* enum scissors_type */
+ struct argv_array git_apply_opts;
+ const char *resolvemsg;
+ int committer_date_is_author_date;
+ int ignore_date;
+ int allow_rerere_autoupdate;
+ const char *sign_commit;
+ int rebasing;
+};
+
+/**
+ * Initializes am_state with the default values. The state directory is set to
+ * dir.
+ */
+static void am_state_init(struct am_state *state, const char *dir)
+{
+ int gpgsign;
+
+ memset(state, 0, sizeof(*state));
+
+ assert(dir);
+ state->dir = xstrdup(dir);
+
+ state->prec = 4;
+
+ git_config_get_bool("am.threeway", &state->threeway);
+
+ state->utf8 = 1;
+
+ git_config_get_bool("am.messageid", &state->message_id);
+
+ state->scissors = SCISSORS_UNSET;
+
+ argv_array_init(&state->git_apply_opts);
+
+ if (!git_config_get_bool("commit.gpgsign", &gpgsign))
+ state->sign_commit = gpgsign ? "" : NULL;
+}
+
+/**
+ * Releases memory allocated by an am_state.
+ */
+static void am_state_release(struct am_state *state)
+{
+ free(state->dir);
+ free(state->author_name);
+ free(state->author_email);
+ free(state->author_date);
+ free(state->msg);
+ argv_array_clear(&state->git_apply_opts);
+}
+
+/**
+ * Returns path relative to the am_state directory.
+ */
+static inline const char *am_path(const struct am_state *state, const char *path)
+{
+ return mkpath("%s/%s", state->dir, path);
+}
+
+/**
+ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline
+ * at the end.
+ */
+static void say(const struct am_state *state, FILE *fp, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (!state->quiet) {
+ vfprintf(fp, fmt, ap);
+ putc('\n', fp);
+ }
+ va_end(ap);
+}
+
+/**
+ * Returns 1 if there is an am session in progress, 0 otherwise.
+ */
+static int am_in_progress(const struct am_state *state)
+{
+ struct stat st;
+
+ if (lstat(state->dir, &st) < 0 || !S_ISDIR(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "last"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ if (lstat(am_path(state, "next"), &st) || !S_ISREG(st.st_mode))
+ return 0;
+ return 1;
+}
+
+/**
+ * Reads the contents of `file` in the `state` directory into `sb`. Returns the
+ * number of bytes read on success, -1 if the file does not exist. If `trim` is
+ * set, trailing whitespace will be removed.
+ */
+static int read_state_file(struct strbuf *sb, const struct am_state *state,
+ const char *file, int trim)
+{
+ strbuf_reset(sb);
+
+ if (strbuf_read_file(sb, am_path(state, file), 0) >= 0) {
+ if (trim)
+ strbuf_trim(sb);
+
+ return sb->len;
+ }
+
+ if (errno == ENOENT)
+ return -1;
+
+ die_errno(_("could not read '%s'"), am_path(state, file));
+}
+
+/**
+ * Reads a KEY=VALUE shell variable assignment from `fp`, returning the VALUE
+ * as a newly-allocated string. VALUE must be a quoted string, and the KEY must
+ * match `key`. Returns NULL on failure.
+ *
+ * This is used by read_author_script() to read the GIT_AUTHOR_* variables from
+ * the author-script.
+ */
+static char *read_shell_var(FILE *fp, const char *key)
+{
+ struct strbuf sb = STRBUF_INIT;
+ const char *str;
+
+ if (strbuf_getline(&sb, fp, '\n'))
+ goto fail;
+
+ if (!skip_prefix(sb.buf, key, &str))
+ goto fail;
+
+ if (!skip_prefix(str, "=", &str))
+ goto fail;
+
+ strbuf_remove(&sb, 0, str - sb.buf);
+
+ str = sq_dequote(sb.buf);
+ if (!str)
+ goto fail;
+
+ return strbuf_detach(&sb, NULL);
+
+fail:
+ strbuf_release(&sb);
+ return NULL;
+}
+
+/**
+ * Reads and parses the state directory's "author-script" file, and sets
+ * state->author_name, state->author_email and state->author_date accordingly.
+ * Returns 0 on success, -1 if the file could not be parsed.
+ *
+ * The author script is of the format:
+ *
+ * GIT_AUTHOR_NAME='$author_name'
+ * GIT_AUTHOR_EMAIL='$author_email'
+ * GIT_AUTHOR_DATE='$author_date'
+ *
+ * where $author_name, $author_email and $author_date are quoted. We are strict
+ * with our parsing, as the file was meant to be eval'd in the old git-am.sh
+ * script, and thus if the file differs from what this function expects, it is
+ * better to bail out than to do something that the user does not expect.
+ */
+static int read_author_script(struct am_state *state)
+{
+ const char *filename = am_path(state, "author-script");
+ FILE *fp;
+
+ assert(!state->author_name);
+ assert(!state->author_email);
+ assert(!state->author_date);
+
+ fp = fopen(filename, "r");
+ if (!fp) {
+ if (errno == ENOENT)
+ return 0;
+ die_errno(_("could not open '%s' for reading"), filename);
+ }
+
+ state->author_name = read_shell_var(fp, "GIT_AUTHOR_NAME");
+ if (!state->author_name) {
+ fclose(fp);
+ return -1;
+ }
+
+ state->author_email = read_shell_var(fp, "GIT_AUTHOR_EMAIL");
+ if (!state->author_email) {
+ fclose(fp);
+ return -1;
+ }
+
+ state->author_date = read_shell_var(fp, "GIT_AUTHOR_DATE");
+ if (!state->author_date) {
+ fclose(fp);
+ return -1;
+ }
+
+ if (fgetc(fp) != EOF) {
+ fclose(fp);
+ return -1;
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Saves state->author_name, state->author_email and state->author_date in the
+ * state directory's "author-script" file.
+ */
+static void write_author_script(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_NAME=");
+ sq_quote_buf(&sb, state->author_name);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_EMAIL=");
+ sq_quote_buf(&sb, state->author_email);
+ strbuf_addch(&sb, '\n');
+
+ strbuf_addstr(&sb, "GIT_AUTHOR_DATE=");
+ sq_quote_buf(&sb, state->author_date);
+ strbuf_addch(&sb, '\n');
+
+ write_file(am_path(state, "author-script"), 1, "%s", sb.buf);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Reads the commit message from the state directory's "final-commit" file,
+ * setting state->msg to its contents and state->msg_len to the length of its
+ * contents in bytes.
+ *
+ * Returns 0 on success, -1 if the file does not exist.
+ */
+static int read_commit_msg(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ assert(!state->msg);
+
+ if (read_state_file(&sb, state, "final-commit", 0) < 0) {
+ strbuf_release(&sb);
+ return -1;
+ }
+
+ state->msg = strbuf_detach(&sb, &state->msg_len);
+ return 0;
+}
+
+/**
+ * Saves state->msg in the state directory's "final-commit" file.
+ */
+static void write_commit_msg(const struct am_state *state)
+{
+ int fd;
+ const char *filename = am_path(state, "final-commit");
+
+ fd = xopen(filename, O_WRONLY | O_CREAT, 0666);
+ if (write_in_full(fd, state->msg, state->msg_len) < 0)
+ die_errno(_("could not write to %s"), filename);
+ close(fd);
+}
+
+/**
+ * Loads state from disk.
+ */
+static void am_load(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ if (read_state_file(&sb, state, "next", 1) < 0)
+ die("BUG: state file 'next' does not exist");
+ state->cur = strtol(sb.buf, NULL, 10);
+
+ if (read_state_file(&sb, state, "last", 1) < 0)
+ die("BUG: state file 'last' does not exist");
+ state->last = strtol(sb.buf, NULL, 10);
+
+ if (read_author_script(state) < 0)
+ die(_("could not parse author script"));
+
+ read_commit_msg(state);
+
+ if (read_state_file(&sb, state, "original-commit", 1) < 0)
+ hashclr(state->orig_commit);
+ else if (get_sha1_hex(sb.buf, state->orig_commit) < 0)
+ die(_("could not parse %s"), am_path(state, "original-commit"));
+
+ read_state_file(&sb, state, "threeway", 1);
+ state->threeway = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "quiet", 1);
+ state->quiet = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "sign", 1);
+ state->signoff = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "utf8", 1);
+ state->utf8 = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "keep", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->keep = KEEP_TRUE;
+ else if (!strcmp(sb.buf, "b"))
+ state->keep = KEEP_NON_PATCH;
+ else
+ state->keep = KEEP_FALSE;
+
+ read_state_file(&sb, state, "messageid", 1);
+ state->message_id = !strcmp(sb.buf, "t");
+
+ read_state_file(&sb, state, "scissors", 1);
+ if (!strcmp(sb.buf, "t"))
+ state->scissors = SCISSORS_TRUE;
+ else if (!strcmp(sb.buf, "f"))
+ state->scissors = SCISSORS_FALSE;
+ else
+ state->scissors = SCISSORS_UNSET;
+
+ read_state_file(&sb, state, "apply-opt", 1);
+ argv_array_clear(&state->git_apply_opts);
+ if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0)
+ die(_("could not parse %s"), am_path(state, "apply-opt"));
+
+ state->rebasing = !!file_exists(am_path(state, "rebasing"));
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Removes the am_state directory, forcefully terminating the current am
+ * session.
+ */
+static void am_destroy(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, state->dir);
+ remove_dir_recursively(&sb, 0);
+ strbuf_release(&sb);
+}
+
+/**
+ * Runs applypatch-msg hook. Returns its exit code.
+ */
+static int run_applypatch_msg_hook(struct am_state *state)
+{
+ int ret;
+
+ assert(state->msg);
+ ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL);
+
+ if (!ret) {
+ free(state->msg);
+ state->msg = NULL;
+ if (read_commit_msg(state) < 0)
+ die(_("'%s' was deleted by the applypatch-msg hook"),
+ am_path(state, "final-commit"));
+ }
+
+ return ret;
+}
+
+/**
+ * Runs post-rewrite hook. Returns it exit code.
+ */
+static int run_post_rewrite_hook(const struct am_state *state)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ const char *hook = find_hook("post-rewrite");
+ int ret;
+
+ if (!hook)
+ return 0;
+
+ argv_array_push(&cp.args, hook);
+ argv_array_push(&cp.args, "rebase");
+
+ cp.in = xopen(am_path(state, "rewritten"), O_RDONLY);
+ cp.stdout_to_stderr = 1;
+
+ ret = run_command(&cp);
+
+ close(cp.in);
+ return ret;
+}
+
+/**
+ * Reads the state directory's "rewritten" file, and copies notes from the old
+ * commits listed in the file to their rewritten commits.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int copy_notes_for_rebase(const struct am_state *state)
+{
+ struct notes_rewrite_cfg *c;
+ struct strbuf sb = STRBUF_INIT;
+ const char *invalid_line = _("Malformed input line: '%s'.");
+ const char *msg = "Notes added by 'git rebase'";
+ FILE *fp;
+ int ret = 0;
+
+ assert(state->rebasing);
+
+ c = init_copy_notes_for_rewrite("rebase");
+ if (!c)
+ return 0;
+
+ fp = xfopen(am_path(state, "rewritten"), "r");
+
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ unsigned char from_obj[GIT_SHA1_RAWSZ], to_obj[GIT_SHA1_RAWSZ];
+
+ if (sb.len != GIT_SHA1_HEXSZ * 2 + 1) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_sha1_hex(sb.buf, from_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (sb.buf[GIT_SHA1_HEXSZ] != ' ') {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (get_sha1_hex(sb.buf + GIT_SHA1_HEXSZ + 1, to_obj)) {
+ ret = error(invalid_line, sb.buf);
+ goto finish;
+ }
+
+ if (copy_note_for_rewrite(c, from_obj, to_obj))
+ ret = error(_("Failed to copy notes from '%s' to '%s'"),
+ sha1_to_hex(from_obj), sha1_to_hex(to_obj));
+ }
+
+finish:
+ finish_copy_notes_for_rewrite(c, msg);
+ fclose(fp);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Determines if the file looks like a piece of RFC2822 mail by grabbing all
+ * non-indented lines and checking if they look like they begin with valid
+ * header field names.
+ *
+ * Returns 1 if the file looks like a piece of mail, 0 otherwise.
+ */
+static int is_mail(FILE *fp)
+{
+ const char *header_regex = "^[!-9;-~]+:";
+ struct strbuf sb = STRBUF_INIT;
+ regex_t regex;
+ int ret = 1;
+
+ if (fseek(fp, 0L, SEEK_SET))
+ die_errno(_("fseek failed"));
+
+ if (regcomp(®ex, header_regex, REG_NOSUB | REG_EXTENDED))
+ die("invalid pattern: %s", header_regex);
+
+ while (!strbuf_getline_crlf(&sb, fp)) {
+ if (!sb.len)
+ break; /* End of header */
+
+ /* Ignore indented folded lines */
+ if (*sb.buf == '\t' || *sb.buf == ' ')
+ continue;
+
+ /* It's a header if it matches header_regex */
+ if (regexec(®ex, sb.buf, 0, NULL, 0)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+done:
+ regfree(®ex);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Attempts to detect the patch_format of the patches contained in `paths`,
+ * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
+ * detection fails.
+ */
+static int detect_patch_format(const char **paths)
+{
+ enum patch_format ret = PATCH_FORMAT_UNKNOWN;
+ struct strbuf l1 = STRBUF_INIT;
+ struct strbuf l2 = STRBUF_INIT;
+ struct strbuf l3 = STRBUF_INIT;
+ FILE *fp;
+
+ /*
+ * We default to mbox format if input is from stdin and for directories
+ */
+ if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
+ return PATCH_FORMAT_MBOX;
+
+ /*
+ * Otherwise, check the first few lines of the first patch, starting
+ * from the first non-blank line, to try to detect its format.
+ */
+
+ fp = xfopen(*paths, "r");
+
+ while (!strbuf_getline_crlf(&l1, fp)) {
+ if (l1.len)
+ break;
+ }
+
+ if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+ if (starts_with(l1.buf, "# This series applies on GIT commit")) {
+ ret = PATCH_FORMAT_STGIT_SERIES;
+ goto done;
+ }
+
+ if (!strcmp(l1.buf, "# HG changeset patch")) {
+ ret = PATCH_FORMAT_HG;
+ goto done;
+ }
+
+ strbuf_reset(&l2);
+ strbuf_getline_crlf(&l2, fp);
+ strbuf_reset(&l3);
+ strbuf_getline_crlf(&l3, fp);
+
+ /*
+ * If the second line is empty and the third is a From, Author or Date
+ * entry, this is likely an StGit patch.
+ */
+ if (l1.len && !l2.len &&
+ (starts_with(l3.buf, "From:") ||
+ starts_with(l3.buf, "Author:") ||
+ starts_with(l3.buf, "Date:"))) {
+ ret = PATCH_FORMAT_STGIT;
+ goto done;
+ }
+
+ if (l1.len && is_mail(fp)) {
+ ret = PATCH_FORMAT_MBOX;
+ goto done;
+ }
+
+done:
+ fclose(fp);
+ strbuf_release(&l1);
+ return ret;
+}
+
+/**
+ * Splits out individual email patches from `paths`, where each path is either
+ * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
+ */
+static int split_mail_mbox(struct am_state *state, const char **paths, int keep_cr)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf last = STRBUF_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "mailsplit");
+ argv_array_pushf(&cp.args, "-d%d", state->prec);
+ argv_array_pushf(&cp.args, "-o%s", state->dir);
+ argv_array_push(&cp.args, "-b");
+ if (keep_cr)
+ argv_array_push(&cp.args, "--keep-cr");
+ argv_array_push(&cp.args, "--");
+ argv_array_pushv(&cp.args, paths);
+
+ if (capture_command(&cp, &last, 8))
+ return -1;
+
+ state->cur = 1;
+ state->last = strtol(last.buf, NULL, 10);
+
+ return 0;
+}
+
+/**
+ * Callback signature for split_mail_conv(). The foreign patch should be
+ * read from `in`, and the converted patch (in RFC2822 mail format) should be
+ * written to `out`. Return 0 on success, or -1 on failure.
+ */
+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
+
+/**
+ * Calls `fn` for each file in `paths` to convert the foreign patch to the
+ * RFC2822 mail format suitable for parsing with git-mailinfo.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
+ const char **paths, int keep_cr)
+{
+ static const char *stdin_only[] = {"-", NULL};
+ int i;
+
+ if (!*paths)
+ paths = stdin_only;
+
+ for (i = 0; *paths; paths++, i++) {
+ FILE *in, *out;
+ const char *mail;
+ int ret;
+
+ if (!strcmp(*paths, "-"))
+ in = stdin;
+ else
+ in = fopen(*paths, "r");
+
+ if (!in)
+ return error(_("could not open '%s' for reading: %s"),
+ *paths, strerror(errno));
+
+ mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
+
+ out = fopen(mail, "w");
+ if (!out)
+ return error(_("could not open '%s' for writing: %s"),
+ mail, strerror(errno));
+
+ ret = fn(out, in, keep_cr);
+
+ fclose(out);
+ fclose(in);
+
+ if (ret)
+ return error(_("could not parse patch '%s'"), *paths);
+ }
+
+ state->cur = 1;
+ state->last = i;
+ return 0;
+}
+
+/**
+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int subject_printed = 0;
+
+ while (!strbuf_getline(&sb, in, '\n')) {
+ const char *str;
+
+ if (str_isspace(sb.buf))
+ continue;
+ else if (skip_prefix(sb.buf, "Author:", &str))
+ fprintf(out, "From:%s\n", str);
+ else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
+ fprintf(out, "%s\n", sb.buf);
+ else if (!subject_printed) {
+ fprintf(out, "Subject: %s\n", sb.buf);
+ subject_printed = 1;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * This function only supports a single StGit series file in `paths`.
+ *
+ * Given an StGit series file, converts the StGit patches in the series into
+ * RFC2822 messages suitable for parsing with git-mailinfo, and queues them in
+ * the state directory.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_stgit_series(struct am_state *state, const char **paths,
+ int keep_cr)
+{
+ const char *series_dir;
+ char *series_dir_buf;
+ FILE *fp;
+ struct argv_array patches = ARGV_ARRAY_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ int ret;
+
+ if (!paths[0] || paths[1])
+ return error(_("Only one StGIT patch series can be applied at once"));
+
+ series_dir_buf = xstrdup(*paths);
+ series_dir = dirname(series_dir_buf);
+
+ fp = fopen(*paths, "r");
+ if (!fp)
+ return error(_("could not open '%s' for reading: %s"), *paths,
+ strerror(errno));
+
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ if (*sb.buf == '#')
+ continue; /* skip comment lines */
+
+ argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf));
+ }
+
+ fclose(fp);
+ strbuf_release(&sb);
+ free(series_dir_buf);
+
+ ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr);
+
+ argv_array_clear(&patches);
+ return ret;
+}
+
+/**
+ * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ while (!strbuf_getline(&sb, in, '\n')) {
+ const char *str;
+
+ if (skip_prefix(sb.buf, "# User ", &str))
+ fprintf(out, "From: %s\n", str);
+ else if (skip_prefix(sb.buf, "# Date ", &str)) {
+ unsigned long timestamp;
+ long tz, tz2;
+ char *end;
+
+ errno = 0;
+ timestamp = strtoul(str, &end, 10);
+ if (errno)
+ return error(_("invalid timestamp"));
+
+ if (!skip_prefix(end, " ", &str))
+ return error(_("invalid Date line"));
+
+ errno = 0;
+ tz = strtol(str, &end, 10);
+ if (errno)
+ return error(_("invalid timezone offset"));
+
+ if (*end)
+ return error(_("invalid Date line"));
+
+ /*
+ * mercurial's timezone is in seconds west of UTC,
+ * however git's timezone is in hours + minutes east of
+ * UTC. Convert it.
+ */
+ tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
+ if (tz > 0)
+ tz2 = -tz2;
+
+ fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822)));
+ } else if (starts_with(sb.buf, "# ")) {
+ continue;
+ } else {
+ fprintf(out, "\n%s\n", sb.buf);
+ break;
+ }
+ }
+
+ strbuf_reset(&sb);
+ while (strbuf_fread(&sb, 8192, in) > 0) {
+ fwrite(sb.buf, 1, sb.len, out);
+ strbuf_reset(&sb);
+ }
+
+ strbuf_release(&sb);
+ return 0;
+}
+
+/**
+ * Splits a list of files/directories into individual email patches. Each path
+ * in `paths` must be a file/directory that is formatted according to
+ * `patch_format`.
+ *
+ * Once split out, the individual email patches will be stored in the state
+ * directory, with each patch's filename being its index, padded to state->prec
+ * digits.
+ *
+ * state->cur will be set to the index of the first mail, and state->last will
+ * be set to the index of the last mail.
+ *
+ * Set keep_cr to 0 to convert all lines ending with \r\n to end with \n, 1
+ * to disable this behavior, -1 to use the default configured setting.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ if (keep_cr < 0) {
+ keep_cr = 0;
+ git_config_get_bool("am.keepcr", &keep_cr);
+ }
+
+ switch (patch_format) {
+ case PATCH_FORMAT_MBOX:
+ return split_mail_mbox(state, paths, keep_cr);
+ case PATCH_FORMAT_STGIT:
+ return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
+ case PATCH_FORMAT_STGIT_SERIES:
+ return split_mail_stgit_series(state, paths, keep_cr);
+ case PATCH_FORMAT_HG:
+ return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
+ default:
+ die("BUG: invalid patch_format");
+ }
+ return -1;
+}
+
+/**
+ * Setup a new am session for applying patches
+ */
+static void am_setup(struct am_state *state, enum patch_format patch_format,
+ const char **paths, int keep_cr)
+{
+ unsigned char curr_head[GIT_SHA1_RAWSZ];
+ const char *str;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (!patch_format)
+ patch_format = detect_patch_format(paths);
+
+ if (!patch_format) {
+ fprintf_ln(stderr, _("Patch format detection failed."));
+ exit(128);
+ }
+
+ if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
+ die_errno(_("failed to create directory '%s'"), state->dir);
+
+ if (split_mail(state, patch_format, paths, keep_cr) < 0) {
+ am_destroy(state);
+ die(_("Failed to split patches."));
+ }
+
+ if (state->rebasing)
+ state->threeway = 1;
+
+ write_file(am_path(state, "threeway"), 1, state->threeway ? "t" : "f");
+
+ write_file(am_path(state, "quiet"), 1, state->quiet ? "t" : "f");
+
+ write_file(am_path(state, "sign"), 1, state->signoff ? "t" : "f");
+
+ write_file(am_path(state, "utf8"), 1, state->utf8 ? "t" : "f");
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ str = "f";
+ break;
+ case KEEP_TRUE:
+ str = "t";
+ break;
+ case KEEP_NON_PATCH:
+ str = "b";
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ write_file(am_path(state, "keep"), 1, "%s", str);
+
+ write_file(am_path(state, "messageid"), 1, state->message_id ? "t" : "f");
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ str = "";
+ break;
+ case SCISSORS_FALSE:
+ str = "f";
+ break;
+ case SCISSORS_TRUE:
+ str = "t";
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+
+ write_file(am_path(state, "scissors"), 1, "%s", str);
+
+ sq_quote_argv(&sb, state->git_apply_opts.argv, 0);
+ write_file(am_path(state, "apply-opt"), 1, "%s", sb.buf);
+
+ if (state->rebasing)
+ write_file(am_path(state, "rebasing"), 1, "%s", "");
+ else
+ write_file(am_path(state, "applying"), 1, "%s", "");
+
+ if (!get_sha1("HEAD", curr_head)) {
+ write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(curr_head));
+ if (!state->rebasing)
+ update_ref("am", "ORIG_HEAD", curr_head, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ } else {
+ write_file(am_path(state, "abort-safety"), 1, "%s", "");
+ if (!state->rebasing)
+ delete_ref("ORIG_HEAD", NULL, 0);
+ }
+
+ /*
+ * NOTE: Since the "next" and "last" files determine if an am_state
+ * session is in progress, they should be written last.
+ */
+
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+
+ write_file(am_path(state, "last"), 1, "%d", state->last);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Increments the patch pointer, and cleans am_state for the application of the
+ * next patch.
+ */
+static void am_next(struct am_state *state)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+
+ free(state->author_name);
+ state->author_name = NULL;
+
+ free(state->author_email);
+ state->author_email = NULL;
+
+ free(state->author_date);
+ state->author_date = NULL;
+
+ free(state->msg);
+ state->msg = NULL;
+ state->msg_len = 0;
+
+ unlink(am_path(state, "author-script"));
+ unlink(am_path(state, "final-commit"));
+
+ hashclr(state->orig_commit);
+ unlink(am_path(state, "original-commit"));
+
+ if (!get_sha1("HEAD", head))
+ write_file(am_path(state, "abort-safety"), 1, "%s", sha1_to_hex(head));
+ else
+ write_file(am_path(state, "abort-safety"), 1, "%s", "");
+
+ state->cur++;
+ write_file(am_path(state, "next"), 1, "%d", state->cur);
+}
+
+/**
+ * Returns the filename of the current patch email.
+ */
+static const char *msgnum(const struct am_state *state)
+{
+ static struct strbuf sb = STRBUF_INIT;
+
+ strbuf_reset(&sb);
+ strbuf_addf(&sb, "%0*d", state->prec, state->cur);
+
+ return sb.buf;
+}
+
+/**
+ * Refresh and write index.
+ */
+static void refresh_and_write_cache(void)
+{
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+
+ hold_locked_index(lock_file, 1);
+ refresh_cache(REFRESH_QUIET);
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write index file"));
+}
+
+/**
+ * Returns 1 if the index differs from HEAD, 0 otherwise. When on an unborn
+ * branch, returns 1 if there are entries in the index, 0 otherwise. If an
+ * strbuf is provided, the space-separated list of files that differ will be
+ * appended to it.
+ */
+static int index_has_changes(struct strbuf *sb)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+ int i;
+
+ if (!get_sha1_tree("HEAD", head)) {
+ struct diff_options opt;
+
+ diff_setup(&opt);
+ DIFF_OPT_SET(&opt, EXIT_WITH_STATUS);
+ if (!sb)
+ DIFF_OPT_SET(&opt, QUICK);
+ do_diff_cache(head, &opt);
+ diffcore_std(&opt);
+ for (i = 0; sb && i < diff_queued_diff.nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, diff_queued_diff.queue[i]->two->path);
+ }
+ diff_flush(&opt);
+ return DIFF_OPT_TST(&opt, HAS_CHANGES) != 0;
+ } else {
+ for (i = 0; sb && i < active_nr; i++) {
+ if (i)
+ strbuf_addch(sb, ' ');
+ strbuf_addstr(sb, active_cache[i]->name);
+ }
+ return !!active_nr;
+ }
+}
+
+/**
+ * Dies with a user-friendly message on how to proceed after resolving the
+ * problem. This message can be overridden with state->resolvemsg.
+ */
+static void NORETURN die_user_resolve(const struct am_state *state)
+{
+ if (state->resolvemsg) {
+ printf_ln("%s", state->resolvemsg);
+ } else {
+ const char *cmdline = state->interactive ? "git am -i" : "git am";
+
+ printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline);
+ printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline);
+ printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline);
+ }
+
+ exit(128);
+}
+
+/**
+ * Appends signoff to the "msg" field of the am_state.
+ */
+static void am_append_signoff(struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len);
+ append_signoff(&sb, 0, 0);
+ state->msg = strbuf_detach(&sb, &state->msg_len);
+}
+
+/**
+ * Parses `mail` using git-mailinfo, extracting its patch and authorship info.
+ * state->msg will be set to the patch message. state->author_name,
+ * state->author_email and state->author_date will be set to the patch author's
+ * name, email and date respectively. The patch body will be written to the
+ * state directory's "patch" file.
+ *
+ * Returns 1 if the patch should be skipped, 0 otherwise.
+ */
+static int parse_mail(struct am_state *state, const char *mail)
+{
+ FILE *fp;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ struct strbuf msg = STRBUF_INIT;
+ struct strbuf author_name = STRBUF_INIT;
+ struct strbuf author_date = STRBUF_INIT;
+ struct strbuf author_email = STRBUF_INIT;
+ int ret = 0;
+
+ cp.git_cmd = 1;
+ cp.in = xopen(mail, O_RDONLY, 0);
+ cp.out = xopen(am_path(state, "info"), O_WRONLY | O_CREAT, 0777);
+
+ argv_array_push(&cp.args, "mailinfo");
+ argv_array_push(&cp.args, state->utf8 ? "-u" : "-n");
+
+ switch (state->keep) {
+ case KEEP_FALSE:
+ break;
+ case KEEP_TRUE:
+ argv_array_push(&cp.args, "-k");
+ break;
+ case KEEP_NON_PATCH:
+ argv_array_push(&cp.args, "-b");
+ break;
+ default:
+ die("BUG: invalid value for state->keep");
+ }
+
+ if (state->message_id)
+ argv_array_push(&cp.args, "-m");
+
+ switch (state->scissors) {
+ case SCISSORS_UNSET:
+ break;
+ case SCISSORS_FALSE:
+ argv_array_push(&cp.args, "--no-scissors");
+ break;
+ case SCISSORS_TRUE:
+ argv_array_push(&cp.args, "--scissors");
+ break;
+ default:
+ die("BUG: invalid value for state->scissors");
+ }
+
+ argv_array_push(&cp.args, am_path(state, "msg"));
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp) < 0)
+ die("could not parse patch");
+
+ close(cp.in);
+ close(cp.out);
+
+ /* Extract message and author information */
+ fp = xfopen(am_path(state, "info"), "r");
+ while (!strbuf_getline(&sb, fp, '\n')) {
+ const char *x;
+
+ if (skip_prefix(sb.buf, "Subject: ", &x)) {
+ if (msg.len)
+ strbuf_addch(&msg, '\n');
+ strbuf_addstr(&msg, x);
+ } else if (skip_prefix(sb.buf, "Author: ", &x))
+ strbuf_addstr(&author_name, x);
+ else if (skip_prefix(sb.buf, "Email: ", &x))
+ strbuf_addstr(&author_email, x);
+ else if (skip_prefix(sb.buf, "Date: ", &x))
+ strbuf_addstr(&author_date, x);
+ }
+ fclose(fp);
+
+ /* Skip pine's internal folder data */
+ if (!strcmp(author_name.buf, "Mail System Internal Data")) {
+ ret = 1;
+ goto finish;
+ }
+
+ if (is_empty_file(am_path(state, "patch"))) {
+ printf_ln(_("Patch is empty. Was it split wrong?"));
+ die_user_resolve(state);
+ }
+
+ strbuf_addstr(&msg, "\n\n");
+ if (strbuf_read_file(&msg, am_path(state, "msg"), 0) < 0)
+ die_errno(_("could not read '%s'"), am_path(state, "msg"));
+ stripspace(&msg, 0);
+
+ if (state->signoff)
+ append_signoff(&msg, 0, 0);
+
+ assert(!state->author_name);
+ state->author_name = strbuf_detach(&author_name, NULL);
+
+ assert(!state->author_email);
+ state->author_email = strbuf_detach(&author_email, NULL);
+
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&author_date, NULL);
+
+ assert(!state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+
+finish:
+ strbuf_release(&msg);
+ strbuf_release(&author_date);
+ strbuf_release(&author_email);
+ strbuf_release(&author_name);
+ strbuf_release(&sb);
+ return ret;
+}
+
+/**
+ * Sets commit_id to the commit hash where the mail was generated from.
+ * Returns 0 on success, -1 on failure.
+ */
+static int get_mail_commit_sha1(unsigned char *commit_id, const char *mail)
+{
+ struct strbuf sb = STRBUF_INIT;
+ FILE *fp = xfopen(mail, "r");
+ const char *x;
+
+ if (strbuf_getline(&sb, fp, '\n'))
+ return -1;
+
+ if (!skip_prefix(sb.buf, "From ", &x))
+ return -1;
+
+ if (get_sha1_hex(x, commit_id) < 0)
+ return -1;
+
+ strbuf_release(&sb);
+ fclose(fp);
+ return 0;
+}
+
+/**
+ * Sets state->msg, state->author_name, state->author_email, state->author_date
+ * to the commit's respective info.
+ */
+static void get_commit_info(struct am_state *state, struct commit *commit)
+{
+ const char *buffer, *ident_line, *author_date, *msg;
+ size_t ident_len;
+ struct ident_split ident_split;
+ struct strbuf sb = STRBUF_INIT;
+
+ buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding());
+
+ ident_line = find_commit_header(buffer, "author", &ident_len);
+
+ if (split_ident_line(&ident_split, ident_line, ident_len) < 0) {
+ strbuf_add(&sb, ident_line, ident_len);
+ die(_("invalid ident line: %s"), sb.buf);
+ }
+
+ assert(!state->author_name);
+ if (ident_split.name_begin) {
+ strbuf_add(&sb, ident_split.name_begin,
+ ident_split.name_end - ident_split.name_begin);
+ state->author_name = strbuf_detach(&sb, NULL);
+ } else
+ state->author_name = xstrdup("");
+
+ assert(!state->author_email);
+ if (ident_split.mail_begin) {
+ strbuf_add(&sb, ident_split.mail_begin,
+ ident_split.mail_end - ident_split.mail_begin);
+ state->author_email = strbuf_detach(&sb, NULL);
+ } else
+ state->author_email = xstrdup("");
+
+ author_date = show_ident_date(&ident_split, DATE_MODE(NORMAL));
+ strbuf_addstr(&sb, author_date);
+ assert(!state->author_date);
+ state->author_date = strbuf_detach(&sb, NULL);
+
+ assert(!state->msg);
+ msg = strstr(buffer, "\n\n");
+ if (!msg)
+ die(_("unable to parse commit %s"), sha1_to_hex(commit->object.sha1));
+ state->msg = xstrdup(msg + 2);
+ state->msg_len = strlen(state->msg);
+}
+
+/**
+ * Writes `commit` as a patch to the state directory's "patch" file.
+ */
+static void write_commit_patch(const struct am_state *state, struct commit *commit)
+{
+ struct rev_info rev_info;
+ FILE *fp;
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.abbrev = 0;
+ rev_info.disable_stdin = 1;
+ rev_info.show_root_diff = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.no_commit_id = 1;
+ DIFF_OPT_SET(&rev_info.diffopt, BINARY);
+ DIFF_OPT_SET(&rev_info.diffopt, FULL_INDEX);
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &commit->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ log_tree_commit(&rev_info, commit);
+}
+
+/**
+ * Writes the diff of the index against HEAD as a patch to the state
+ * directory's "patch" file.
+ */
+static void write_index_patch(const struct am_state *state)
+{
+ struct tree *tree;
+ unsigned char head[GIT_SHA1_RAWSZ];
+ struct rev_info rev_info;
+ FILE *fp;
+
+ if (!get_sha1_tree("HEAD", head))
+ tree = lookup_tree(head);
+ else
+ tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
+
+ fp = xfopen(am_path(state, "patch"), "w");
+ init_revisions(&rev_info, NULL);
+ rev_info.diff = 1;
+ rev_info.disable_stdin = 1;
+ rev_info.no_commit_id = 1;
+ rev_info.diffopt.output_format = DIFF_FORMAT_PATCH;
+ rev_info.diffopt.use_color = 0;
+ rev_info.diffopt.file = fp;
+ rev_info.diffopt.close_file = 1;
+ add_pending_object(&rev_info, &tree->object, "");
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+}
+
+/**
+ * Like parse_mail(), but parses the mail by looking up its commit ID
+ * directly. This is used in --rebasing mode to bypass git-mailinfo's munging
+ * of patches.
+ *
+ * state->orig_commit will be set to the original commit ID.
+ *
+ * Will always return 0 as the patch should never be skipped.
+ */
+static int parse_mail_rebase(struct am_state *state, const char *mail)
+{
+ struct commit *commit;
+ unsigned char commit_sha1[GIT_SHA1_RAWSZ];
+
+ if (get_mail_commit_sha1(commit_sha1, mail) < 0)
+ die(_("could not parse %s"), mail);
+
+ commit = lookup_commit_or_die(commit_sha1, mail);
+
+ get_commit_info(state, commit);
+
+ write_commit_patch(state, commit);
+
+ hashcpy(state->orig_commit, commit_sha1);
+ write_file(am_path(state, "original-commit"), 1, "%s",
+ sha1_to_hex(commit_sha1));
+
+ return 0;
+}
+
+/**
+ * Applies current patch with git-apply. Returns 0 on success, -1 otherwise. If
+ * `index_file` is not NULL, the patch will be applied to that index.
+ */
+static int run_apply(const struct am_state *state, const char *index_file)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+
+ if (index_file)
+ argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", index_file);
+
+ /*
+ * If we are allowed to fall back on 3-way merge, don't give false
+ * errors during the initial attempt.
+ */
+ if (state->threeway && !index_file) {
+ cp.no_stdout = 1;
+ cp.no_stderr = 1;
+ }
+
+ argv_array_push(&cp.args, "apply");
+
+ argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+
+ if (index_file)
+ argv_array_push(&cp.args, "--cached");
+ else
+ argv_array_push(&cp.args, "--index");
+
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp))
+ return -1;
+
+ /* Reload index as git-apply will have modified it. */
+ discard_cache();
+ read_cache_from(index_file ? index_file : get_index_file());
+
+ return 0;
+}
+
+/**
+ * Builds an index that contains just the blobs needed for a 3way merge.
+ */
+static int build_fake_ancestor(const struct am_state *state, const char *index_file)
+{
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ cp.git_cmd = 1;
+ argv_array_push(&cp.args, "apply");
+ argv_array_pushv(&cp.args, state->git_apply_opts.argv);
+ argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+
+ if (run_command(&cp))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * Attempt a threeway merge, using index_path as the temporary index.
+ */
+static int fall_back_threeway(const struct am_state *state, const char *index_path)
+{
+ unsigned char orig_tree[GIT_SHA1_RAWSZ], his_tree[GIT_SHA1_RAWSZ],
+ our_tree[GIT_SHA1_RAWSZ];
+ const unsigned char *bases[1] = {orig_tree};
+ struct merge_options o;
+ struct commit *result;
+ char *his_tree_name;
+
+ if (get_sha1("HEAD", our_tree) < 0)
+ hashcpy(our_tree, EMPTY_TREE_SHA1_BIN);
+
+ if (build_fake_ancestor(state, index_path))
+ return error("could not build fake ancestor");
+
+ discard_cache();
+ read_cache_from(index_path);
+
+ if (write_index_as_tree(orig_tree, &the_index, index_path, 0, NULL))
+ return error(_("Repository lacks necessary blobs to fall back on 3-way merge."));
+
+ say(state, stdout, _("Using index info to reconstruct a base tree..."));
+
+ if (!state->quiet) {
+ /*
+ * List paths that needed 3-way fallback, so that the user can
+ * review them with extra care to spot mismerges.
+ */
+ struct rev_info rev_info;
+ const char *diff_filter_str = "--diff-filter=AM";
+
+ init_revisions(&rev_info, NULL);
+ rev_info.diffopt.output_format = DIFF_FORMAT_NAME_STATUS;
+ diff_opt_parse(&rev_info.diffopt, &diff_filter_str, 1);
+ add_pending_sha1(&rev_info, "HEAD", our_tree, 0);
+ diff_setup_done(&rev_info.diffopt);
+ run_diff_index(&rev_info, 1);
+ }
+
+ if (run_apply(state, index_path))
+ return error(_("Did you hand edit your patch?\n"
+ "It does not apply to blobs recorded in its index."));
+
+ if (write_index_as_tree(his_tree, &the_index, index_path, 0, NULL))
+ return error("could not write tree");
+
+ say(state, stdout, _("Falling back to patching base and 3-way merge..."));
+
+ discard_cache();
+ read_cache();
+
+ /*
+ * This is not so wrong. Depending on which base we picked, orig_tree
+ * may be wildly different from ours, but his_tree has the same set of
+ * wildly different changes in parts the patch did not touch, so
+ * recursive ends up canceling them, saying that we reverted all those
+ * changes.
+ */
+
+ init_merge_options(&o);
+
+ o.branch1 = "HEAD";
+ his_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg);
+ o.branch2 = his_tree_name;
+
+ if (state->quiet)
+ o.verbosity = 0;
+
+ if (merge_recursive_generic(&o, our_tree, his_tree, 1, bases, &result)) {
+ rerere(state->allow_rerere_autoupdate);
+ free(his_tree_name);
+ return error(_("Failed to merge in the changes."));
+ }
+
+ free(his_tree_name);
+ return 0;
+}
+
+/**
+ * Commits the current index with state->msg as the commit message and
+ * state->author_name, state->author_email and state->author_date as the author
+ * information.
+ */
+static void do_commit(const struct am_state *state)
+{
+ unsigned char tree[GIT_SHA1_RAWSZ], parent[GIT_SHA1_RAWSZ],
+ commit[GIT_SHA1_RAWSZ];
+ unsigned char *ptr;
+ struct commit_list *parents = NULL;
+ const char *reflog_msg, *author;
+ struct strbuf sb = STRBUF_INIT;
+
+ if (run_hook_le(NULL, "pre-applypatch", NULL))
+ exit(1);
+
+ if (write_cache_as_tree(tree, 0, NULL))
+ die(_("git write-tree failed to write a tree"));
+
+ if (!get_sha1_commit("HEAD", parent)) {
+ ptr = parent;
+ commit_list_insert(lookup_commit(parent), &parents);
+ } else {
+ ptr = NULL;
+ say(state, stderr, _("applying to an empty history"));
+ }
+
+ author = fmt_ident(state->author_name, state->author_email,
+ state->ignore_date ? NULL : state->author_date,
+ IDENT_STRICT);
+
+ if (state->committer_date_is_author_date)
+ setenv("GIT_COMMITTER_DATE",
+ state->ignore_date ? "" : state->author_date, 1);
+
+ if (commit_tree(state->msg, state->msg_len, tree, parents, commit,
+ author, state->sign_commit))
+ die(_("failed to write commit object"));
+
+ reflog_msg = getenv("GIT_REFLOG_ACTION");
+ if (!reflog_msg)
+ reflog_msg = "am";
+
+ strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
+ state->msg);
+
+ update_ref(sb.buf, "HEAD", commit, ptr, 0, UPDATE_REFS_DIE_ON_ERR);
+
+ if (state->rebasing) {
+ FILE *fp = xfopen(am_path(state, "rewritten"), "a");
+
+ assert(!is_null_sha1(state->orig_commit));
+ fprintf(fp, "%s ", sha1_to_hex(state->orig_commit));
+ fprintf(fp, "%s\n", sha1_to_hex(commit));
+ fclose(fp);
+ }
+
+ run_hook_le(NULL, "post-applypatch", NULL);
+
+ strbuf_release(&sb);
+}
+
+/**
+ * Validates the am_state for resuming -- the "msg" and authorship fields must
+ * be filled up.
+ */
+static void validate_resume_state(const struct am_state *state)
+{
+ if (!state->msg)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "final-commit"));
+
+ if (!state->author_name || !state->author_email || !state->author_date)
+ die(_("cannot resume: %s does not exist."),
+ am_path(state, "author-script"));
+}
+
+/**
+ * Interactively prompt the user on whether the current patch should be
+ * applied.
+ *
+ * Returns 0 if the user chooses to apply the patch, 1 if the user chooses to
+ * skip it.
+ */
+static int do_interactive(struct am_state *state)
+{
+ assert(state->msg);
+
+ if (!isatty(0))
+ die(_("cannot be interactive without stdin connected to a terminal."));
+
+ for (;;) {
+ const char *reply;
+
+ puts(_("Commit Body is:"));
+ puts("--------------------------");
+ printf("%s", state->msg);
+ puts("--------------------------");
+
+ /*
+ * TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a]
+ * in your translation. The program will only accept English
+ * input at this point.
+ */
+ reply = git_prompt(_("Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all: "), PROMPT_ECHO);
+
+ if (!reply) {
+ continue;
+ } else if (*reply == 'y' || *reply == 'Y') {
+ return 0;
+ } else if (*reply == 'a' || *reply == 'A') {
+ state->interactive = 0;
+ return 0;
+ } else if (*reply == 'n' || *reply == 'N') {
+ return 1;
+ } else if (*reply == 'e' || *reply == 'E') {
+ struct strbuf msg = STRBUF_INIT;
+
+ if (!launch_editor(am_path(state, "final-commit"), &msg, NULL)) {
+ free(state->msg);
+ state->msg = strbuf_detach(&msg, &state->msg_len);
+ }
+ strbuf_release(&msg);
+ } else if (*reply == 'v' || *reply == 'V') {
+ const char *pager = git_pager(1);
+ struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (!pager)
+ pager = "cat";
+ argv_array_push(&cp.args, pager);
+ argv_array_push(&cp.args, am_path(state, "patch"));
+ run_command(&cp);
+ }
+ }
+}
+
+/**
+ * Applies all queued mail.
+ *
+ * If `resume` is true, we are "resuming". The "msg" and authorship fields, as
+ * well as the state directory's "patch" file is used as-is for applying the
+ * patch and committing it.
+ */
+static void am_run(struct am_state *state, int resume)
+{
+ const char *argv_gc_auto[] = {"gc", "--auto", NULL};
+ struct strbuf sb = STRBUF_INIT;
+
+ unlink(am_path(state, "dirtyindex"));
+
+ refresh_and_write_cache();
+
+ if (index_has_changes(&sb)) {
+ write_file(am_path(state, "dirtyindex"), 1, "t");
+ die(_("Dirty index: cannot apply patches (dirty: %s)"), sb.buf);
+ }
+
+ strbuf_release(&sb);
+
+ while (state->cur <= state->last) {
+ const char *mail = am_path(state, msgnum(state));
+ int apply_status;
+
+ if (!file_exists(mail))
+ goto next;
+
+ if (resume) {
+ validate_resume_state(state);
+ } else {
+ int skip;
+
+ if (state->rebasing)
+ skip = parse_mail_rebase(state, mail);
+ else
+ skip = parse_mail(state, mail);
+
+ if (skip)
+ goto next; /* mail should be skipped */
+
+ write_author_script(state);
+ write_commit_msg(state);
+ }
+
+ if (state->interactive && do_interactive(state))
+ goto next;
+
+ if (run_applypatch_msg_hook(state))
+ exit(1);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ apply_status = run_apply(state, NULL);
+
+ if (apply_status && state->threeway) {
+ struct strbuf sb = STRBUF_INIT;
+
+ strbuf_addstr(&sb, am_path(state, "patch-merge-index"));
+ apply_status = fall_back_threeway(state, sb.buf);
+ strbuf_release(&sb);
+
+ /*
+ * Applying the patch to an earlier tree and merging
+ * the result may have produced the same tree as ours.
+ */
+ if (!apply_status && !index_has_changes(NULL)) {
+ say(state, stdout, _("No changes -- Patch already applied."));
+ goto next;
+ }
+ }
+
+ if (apply_status) {
+ int advice_amworkdir = 1;
+
+ printf_ln(_("Patch failed at %s %.*s"), msgnum(state),
+ linelen(state->msg), state->msg);
+
+ git_config_get_bool("advice.amworkdir", &advice_amworkdir);
+
+ if (advice_amworkdir)
+ printf_ln(_("The copy of the patch that failed is found in: %s"),
+ am_path(state, "patch"));
+
+ die_user_resolve(state);
+ }
+
+ do_commit(state);
+
+next:
+ am_next(state);
+
+ if (resume)
+ am_load(state);
+ resume = 0;
+ }
+
+ if (!is_empty_file(am_path(state, "rewritten"))) {
+ assert(state->rebasing);
+ copy_notes_for_rebase(state);
+ run_post_rewrite_hook(state);
+ }
+
+ /*
+ * In rebasing mode, it's up to the caller to take care of
+ * housekeeping.
+ */
+ if (!state->rebasing) {
+ am_destroy(state);
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+}
+
+/**
+ * Resume the current am session after patch application failure. The user did
+ * all the hard work, and we do not have to do any patch application. Just
+ * trust and commit what the user has in the index and working tree.
+ */
+static void am_resolve(struct am_state *state)
+{
+ validate_resume_state(state);
+
+ say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg);
+
+ if (!index_has_changes(NULL)) {
+ printf_ln(_("No changes - did you forget to use 'git add'?\n"
+ "If there is nothing left to stage, chances are that something else\n"
+ "already introduced the same changes; you might want to skip this patch."));
+ die_user_resolve(state);
+ }
+
+ if (unmerged_cache()) {
+ printf_ln(_("You still have unmerged paths in your index.\n"
+ "Did you forget to use 'git add'?"));
+ die_user_resolve(state);
+ }
+
+ if (state->interactive) {
+ write_index_patch(state);
+ if (do_interactive(state))
+ goto next;
+ }
+
+ rerere(0);
+
+ do_commit(state);
+
+next:
+ am_next(state);
+ am_load(state);
+ am_run(state, 0);
+}
+
+/**
+ * Performs a checkout fast-forward from `head` to `remote`. If `reset` is
+ * true, any unmerged entries will be discarded. Returns 0 on success, -1 on
+ * failure.
+ */
+static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
+{
+ struct lock_file *lock_file;
+ struct unpack_trees_options opts;
+ struct tree_desc t[2];
+
+ if (parse_tree(head) || parse_tree(remote))
+ return -1;
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, 1);
+
+ refresh_cache(REFRESH_QUIET);
+
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = 1;
+ opts.src_index = &the_index;
+ opts.dst_index = &the_index;
+ opts.update = 1;
+ opts.merge = 1;
+ opts.reset = reset;
+ opts.fn = twoway_merge;
+ init_tree_desc(&t[0], head->buffer, head->size);
+ init_tree_desc(&t[1], remote->buffer, remote->size);
+
+ if (unpack_trees(2, t, &opts)) {
+ rollback_lock_file(lock_file);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ return 0;
+}
+
+/**
+ * Clean the index without touching entries that are not modified between
+ * `head` and `remote`.
+ */
+static int clean_index(const unsigned char *head, const unsigned char *remote)
+{
+ struct lock_file *lock_file;
+ struct tree *head_tree, *remote_tree, *index_tree;
+ unsigned char index[GIT_SHA1_RAWSZ];
+ struct pathspec pathspec;
+
+ head_tree = parse_tree_indirect(head);
+ if (!head_tree)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(head));
+
+ remote_tree = parse_tree_indirect(remote);
+ if (!remote_tree)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(remote));
+
+ read_cache_unmerged();
+
+ if (fast_forward_to(head_tree, head_tree, 1))
+ return -1;
+
+ if (write_cache_as_tree(index, 0, NULL))
+ return -1;
+
+ index_tree = parse_tree_indirect(index);
+ if (!index_tree)
+ return error(_("Could not parse object '%s'."), sha1_to_hex(index));
+
+ if (fast_forward_to(index_tree, remote_tree, 0))
+ return -1;
+
+ memset(&pathspec, 0, sizeof(pathspec));
+
+ lock_file = xcalloc(1, sizeof(struct lock_file));
+ hold_locked_index(lock_file, 1);
+
+ if (read_tree(remote_tree, 0, &pathspec)) {
+ rollback_lock_file(lock_file);
+ return -1;
+ }
+
+ if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
+ die(_("unable to write new index file"));
+
+ remove_branch_state();
+
+ return 0;
+}
+
+/**
+ * Resets rerere's merge resolution metadata.
+ */
+static void am_rerere_clear(void)
+{
+ struct string_list merge_rr = STRING_LIST_INIT_DUP;
+ int fd = setup_rerere(&merge_rr, 0);
+
+ if (fd < 0)
+ return;
+
+ rerere_clear(&merge_rr);
+ string_list_clear(&merge_rr, 1);
+}
+
+/**
+ * Resume the current am session by skipping the current patch.
+ */
+static void am_skip(struct am_state *state)
+{
+ unsigned char head[GIT_SHA1_RAWSZ];
+
+ am_rerere_clear();
+
+ if (get_sha1("HEAD", head))
+ hashcpy(head, EMPTY_TREE_SHA1_BIN);
+
+ if (clean_index(head, head))
+ die(_("failed to clean index"));
+
+ am_next(state);
+ am_load(state);
+ am_run(state, 0);
+}
+
+/**
+ * Returns true if it is safe to reset HEAD to the ORIG_HEAD, false otherwise.
+ *
+ * It is not safe to reset HEAD when:
+ * 1. git-am previously failed because the index was dirty.
+ * 2. HEAD has moved since git-am previously failed.
+ */
+static int safe_to_abort(const struct am_state *state)
+{
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char abort_safety[GIT_SHA1_RAWSZ], head[GIT_SHA1_RAWSZ];
+
+ if (file_exists(am_path(state, "dirtyindex")))
+ return 0;
+
+ if (read_state_file(&sb, state, "abort-safety", 1) > 0) {
+ if (get_sha1_hex(sb.buf, abort_safety))
+ die(_("could not parse %s"), am_path(state, "abort_safety"));
+ } else
+ hashclr(abort_safety);
+
+ if (get_sha1("HEAD", head))
+ hashclr(head);
+
+ if (!hashcmp(head, abort_safety))
+ return 1;
+
+ error(_("You seem to have moved HEAD since the last 'am' failure.\n"
+ "Not rewinding to ORIG_HEAD"));
+
+ return 0;
+}
+
+/**
+ * Aborts the current am session if it is safe to do so.
+ */
+static void am_abort(struct am_state *state)
+{
+ unsigned char curr_head[GIT_SHA1_RAWSZ], orig_head[GIT_SHA1_RAWSZ];
+ int has_curr_head, has_orig_head;
+ char *curr_branch;
+
+ if (!safe_to_abort(state)) {
+ am_destroy(state);
+ return;
+ }
+
+ am_rerere_clear();
+
+ curr_branch = resolve_refdup("HEAD", 0, curr_head, NULL);
+ has_curr_head = !is_null_sha1(curr_head);
+ if (!has_curr_head)
+ hashcpy(curr_head, EMPTY_TREE_SHA1_BIN);
+
+ has_orig_head = !get_sha1("ORIG_HEAD", orig_head);
+ if (!has_orig_head)
+ hashcpy(orig_head, EMPTY_TREE_SHA1_BIN);
+
+ clean_index(curr_head, orig_head);
+
+ if (has_orig_head)
+ update_ref("am --abort", "HEAD", orig_head,
+ has_curr_head ? curr_head : NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
+ else if (curr_branch)
+ delete_ref(curr_branch, NULL, REF_NODEREF);
+
+ free(curr_branch);
+ am_destroy(state);
+}
+
+/**
+ * parse_options() callback that validates and sets opt->value to the
+ * PATCH_FORMAT_* enum value corresponding to `arg`.
+ */
+static int parse_opt_patchformat(const struct option *opt, const char *arg, int unset)
+{
+ int *opt_value = opt->value;
+
+ if (!strcmp(arg, "mbox"))
+ *opt_value = PATCH_FORMAT_MBOX;
+ else if (!strcmp(arg, "stgit"))
+ *opt_value = PATCH_FORMAT_STGIT;
+ else if (!strcmp(arg, "stgit-series"))
+ *opt_value = PATCH_FORMAT_STGIT_SERIES;
+ else if (!strcmp(arg, "hg"))
+ *opt_value = PATCH_FORMAT_HG;
+ else
+ return error(_("Invalid value for --patch-format: %s"), arg);
+ return 0;
+}
+
+enum resume_mode {
+ RESUME_FALSE = 0,
+ RESUME_APPLY,
+ RESUME_RESOLVED,
+ RESUME_SKIP,
+ RESUME_ABORT
+};
+
+int cmd_am(int argc, const char **argv, const char *prefix)
+{
+ struct am_state state;
+ int binary = -1;
+ int keep_cr = -1;
+ int patch_format = PATCH_FORMAT_UNKNOWN;
+ enum resume_mode resume = RESUME_FALSE;
+ int in_progress;
+
+ const char * const usage[] = {
+ N_("git am [options] [(<mbox>|<Maildir>)...]"),
+ N_("git am [options] (--continue | --skip | --abort)"),
+ NULL
+ };
+
+ struct option options[] = {
+ OPT_BOOL('i', "interactive", &state.interactive,
+ N_("run interactively")),
+ OPT_HIDDEN_BOOL('b', "binary", &binary,
+ N_("(historical option -- no-op")),
+ OPT_BOOL('3', "3way", &state.threeway,
+ N_("allow fall back on 3way merging if needed")),
+ OPT__QUIET(&state.quiet, N_("be quiet")),
+ OPT_SET_INT('s', "signoff", &state.signoff,
+ N_("add a Signed-off-by line to the commit message"),
+ SIGNOFF_EXPLICIT),
+ OPT_BOOL('u', "utf8", &state.utf8,
+ N_("recode into utf8 (default)")),
+ OPT_SET_INT('k', "keep", &state.keep,
+ N_("pass -k flag to git-mailinfo"), KEEP_TRUE),
+ OPT_SET_INT(0, "keep-non-patch", &state.keep,
+ N_("pass -b flag to git-mailinfo"), KEEP_NON_PATCH),
+ OPT_BOOL('m', "message-id", &state.message_id,
+ N_("pass -m flag to git-mailinfo")),
+ { OPTION_SET_INT, 0, "keep-cr", &keep_cr, NULL,
+ N_("pass --keep-cr flag to git-mailsplit for mbox format"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 1},
+ { OPTION_SET_INT, 0, "no-keep-cr", &keep_cr, NULL,
+ N_("do not pass --keep-cr flag to git-mailsplit independent of am.keepcr"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, 0},
+ OPT_BOOL('c', "scissors", &state.scissors,
+ N_("strip everything before a scissors line")),
+ OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "ignore-space-change", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV(0, "directory", &state.git_apply_opts, N_("root"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "exclude", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV(0, "include", &state.git_apply_opts, N_("path"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('C', NULL, &state.git_apply_opts, N_("n"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_PASSTHRU_ARGV('p', NULL, &state.git_apply_opts, N_("num"),
+ N_("pass it through git-apply"),
+ 0),
+ OPT_CALLBACK(0, "patch-format", &patch_format, N_("format"),
+ N_("format the patch(es) are in"),
+ parse_opt_patchformat),
+ OPT_PASSTHRU_ARGV(0, "reject", &state.git_apply_opts, NULL,
+ N_("pass it through git-apply"),
+ PARSE_OPT_NOARG),
+ OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL,
+ N_("override error message when patch failure occurs")),
+ OPT_CMDMODE(0, "continue", &resume,
+ N_("continue applying patches after resolving a conflict"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE('r', "resolved", &resume,
+ N_("synonyms for --continue"),
+ RESUME_RESOLVED),
+ OPT_CMDMODE(0, "skip", &resume,
+ N_("skip the current patch"),
+ RESUME_SKIP),
+ OPT_CMDMODE(0, "abort", &resume,
+ N_("restore the original branch and abort the patching operation."),
+ RESUME_ABORT),
+ OPT_BOOL(0, "committer-date-is-author-date",
+ &state.committer_date_is_author_date,
+ N_("lie about committer date")),
+ OPT_BOOL(0, "ignore-date", &state.ignore_date,
+ N_("use current timestamp for author date")),
+ OPT_RERERE_AUTOUPDATE(&state.allow_rerere_autoupdate),
+ { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"),
+ N_("GPG-sign commits"),
+ PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
+ OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing,
+ N_("(internal use for git-rebase)")),
+ OPT_END()
+ };
+
+ git_config(git_default_config, NULL);
+
+ am_state_init(&state, git_path("rebase-apply"));
+
+ in_progress = am_in_progress(&state);
+ if (in_progress)
+ am_load(&state);
+
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (binary >= 0)
+ fprintf_ln(stderr, _("The -b/--binary option has been a no-op for long time, and\n"
+ "it will be removed. Please do not use it anymore."));
+
+ /* Ensure a valid committer ident can be constructed */
+ git_committer_info(IDENT_STRICT);
+
+ if (read_index_preload(&the_index, NULL) < 0)
+ die(_("failed to read the index"));
+
+ if (in_progress) {
+ /*
+ * Catch user error to feed us patches when there is a session
+ * in progress:
+ *
+ * 1. mbox path(s) are provided on the command-line.
+ * 2. stdin is not a tty: the user is trying to feed us a patch
+ * from standard input. This is somewhat unreliable -- stdin
+ * could be /dev/null for example and the caller did not
+ * intend to feed us a patch but wanted to continue
+ * unattended.
+ */
+ if (argc || (resume == RESUME_FALSE && !isatty(0)))
+ die(_("previous rebase directory %s still exists but mbox given."),
+ state.dir);
+
+ if (resume == RESUME_FALSE)
+ resume = RESUME_APPLY;
+
+ if (state.signoff == SIGNOFF_EXPLICIT)
+ am_append_signoff(&state);
+ } else {
+ struct argv_array paths = ARGV_ARRAY_INIT;
+ int i;
+
+ /*
+ * Handle stray state directory in the independent-run case. In
+ * the --rebasing case, it is up to the caller to take care of
+ * stray directories.
+ */
+ if (file_exists(state.dir) && !state.rebasing) {
+ if (resume == RESUME_ABORT) {
+ am_destroy(&state);
+ am_state_release(&state);
+ return 0;
+ }
+
+ die(_("Stray %s directory found.\n"
+ "Use \"git am --abort\" to remove it."),
+ state.dir);
+ }
+
+ if (resume)
+ die(_("Resolve operation not in progress, we are not resuming."));
+
+ for (i = 0; i < argc; i++) {
+ if (is_absolute_path(argv[i]) || !prefix)
+ argv_array_push(&paths, argv[i]);
+ else
+ argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i]));
+ }
+
+ am_setup(&state, patch_format, paths.argv, keep_cr);
+
+ argv_array_clear(&paths);
+ }
+
+ switch (resume) {
+ case RESUME_FALSE:
+ am_run(&state, 0);
+ break;
+ case RESUME_APPLY:
+ am_run(&state, 1);
+ break;
+ case RESUME_RESOLVED:
+ am_resolve(&state);
+ break;
+ case RESUME_SKIP:
+ am_skip(&state);
+ break;
+ case RESUME_ABORT:
+ am_abort(&state);
+ break;
+ default:
+ die("BUG: invalid resume value");
+ }
+
+ am_state_release(&state);
+
+ return 0;
+}
static void determine_whence(struct wt_status *s)
{
- if (file_exists(git_path("MERGE_HEAD")))
+ if (file_exists(git_path_merge_head()))
whence = FROM_MERGE;
- else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
+ else if (file_exists(git_path_cherry_pick_head())) {
whence = FROM_CHERRY_PICK;
if (file_exists(git_path(SEQ_DIR)))
sequencer_in_use = 1;
struct string_list partial;
struct pathspec pathspec;
int refresh_flags = REFRESH_QUIET;
+ const char *ret;
if (is_status)
refresh_flags |= REFRESH_UNMERGED;
die(_("unable to create temporary index"));
old_index_env = getenv(INDEX_ENVIRONMENT);
- setenv(INDEX_ENVIRONMENT, index_lock.filename.buf, 1);
+ setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1);
if (interactive_add(argc, argv, prefix, patch_interactive) != 0)
die(_("interactive add failed"));
unsetenv(INDEX_ENVIRONMENT);
discard_cache();
- read_cache_from(index_lock.filename.buf);
+ read_cache_from(get_lock_file_path(&index_lock));
if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
if (reopen_lock_file(&index_lock) < 0)
die(_("unable to write index file"));
warning(_("Failed to update main cache tree"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename.buf;
+ return get_lock_file_path(&index_lock);
}
/*
if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
die(_("unable to write new_index file"));
commit_style = COMMIT_NORMAL;
- return index_lock.filename.buf;
+ return get_lock_file_path(&index_lock);
}
/*
die(_("unable to write temporary index file"));
discard_cache();
- read_cache_from(false_lock.filename.buf);
-
- return false_lock.filename.buf;
+ ret = get_lock_file_path(&false_lock);
+ read_cache_from(ret);
+ return ret;
}
static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
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)
+ } 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)
+ } 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) {
_("%s"
"Date: %s"),
ident_shown++ ? "" : "\n",
- show_ident_date(&ai, DATE_NORMAL));
+ show_ident_date(&ai, DATE_MODE(NORMAL)));
if (!committer_ident_sufficiently_given())
status_printf_ln(s, GIT_COLOR_NORMAL,
commit = get_revision(&revs);
if (commit) {
struct pretty_print_context ctx = {0};
- ctx.date_mode = DATE_NORMAL;
+ ctx.date_mode.type = DATE_NORMAL;
strbuf_release(&buf);
format_commit_message(commit, "%aN <%aE>", &buf, &ctx);
clear_mailmap(&mailmap);
if (!reflog_msg)
reflog_msg = "commit (merge)";
pptr = &commit_list_insert(current_head, pptr)->next;
- fp = fopen(git_path("MERGE_HEAD"), "r");
+ fp = fopen(git_path_merge_head(), "r");
if (fp == NULL)
die_errno(_("could not open '%s' for reading"),
- git_path("MERGE_HEAD"));
+ git_path_merge_head());
while (strbuf_getline(&m, fp, '\n') != EOF) {
struct commit *parent;
}
fclose(fp);
strbuf_release(&m);
- if (!stat(git_path("MERGE_MODE"), &statbuf)) {
- if (strbuf_read_file(&sb, git_path("MERGE_MODE"), 0) < 0)
+ if (!stat(git_path_merge_mode(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0)
die_errno(_("could not read MERGE_MODE"));
if (!strcmp(sb.buf, "no-ff"))
allow_fast_forward = 0;
}
ref_transaction_free(transaction);
- unlink(git_path("CHERRY_PICK_HEAD"));
- unlink(git_path("REVERT_HEAD"));
- unlink(git_path("MERGE_HEAD"));
- unlink(git_path("MERGE_MSG"));
- unlink(git_path("MERGE_MODE"));
- unlink(git_path("SQUASH_MSG"));
+ unlink(git_path_cherry_pick_head());
+ unlink(git_path_revert_head());
+ unlink(git_path_merge_head());
+ unlink(git_path_merge_msg());
+ unlink(git_path_merge_mode());
+ unlink(git_path_squash_msg());
if (commit_index_files())
die (_("Repository has been updated, but unable to write\n"
*/
#include "builtin.h"
+ #include "tempfile.h"
#include "lockfile.h"
#include "parse-options.h"
#include "run-command.h"
static struct argv_array prune_worktrees = ARGV_ARRAY_INIT;
static struct argv_array rerere = ARGV_ARRAY_INIT;
- static char *pidfile;
-
- static void remove_pidfile(void)
- {
- if (pidfile)
- unlink(pidfile);
- }
-
- static void remove_pidfile_on_signal(int signo)
- {
- remove_pidfile();
- sigchain_pop(signo);
- raise(signo);
- }
+ static struct tempfile pidfile;
static void git_config_date_string(const char *key, const char **output)
{
git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit);
git_config_get_bool("gc.autodetach", &detach_auto);
git_config_date_string("gc.pruneexpire", &prune_expire);
- git_config_date_string("gc.pruneworktreesexpire", &prune_worktrees_expire);
+ git_config_date_string("gc.worktreepruneexpire", &prune_worktrees_expire);
git_config(git_default_config, NULL);
}
uintmax_t pid;
FILE *fp;
int fd;
+ char *pidfile_path;
- if (pidfile)
+ if (is_tempfile_active(&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"),
+ 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[128];
int should_exit;
- fp = fopen(git_path("gc.pid"), "r");
+ fp = fopen(pidfile_path, "r");
memset(locking_host, 0, sizeof(locking_host));
should_exit =
fp != NULL &&
if (fd >= 0)
rollback_lock_file(&lock);
*ret_pid = pid;
+ free(pidfile_path);
return locking_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);
-
+ register_tempfile(&pidfile, pidfile_path);
+ free(pidfile_path);
return NULL;
}
argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL);
argv_array_pushl(&repack, "repack", "-d", "-l", NULL);
argv_array_pushl(&prune, "prune", "--expire", NULL);
- argv_array_pushl(&prune_worktrees, "prune", "--worktrees", "--expire", NULL);
+ argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL);
argv_array_pushl(&rerere, "rerere", "gc", NULL);
gc_config();
--- /dev/null
+/*
+ * Builtin "git pull"
+ *
+ * Based on git-pull.sh by Junio C Hamano
+ *
+ * Fetch one or more remote refs and merge it/them into the current HEAD.
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "exec_cmd.h"
+#include "run-command.h"
+#include "sha1-array.h"
+#include "remote.h"
+#include "dir.h"
+#include "refs.h"
+#include "revision.h"
++#include "tempfile.h"
+#include "lockfile.h"
+
+enum rebase_type {
+ REBASE_INVALID = -1,
+ REBASE_FALSE = 0,
+ REBASE_TRUE,
+ REBASE_PRESERVE
+};
+
+/**
+ * Parses the value of --rebase. If value is a false value, returns
+ * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is
+ * "preserve", returns REBASE_PRESERVE. If value is a invalid value, dies with
+ * a fatal error if fatal is true, otherwise returns REBASE_INVALID.
+ */
+static enum rebase_type parse_config_rebase(const char *key, const char *value,
+ int fatal)
+{
+ int v = git_config_maybe_bool("pull.rebase", value);
+
+ if (!v)
+ return REBASE_FALSE;
+ else if (v > 0)
+ return REBASE_TRUE;
+ else if (!strcmp(value, "preserve"))
+ return REBASE_PRESERVE;
+
+ if (fatal)
+ die(_("Invalid value for %s: %s"), key, value);
+ else
+ error(_("Invalid value for %s: %s"), key, value);
+
+ return REBASE_INVALID;
+}
+
+/**
+ * Callback for --rebase, which parses arg with parse_config_rebase().
+ */
+static int parse_opt_rebase(const struct option *opt, const char *arg, int unset)
+{
+ enum rebase_type *value = opt->value;
+
+ if (arg)
+ *value = parse_config_rebase("--rebase", arg, 0);
+ else
+ *value = unset ? REBASE_FALSE : REBASE_TRUE;
+ return *value == REBASE_INVALID ? -1 : 0;
+}
+
+static const char * const pull_usage[] = {
+ N_("git pull [options] [<repository> [<refspec>...]]"),
+ NULL
+};
+
+/* Shared options */
+static int opt_verbosity;
+static char *opt_progress;
+
+/* Options passed to git-merge or git-rebase */
+static enum rebase_type opt_rebase = -1;
+static char *opt_diffstat;
+static char *opt_log;
+static char *opt_squash;
+static char *opt_commit;
+static char *opt_edit;
+static char *opt_ff;
+static char *opt_verify_signatures;
+static struct argv_array opt_strategies = ARGV_ARRAY_INIT;
+static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT;
+static char *opt_gpg_sign;
+
+/* Options passed to git-fetch */
+static char *opt_all;
+static char *opt_append;
+static char *opt_upload_pack;
+static int opt_force;
+static char *opt_tags;
+static char *opt_prune;
+static char *opt_recurse_submodules;
+static int opt_dry_run;
+static char *opt_keep;
+static char *opt_depth;
+static char *opt_unshallow;
+static char *opt_update_shallow;
+static char *opt_refmap;
+
+static struct option pull_options[] = {
+ /* Shared options */
+ OPT__VERBOSITY(&opt_verbosity),
+ OPT_PASSTHRU(0, "progress", &opt_progress, NULL,
+ N_("force progress reporting"),
+ PARSE_OPT_NOARG),
+
+ /* Options passed to git-merge or git-rebase */
+ OPT_GROUP(N_("Options related to merging")),
+ { OPTION_CALLBACK, 'r', "rebase", &opt_rebase,
+ N_("false|true|preserve"),
+ N_("incorporate changes by rebasing rather than merging"),
+ PARSE_OPT_OPTARG, parse_opt_rebase },
+ OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,
+ N_("do not show a diffstat at the end of the merge"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+ OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL,
+ N_("show a diffstat at the end of the merge"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL,
+ N_("(synonym to --stat)"),
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN),
+ OPT_PASSTHRU(0, "log", &opt_log, N_("n"),
+ N_("add (at most <n>) entries from shortlog to merge commit message"),
+ PARSE_OPT_OPTARG),
+ OPT_PASSTHRU(0, "squash", &opt_squash, NULL,
+ N_("create a single commit instead of doing a merge"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "commit", &opt_commit, NULL,
+ N_("perform a commit if the merge succeeds (default)"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "edit", &opt_edit, NULL,
+ N_("edit message before committing"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "ff", &opt_ff, NULL,
+ N_("allow fast-forward"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL,
+ N_("abort if fast-forward is not possible"),
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG),
+ OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL,
+ N_("verify that the named commit has a valid GPG signature"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"),
+ N_("merge strategy to use"),
+ 0),
+ OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts,
+ N_("option=value"),
+ N_("option for selected merge strategy"),
+ 0),
+ OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"),
+ N_("GPG sign commit"),
+ PARSE_OPT_OPTARG),
+
+ /* Options passed to git-fetch */
+ OPT_GROUP(N_("Options related to fetching")),
+ OPT_PASSTHRU(0, "all", &opt_all, NULL,
+ N_("fetch from all remotes"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU('a', "append", &opt_append, NULL,
+ N_("append to .git/FETCH_HEAD instead of overwriting"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"),
+ N_("path to upload pack on remote end"),
+ 0),
+ OPT__FORCE(&opt_force, N_("force overwrite of local branch")),
+ OPT_PASSTHRU('t', "tags", &opt_tags, NULL,
+ N_("fetch all tags and associated objects"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU('p', "prune", &opt_prune, NULL,
+ N_("prune remote-tracking branches no longer on remote"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "recurse-submodules", &opt_recurse_submodules,
+ N_("on-demand"),
+ N_("control recursive fetching of submodules"),
+ PARSE_OPT_OPTARG),
+ OPT_BOOL(0, "dry-run", &opt_dry_run,
+ N_("dry run")),
+ OPT_PASSTHRU('k', "keep", &opt_keep, NULL,
+ N_("keep downloaded pack"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"),
+ N_("deepen history of shallow clone"),
+ 0),
+ OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL,
+ N_("convert to a complete repository"),
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL,
+ N_("accept refs that update .git/shallow"),
+ PARSE_OPT_NOARG),
+ OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"),
+ N_("specify fetch refmap"),
+ PARSE_OPT_NONEG),
+
+ OPT_END()
+};
+
+/**
+ * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level.
+ */
+static void argv_push_verbosity(struct argv_array *arr)
+{
+ int verbosity;
+
+ for (verbosity = opt_verbosity; verbosity > 0; verbosity--)
+ argv_array_push(arr, "-v");
+
+ for (verbosity = opt_verbosity; verbosity < 0; verbosity++)
+ argv_array_push(arr, "-q");
+}
+
+/**
+ * Pushes "-f" switches into arr to match the opt_force level.
+ */
+static void argv_push_force(struct argv_array *arr)
+{
+ int force = opt_force;
+ while (force-- > 0)
+ argv_array_push(arr, "-f");
+}
+
+/**
+ * Sets the GIT_REFLOG_ACTION environment variable to the concatenation of argv
+ */
+static void set_reflog_message(int argc, const char **argv)
+{
+ int i;
+ struct strbuf msg = STRBUF_INIT;
+
+ for (i = 0; i < argc; i++) {
+ if (i)
+ strbuf_addch(&msg, ' ');
+ strbuf_addstr(&msg, argv[i]);
+ }
+
+ setenv("GIT_REFLOG_ACTION", msg.buf, 0);
+
+ strbuf_release(&msg);
+}
+
+/**
+ * If pull.ff is unset, returns NULL. If pull.ff is "true", returns "--ff". If
+ * pull.ff is "false", returns "--no-ff". If pull.ff is "only", returns
+ * "--ff-only". Otherwise, if pull.ff is set to an invalid value, die with an
+ * error.
+ */
+static const char *config_get_ff(void)
+{
+ const char *value;
+
+ if (git_config_get_value("pull.ff", &value))
+ return NULL;
+
+ switch (git_config_maybe_bool("pull.ff", value)) {
+ case 0:
+ return "--no-ff";
+ case 1:
+ return "--ff";
+ }
+
+ if (!strcmp(value, "only"))
+ return "--ff-only";
+
+ die(_("Invalid value for pull.ff: %s"), value);
+}
+
+/**
+ * Returns the default configured value for --rebase. It first looks for the
+ * value of "branch.$curr_branch.rebase", where $curr_branch is the current
+ * branch, and if HEAD is detached or the configuration key does not exist,
+ * looks for the value of "pull.rebase". If both configuration keys do not
+ * exist, returns REBASE_FALSE.
+ */
+static enum rebase_type config_get_rebase(void)
+{
+ struct branch *curr_branch = branch_get("HEAD");
+ const char *value;
+
+ if (curr_branch) {
+ char *key = xstrfmt("branch.%s.rebase", curr_branch->name);
+
+ if (!git_config_get_value(key, &value)) {
+ enum rebase_type ret = parse_config_rebase(key, value, 1);
+ free(key);
+ return ret;
+ }
+
+ free(key);
+ }
+
+ if (!git_config_get_value("pull.rebase", &value))
+ return parse_config_rebase("pull.rebase", value, 1);
+
+ return REBASE_FALSE;
+}
+
+/**
+ * Returns 1 if there are unstaged changes, 0 otherwise.
+ */
+static int has_unstaged_changes(const char *prefix)
+{
+ struct rev_info rev_info;
+ int result;
+
+ init_revisions(&rev_info, prefix);
+ DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+ diff_setup_done(&rev_info.diffopt);
+ result = run_diff_files(&rev_info, 0);
+ return diff_result_code(&rev_info.diffopt, result);
+}
+
+/**
+ * Returns 1 if there are uncommitted changes, 0 otherwise.
+ */
+static int has_uncommitted_changes(const char *prefix)
+{
+ struct rev_info rev_info;
+ int result;
+
+ if (is_cache_unborn())
+ return 0;
+
+ init_revisions(&rev_info, prefix);
+ DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
+ DIFF_OPT_SET(&rev_info.diffopt, QUICK);
+ add_head_to_pending(&rev_info);
+ diff_setup_done(&rev_info.diffopt);
+ result = run_diff_index(&rev_info, 1);
+ return diff_result_code(&rev_info.diffopt, result);
+}
+
+/**
+ * If the work tree has unstaged or uncommitted changes, dies with the
+ * appropriate message.
+ */
+static void die_on_unclean_work_tree(const char *prefix)
+{
+ struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
+ int do_die = 0;
+
+ hold_locked_index(lock_file, 0);
+ refresh_cache(REFRESH_QUIET);
+ update_index_if_able(&the_index, lock_file);
+ rollback_lock_file(lock_file);
+
+ if (has_unstaged_changes(prefix)) {
+ error(_("Cannot pull with rebase: You have unstaged changes."));
+ do_die = 1;
+ }
+
+ if (has_uncommitted_changes(prefix)) {
+ if (do_die)
+ error(_("Additionally, your index contains uncommitted changes."));
+ else
+ error(_("Cannot pull with rebase: Your index contains uncommitted changes."));
+ do_die = 1;
+ }
+
+ if (do_die)
+ exit(1);
+}
+
+/**
+ * Appends merge candidates from FETCH_HEAD that are not marked not-for-merge
+ * into merge_heads.
+ */
+static void get_merge_heads(struct sha1_array *merge_heads)
+{
+ const char *filename = git_path("FETCH_HEAD");
+ FILE *fp;
+ struct strbuf sb = STRBUF_INIT;
+ unsigned char sha1[GIT_SHA1_RAWSZ];
+
+ if (!(fp = fopen(filename, "r")))
+ die_errno(_("could not open '%s' for reading"), filename);
+ while (strbuf_getline(&sb, fp, '\n') != EOF) {
+ if (get_sha1_hex(sb.buf, sha1))
+ continue; /* invalid line: does not start with SHA1 */
+ if (starts_with(sb.buf + GIT_SHA1_HEXSZ, "\tnot-for-merge\t"))
+ continue; /* ref is not-for-merge */
+ sha1_array_append(merge_heads, sha1);
+ }
+ fclose(fp);
+ strbuf_release(&sb);
+}
+
+/**
+ * Used by die_no_merge_candidates() as a for_each_remote() callback to
+ * retrieve the name of the remote if the repository only has one remote.
+ */
+static int get_only_remote(struct remote *remote, void *cb_data)
+{
+ const char **remote_name = cb_data;
+
+ if (*remote_name)
+ return -1;
+
+ *remote_name = remote->name;
+ return 0;
+}
+
+/**
+ * Dies with the appropriate reason for why there are no merge candidates:
+ *
+ * 1. We fetched from a specific remote, and a refspec was given, but it ended
+ * up not fetching anything. This is usually because the user provided a
+ * wildcard refspec which had no matches on the remote end.
+ *
+ * 2. We fetched from a non-default remote, but didn't specify a branch to
+ * merge. We can't use the configured one because it applies to the default
+ * remote, thus the user must specify the branches to merge.
+ *
+ * 3. We fetched from the branch's or repo's default remote, but:
+ *
+ * a. We are not on a branch, so there will never be a configured branch to
+ * merge with.
+ *
+ * b. We are on a branch, but there is no configured branch to merge with.
+ *
+ * 4. We fetched from the branch's or repo's default remote, but the configured
+ * branch to merge didn't get fetched. (Either it doesn't exist, or wasn't
+ * part of the configured fetch refspec.)
+ */
+static void NORETURN die_no_merge_candidates(const char *repo, const char **refspecs)
+{
+ struct branch *curr_branch = branch_get("HEAD");
+ const char *remote = curr_branch ? curr_branch->remote_name : NULL;
+
+ if (*refspecs) {
+ if (opt_rebase)
+ fprintf_ln(stderr, _("There is no candidate for rebasing against among the refs that you just fetched."));
+ else
+ fprintf_ln(stderr, _("There are no candidates for merging among the refs that you just fetched."));
+ fprintf_ln(stderr, _("Generally this means that you provided a wildcard refspec which had no\n"
+ "matches on the remote end."));
+ } else if (repo && curr_branch && (!remote || strcmp(repo, remote))) {
+ fprintf_ln(stderr, _("You asked to pull from the remote '%s', but did not specify\n"
+ "a branch. Because this is not the default configured remote\n"
+ "for your current branch, you must specify a branch on the command line."),
+ repo);
+ } else if (!curr_branch) {
+ fprintf_ln(stderr, _("You are not currently on a branch."));
+ if (opt_rebase)
+ fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+ else
+ fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+ fprintf_ln(stderr, _("See git-pull(1) for details."));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git pull <remote> <branch>");
+ fprintf(stderr, "\n");
+ } else if (!curr_branch->merge_nr) {
+ const char *remote_name = NULL;
+
+ if (for_each_remote(get_only_remote, &remote_name) || !remote_name)
+ remote_name = "<remote>";
+
+ fprintf_ln(stderr, _("There is no tracking information for the current branch."));
+ if (opt_rebase)
+ fprintf_ln(stderr, _("Please specify which branch you want to rebase against."));
+ else
+ fprintf_ln(stderr, _("Please specify which branch you want to merge with."));
+ fprintf_ln(stderr, _("See git-pull(1) for details."));
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, " git pull <remote> <branch>");
+ fprintf(stderr, "\n");
+ fprintf_ln(stderr, _("If you wish to set tracking information for this branch you can do so with:\n"
+ "\n"
+ " git branch --set-upstream-to=%s/<branch> %s\n"),
+ remote_name, curr_branch->name);
+ } else
+ fprintf_ln(stderr, _("Your configuration specifies to merge with the ref '%s'\n"
+ "from the remote, but no such ref was fetched."),
+ *curr_branch->merge_name);
+ exit(1);
+}
+
+/**
+ * Parses argv into [<repo> [<refspecs>...]], returning their values in `repo`
+ * as a string and `refspecs` as a null-terminated array of strings. If `repo`
+ * is not provided in argv, it is set to NULL.
+ */
+static void parse_repo_refspecs(int argc, const char **argv, const char **repo,
+ const char ***refspecs)
+{
+ if (argc > 0) {
+ *repo = *argv++;
+ argc--;
+ } else
+ *repo = NULL;
+ *refspecs = argv;
+}
+
+/**
+ * Runs git-fetch, returning its exit status. `repo` and `refspecs` are the
+ * repository and refspecs to fetch, or NULL if they are not provided.
+ */
+static int run_fetch(const char *repo, const char **refspecs)
+{
+ struct argv_array args = ARGV_ARRAY_INIT;
+ int ret;
+
+ argv_array_pushl(&args, "fetch", "--update-head-ok", NULL);
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+ if (opt_progress)
+ argv_array_push(&args, opt_progress);
+
+ /* Options passed to git-fetch */
+ if (opt_all)
+ argv_array_push(&args, opt_all);
+ if (opt_append)
+ argv_array_push(&args, opt_append);
+ if (opt_upload_pack)
+ argv_array_push(&args, opt_upload_pack);
+ argv_push_force(&args);
+ if (opt_tags)
+ argv_array_push(&args, opt_tags);
+ if (opt_prune)
+ argv_array_push(&args, opt_prune);
+ if (opt_recurse_submodules)
+ argv_array_push(&args, opt_recurse_submodules);
+ if (opt_dry_run)
+ argv_array_push(&args, "--dry-run");
+ if (opt_keep)
+ argv_array_push(&args, opt_keep);
+ if (opt_depth)
+ argv_array_push(&args, opt_depth);
+ if (opt_unshallow)
+ argv_array_push(&args, opt_unshallow);
+ if (opt_update_shallow)
+ argv_array_push(&args, opt_update_shallow);
+ if (opt_refmap)
+ argv_array_push(&args, opt_refmap);
+
+ if (repo) {
+ argv_array_push(&args, repo);
+ argv_array_pushv(&args, refspecs);
+ } else if (*refspecs)
+ die("BUG: refspecs without repo?");
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+/**
+ * "Pulls into void" by branching off merge_head.
+ */
+static int pull_into_void(const unsigned char *merge_head,
+ const unsigned char *curr_head)
+{
+ /*
+ * Two-way merge: we treat the index as based on an empty tree,
+ * and try to fast-forward to HEAD. This ensures we will not lose
+ * index/worktree changes that the user already made on the unborn
+ * branch.
+ */
+ if (checkout_fast_forward(EMPTY_TREE_SHA1_BIN, merge_head, 0))
+ return 1;
+
+ if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * Runs git-merge, returning its exit status.
+ */
+static int run_merge(void)
+{
+ int ret;
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ argv_array_pushl(&args, "merge", NULL);
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+ if (opt_progress)
+ argv_array_push(&args, opt_progress);
+
+ /* Options passed to git-merge */
+ if (opt_diffstat)
+ argv_array_push(&args, opt_diffstat);
+ if (opt_log)
+ argv_array_push(&args, opt_log);
+ if (opt_squash)
+ argv_array_push(&args, opt_squash);
+ if (opt_commit)
+ argv_array_push(&args, opt_commit);
+ if (opt_edit)
+ argv_array_push(&args, opt_edit);
+ if (opt_ff)
+ argv_array_push(&args, opt_ff);
+ if (opt_verify_signatures)
+ argv_array_push(&args, opt_verify_signatures);
+ argv_array_pushv(&args, opt_strategies.argv);
+ argv_array_pushv(&args, opt_strategy_opts.argv);
+ if (opt_gpg_sign)
+ argv_array_push(&args, opt_gpg_sign);
+
+ argv_array_push(&args, "FETCH_HEAD");
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+/**
+ * Returns remote's upstream branch for the current branch. If remote is NULL,
+ * the current branch's configured default remote is used. Returns NULL if
+ * `remote` does not name a valid remote, HEAD does not point to a branch,
+ * remote is not the branch's configured remote or the branch does not have any
+ * configured upstream branch.
+ */
+static const char *get_upstream_branch(const char *remote)
+{
+ struct remote *rm;
+ struct branch *curr_branch;
+ const char *curr_branch_remote;
+
+ rm = remote_get(remote);
+ if (!rm)
+ return NULL;
+
+ curr_branch = branch_get("HEAD");
+ if (!curr_branch)
+ return NULL;
+
+ curr_branch_remote = remote_for_branch(curr_branch, NULL);
+ assert(curr_branch_remote);
+
+ if (strcmp(curr_branch_remote, rm->name))
+ return NULL;
+
+ return branch_get_upstream(curr_branch, NULL);
+}
+
+/**
+ * Derives the remote tracking branch from the remote and refspec.
+ *
+ * FIXME: The current implementation assumes the default mapping of
+ * refs/heads/<branch_name> to refs/remotes/<remote_name>/<branch_name>.
+ */
+static const char *get_tracking_branch(const char *remote, const char *refspec)
+{
+ struct refspec *spec;
+ const char *spec_src;
+ const char *merge_branch;
+
+ spec = parse_fetch_refspec(1, &refspec);
+ spec_src = spec->src;
+ if (!*spec_src || !strcmp(spec_src, "HEAD"))
+ spec_src = "HEAD";
+ else if (skip_prefix(spec_src, "heads/", &spec_src))
+ ;
+ else if (skip_prefix(spec_src, "refs/heads/", &spec_src))
+ ;
+ else if (starts_with(spec_src, "refs/") ||
+ starts_with(spec_src, "tags/") ||
+ starts_with(spec_src, "remotes/"))
+ spec_src = "";
+
+ if (*spec_src) {
+ if (!strcmp(remote, "."))
+ merge_branch = mkpath("refs/heads/%s", spec_src);
+ else
+ merge_branch = mkpath("refs/remotes/%s/%s", remote, spec_src);
+ } else
+ merge_branch = NULL;
+
+ free_refspec(1, spec);
+ return merge_branch;
+}
+
+/**
+ * Given the repo and refspecs, sets fork_point to the point at which the
+ * current branch forked from its remote tracking branch. Returns 0 on success,
+ * -1 on failure.
+ */
+static int get_rebase_fork_point(unsigned char *fork_point, const char *repo,
+ const char *refspec)
+{
+ int ret;
+ struct branch *curr_branch;
+ const char *remote_branch;
+ struct child_process cp = CHILD_PROCESS_INIT;
+ struct strbuf sb = STRBUF_INIT;
+
+ curr_branch = branch_get("HEAD");
+ if (!curr_branch)
+ return -1;
+
+ if (refspec)
+ remote_branch = get_tracking_branch(repo, refspec);
+ else
+ remote_branch = get_upstream_branch(repo);
+
+ if (!remote_branch)
+ return -1;
+
+ argv_array_pushl(&cp.args, "merge-base", "--fork-point",
+ remote_branch, curr_branch->name, NULL);
+ cp.no_stdin = 1;
+ cp.no_stderr = 1;
+ cp.git_cmd = 1;
+
+ ret = capture_command(&cp, &sb, GIT_SHA1_HEXSZ);
+ if (ret)
+ goto cleanup;
+
+ ret = get_sha1_hex(sb.buf, fork_point);
+ if (ret)
+ goto cleanup;
+
+cleanup:
+ strbuf_release(&sb);
+ return ret ? -1 : 0;
+}
+
+/**
+ * Sets merge_base to the octopus merge base of curr_head, merge_head and
+ * fork_point. Returns 0 if a merge base is found, 1 otherwise.
+ */
+static int get_octopus_merge_base(unsigned char *merge_base,
+ const unsigned char *curr_head,
+ const unsigned char *merge_head,
+ const unsigned char *fork_point)
+{
+ struct commit_list *revs = NULL, *result;
+
+ commit_list_insert(lookup_commit_reference(curr_head), &revs);
+ commit_list_insert(lookup_commit_reference(merge_head), &revs);
+ if (!is_null_sha1(fork_point))
+ commit_list_insert(lookup_commit_reference(fork_point), &revs);
+
+ result = reduce_heads(get_octopus_merge_bases(revs));
+ free_commit_list(revs);
+ if (!result)
+ return 1;
+
+ hashcpy(merge_base, result->item->object.sha1);
+ return 0;
+}
+
+/**
+ * Given the current HEAD SHA1, the merge head returned from git-fetch and the
+ * fork point calculated by get_rebase_fork_point(), runs git-rebase with the
+ * appropriate arguments and returns its exit status.
+ */
+static int run_rebase(const unsigned char *curr_head,
+ const unsigned char *merge_head,
+ const unsigned char *fork_point)
+{
+ int ret;
+ unsigned char oct_merge_base[GIT_SHA1_RAWSZ];
+ struct argv_array args = ARGV_ARRAY_INIT;
+
+ if (!get_octopus_merge_base(oct_merge_base, curr_head, merge_head, fork_point))
+ if (!is_null_sha1(fork_point) && !hashcmp(oct_merge_base, fork_point))
+ fork_point = NULL;
+
+ argv_array_push(&args, "rebase");
+
+ /* Shared options */
+ argv_push_verbosity(&args);
+
+ /* Options passed to git-rebase */
+ if (opt_rebase == REBASE_PRESERVE)
+ argv_array_push(&args, "--preserve-merges");
+ if (opt_diffstat)
+ argv_array_push(&args, opt_diffstat);
+ argv_array_pushv(&args, opt_strategies.argv);
+ argv_array_pushv(&args, opt_strategy_opts.argv);
+ if (opt_gpg_sign)
+ argv_array_push(&args, opt_gpg_sign);
+
+ argv_array_push(&args, "--onto");
+ argv_array_push(&args, sha1_to_hex(merge_head));
+
+ if (fork_point && !is_null_sha1(fork_point))
+ argv_array_push(&args, sha1_to_hex(fork_point));
+ else
+ argv_array_push(&args, sha1_to_hex(merge_head));
+
+ ret = run_command_v_opt(args.argv, RUN_GIT_CMD);
+ argv_array_clear(&args);
+ return ret;
+}
+
+int cmd_pull(int argc, const char **argv, const char *prefix)
+{
+ const char *repo, **refspecs;
+ struct sha1_array merge_heads = SHA1_ARRAY_INIT;
+ unsigned char orig_head[GIT_SHA1_RAWSZ], curr_head[GIT_SHA1_RAWSZ];
+ unsigned char rebase_fork_point[GIT_SHA1_RAWSZ];
+
+ if (!getenv("GIT_REFLOG_ACTION"))
+ set_reflog_message(argc, argv);
+
+ argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
+
+ parse_repo_refspecs(argc, argv, &repo, &refspecs);
+
+ if (!opt_ff)
+ opt_ff = xstrdup_or_null(config_get_ff());
+
+ if (opt_rebase < 0)
+ opt_rebase = config_get_rebase();
+
+ git_config(git_default_config, NULL);
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("Pull");
+
+ if (file_exists(git_path("MERGE_HEAD")))
+ die_conclude_merge();
+
+ if (get_sha1("HEAD", orig_head))
+ hashclr(orig_head);
+
+ if (opt_rebase) {
+ int autostash = 0;
+
+ if (is_null_sha1(orig_head) && !is_cache_unborn())
+ die(_("Updating an unborn branch with changes added to the index."));
+
+ git_config_get_bool("rebase.autostash", &autostash);
+ if (!autostash)
+ die_on_unclean_work_tree(prefix);
+
+ if (get_rebase_fork_point(rebase_fork_point, repo, *refspecs))
+ hashclr(rebase_fork_point);
+ }
+
+ if (run_fetch(repo, refspecs))
+ return 1;
+
+ if (opt_dry_run)
+ return 0;
+
+ if (get_sha1("HEAD", curr_head))
+ hashclr(curr_head);
+
+ if (!is_null_sha1(orig_head) && !is_null_sha1(curr_head) &&
+ hashcmp(orig_head, curr_head)) {
+ /*
+ * The fetch involved updating the current branch.
+ *
+ * The working tree and the index file are still based on
+ * orig_head commit, but we are merging into curr_head.
+ * Update the working tree to match curr_head.
+ */
+
+ warning(_("fetch updated the current branch head.\n"
+ "fast-forwarding your working tree from\n"
+ "commit %s."), sha1_to_hex(orig_head));
+
+ if (checkout_fast_forward(orig_head, curr_head, 0))
+ die(_("Cannot fast-forward your working tree.\n"
+ "After making sure that you saved anything precious from\n"
+ "$ git diff %s\n"
+ "output, run\n"
+ "$ git reset --hard\n"
+ "to recover."), sha1_to_hex(orig_head));
+ }
+
+ get_merge_heads(&merge_heads);
+
+ if (!merge_heads.nr)
+ die_no_merge_candidates(repo, refspecs);
+
+ if (is_null_sha1(orig_head)) {
+ if (merge_heads.nr > 1)
+ die(_("Cannot merge multiple branches into empty head."));
+ return pull_into_void(*merge_heads.sha1, curr_head);
+ } else if (opt_rebase) {
+ if (merge_heads.nr > 1)
+ die(_("Cannot rebase onto multiple branches."));
+ return run_rebase(curr_head, *merge_heads.sha1, rebase_fork_point);
+ } else
+ return run_merge();
+}
int ret;
struct lock_file *lock = NULL;
char *filename_buf = NULL;
+ char *contents = NULL;
+ size_t contents_sz;
/* parse-key returns negative; flip the sign to feed exit(3) */
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
goto write_err_out;
} else {
struct stat st;
- char *contents;
- size_t contents_sz, copy_begin, copy_end;
+ size_t copy_begin, copy_end;
int i, new_line = 0;
if (value_regex == NULL)
fstat(in_fd, &st);
contents_sz = xsize_t(st.st_size);
- contents = xmmap(NULL, contents_sz, PROT_READ,
- MAP_PRIVATE, in_fd, 0);
+ contents = xmmap_gently(NULL, contents_sz, PROT_READ,
+ MAP_PRIVATE, in_fd, 0);
+ if (contents == MAP_FAILED) {
+ if (errno == ENODEV && S_ISDIR(st.st_mode))
+ errno = EISDIR;
+ error("unable to mmap '%s': %s",
+ config_filename, strerror(errno));
+ ret = CONFIG_INVALID_FILE;
+ contents = NULL;
+ goto out_free;
+ }
close(in_fd);
- if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
+ if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
error("chmod on %s failed: %s",
- lock->filename.buf, strerror(errno));
+ get_lock_file_path(lock), strerror(errno));
ret = CONFIG_NO_WRITE;
goto out_free;
}
goto write_err_out;
munmap(contents, contents_sz);
+ contents = NULL;
}
if (commit_lock_file(lock) < 0) {
if (lock)
rollback_lock_file(lock);
free(filename_buf);
+ if (contents)
+ munmap(contents, contents_sz);
return ret;
write_err_out:
- ret = write_error(lock->filename.buf);
+ ret = write_error(get_lock_file_path(lock));
goto out_free;
}
fstat(fileno(config_file), &st);
- if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) {
+ if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
ret = error("chmod on %s failed: %s",
- lock->filename.buf, strerror(errno));
+ get_lock_file_path(lock), strerror(errno));
goto out;
}
}
store.baselen = strlen(new_name);
if (!store_write_section(out_fd, new_name)) {
- ret = write_error(lock->filename.buf);
+ ret = write_error(get_lock_file_path(lock));
goto out;
}
/*
continue;
length = strlen(output);
if (write_in_full(out_fd, output, length) != length) {
- ret = write_error(lock->filename.buf);
+ ret = write_error(get_lock_file_path(lock));
goto out;
}
}
* Copyright (C) 2005 Junio C Hamano
*/
#include "cache.h"
+ #include "tempfile.h"
#include "quote.h"
#include "diff.h"
#include "diffcore.h"
static char diff_colors[][COLOR_MAXLEN] = {
GIT_COLOR_RESET,
- GIT_COLOR_NORMAL, /* PLAIN */
+ GIT_COLOR_NORMAL, /* CONTEXT */
GIT_COLOR_BOLD, /* METAINFO */
GIT_COLOR_CYAN, /* FRAGINFO */
GIT_COLOR_RED, /* OLD */
static int parse_diff_color_slot(const char *var)
{
- if (!strcasecmp(var, "plain"))
- return DIFF_PLAIN;
+ if (!strcasecmp(var, "context") || !strcasecmp(var, "plain"))
+ return DIFF_CONTEXT;
if (!strcasecmp(var, "meta"))
return DIFF_METAINFO;
if (!strcasecmp(var, "frag"))
return external_diff_cmd;
}
+ /*
+ * Keep track of files used for diffing. Sometimes such an entry
+ * refers to a temporary file, sometimes to an existing file, and
+ * sometimes to "/dev/null".
+ */
static struct diff_tempfile {
- const char *name; /* filename external diff should read from */
+ /*
+ * filename external diff should read from, or NULL if this
+ * entry is currently not in use:
+ */
+ const char *name;
+
char hex[41];
char mode[10];
- char tmp_path[PATH_MAX];
+
+ /*
+ * If this diff_tempfile instance refers to a temporary file,
+ * this tempfile object is used to manage its lifetime.
+ */
+ struct tempfile tempfile;
} diff_temp[2];
typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
return ws_blank_line(line, len, ecbdata->ws_rule);
}
-static void emit_add_line(const char *reset,
- struct emit_callback *ecbdata,
- const char *line, int len)
+static void emit_line_checked(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len,
+ enum color_diff color,
+ unsigned ws_error_highlight,
+ char sign)
{
- const char *ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
- const char *set = diff_get_color(ecbdata->color_diff, DIFF_FILE_NEW);
+ const char *set = diff_get_color(ecbdata->color_diff, color);
+ const char *ws = NULL;
- if (!*ws)
- emit_line_0(ecbdata->opt, set, reset, '+', line, len);
- else if (new_blank_line_at_eof(ecbdata, line, len))
+ if (ecbdata->opt->ws_error_highlight & ws_error_highlight) {
+ ws = diff_get_color(ecbdata->color_diff, DIFF_WHITESPACE);
+ if (!*ws)
+ ws = NULL;
+ }
+
+ if (!ws)
+ emit_line_0(ecbdata->opt, set, reset, sign, line, len);
+ else if (sign == '+' && new_blank_line_at_eof(ecbdata, line, len))
/* Blank line at EOF - paint '+' as well */
- emit_line_0(ecbdata->opt, ws, reset, '+', line, len);
+ emit_line_0(ecbdata->opt, ws, reset, sign, line, len);
else {
/* Emit just the prefix, then the rest. */
- emit_line_0(ecbdata->opt, set, reset, '+', "", 0);
+ emit_line_0(ecbdata->opt, set, reset, sign, "", 0);
ws_check_emit(line, len, ecbdata->ws_rule,
ecbdata->opt->file, set, reset, ws);
}
}
+static void emit_add_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_FILE_NEW, WSEH_NEW, '+');
+}
+
+static void emit_del_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_FILE_OLD, WSEH_OLD, '-');
+}
+
+static void emit_context_line(const char *reset,
+ struct emit_callback *ecbdata,
+ const char *line, int len)
+{
+ emit_line_checked(reset, ecbdata, line, len,
+ DIFF_CONTEXT, WSEH_CONTEXT, ' ');
+}
+
static void emit_hunk_header(struct emit_callback *ecbdata,
const char *line, int len)
{
- const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
if (len < 10 ||
memcmp(line, atat, 2) ||
!(ep = memmem(line + 2, len - 2, atat, 2))) {
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
return;
}
ep += 2; /* skip over @@ */
if (*ep != ' ' && *ep != '\t')
break;
if (ep != cp) {
- strbuf_addstr(&msgbuf, plain);
+ strbuf_addstr(&msgbuf, context);
strbuf_add(&msgbuf, cp, ep - cp);
strbuf_addstr(&msgbuf, reset);
}
die("BUG: diff is failing to clean up its tempfiles");
}
- static int remove_tempfile_installed;
-
static void remove_tempfile(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
- if (diff_temp[i].name == diff_temp[i].tmp_path)
- unlink_or_warn(diff_temp[i].name);
+ if (is_tempfile_active(&diff_temp[i].tempfile))
+ delete_tempfile(&diff_temp[i].tempfile);
diff_temp[i].name = NULL;
}
}
- static void remove_tempfile_on_signal(int signo)
- {
- remove_tempfile();
- sigchain_pop(signo);
- raise(signo);
- }
-
static void print_line_count(FILE *file, int count)
{
switch (count) {
{
const char *endp = NULL;
static const char *nneof = " No newline at end of file\n";
- const char *old = diff_get_color(ecb->color_diff, DIFF_FILE_OLD);
const char *reset = diff_get_color(ecb->color_diff, DIFF_RESET);
while (0 < size) {
len = endp ? (endp - data + 1) : size;
if (prefix != '+') {
ecb->lno_in_preimage++;
- emit_line_0(ecb->opt, old, reset, '-',
- data, len);
+ emit_del_line(reset, ecb, data, len);
} else {
ecb->lno_in_postimage++;
emit_add_line(reset, ecb, data, len);
data += len;
}
if (!endp) {
- const char *plain = diff_get_color(ecb->color_diff,
- DIFF_PLAIN);
+ const char *context = diff_get_color(ecb->color_diff,
+ DIFF_CONTEXT);
putc('\n', ecb->opt->file);
- emit_line_0(ecb->opt, plain, reset, '\\',
+ emit_line_0(ecb->opt, context, reset, '\\',
nneof, strlen(nneof));
}
}
struct diff_words_style *st = ecbdata->diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
- st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
+ st->ctx.color = diff_get_color_opt(o, DIFF_CONTEXT);
}
}
{
struct emit_callback *ecbdata = priv;
const char *meta = diff_get_color(ecbdata->color_diff, DIFF_METAINFO);
- const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+ const char *context = diff_get_color(ecbdata->color_diff, DIFF_CONTEXT);
const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
struct diff_options *o = ecbdata->opt;
const char *line_prefix = diff_line_prefix(o);
}
diff_words_flush(ecbdata);
if (ecbdata->diff_words->type == DIFF_WORDS_PORCELAIN) {
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
fputs("~\n", ecbdata->opt->file);
} else {
/*
line++;
len--;
}
- emit_line(ecbdata->opt, plain, reset, line, len);
+ emit_line(ecbdata->opt, context, reset, line, len);
}
return;
}
- if (line[0] != '+') {
- const char *color =
- diff_get_color(ecbdata->color_diff,
- line[0] == '-' ? DIFF_FILE_OLD : DIFF_PLAIN);
- ecbdata->lno_in_preimage++;
- if (line[0] == ' ')
- ecbdata->lno_in_postimage++;
- emit_line(ecbdata->opt, color, reset, line, len);
- } else {
+ switch (line[0]) {
+ case '+':
ecbdata->lno_in_postimage++;
emit_add_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ case '-':
+ ecbdata->lno_in_preimage++;
+ emit_del_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ case ' ':
+ ecbdata->lno_in_postimage++;
+ ecbdata->lno_in_preimage++;
+ emit_context_line(reset, ecbdata, line + 1, len - 1);
+ break;
+ default:
+ /* incomplete line at the end */
+ ecbdata->lno_in_preimage++;
+ emit_line(ecbdata->opt,
+ diff_get_color(ecbdata->color_diff, DIFF_CONTEXT),
+ reset, line, len);
+ break;
}
}
strbuf_addstr(&template, "XXXXXX_");
strbuf_addstr(&template, base);
- fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
- strlen(base) + 1);
+ fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1);
if (fd < 0)
die_errno("unable to create temp-file");
if (convert_to_working_tree(path,
}
if (write_in_full(fd, blob, size) != size)
die_errno("unable to write temp-file");
- close(fd);
- temp->name = temp->tmp_path;
+ close_tempfile(&temp->tempfile);
+ temp->name = get_tempfile_path(&temp->tempfile);
strcpy(temp->hex, sha1_to_hex(sha1));
temp->hex[40] = 0;
sprintf(temp->mode, "%06o", mode);
return temp;
}
- if (!remove_tempfile_installed) {
- atexit(remove_tempfile);
- sigchain_push_common(remove_tempfile_on_signal);
- remove_tempfile_installed = 1;
- }
-
if (!S_ISGITLINK(one->mode) &&
(!one->sha1_valid ||
reuse_worktree_file(name, one->sha1, 1))) {
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = diff_context_default;
+ options->ws_error_highlight = WSEH_NEW;
DIFF_OPT_SET(options, RENAME_EMPTY);
/* pathchange left =NULL by default */
*fmt |= DIFF_FORMAT_PATCH;
}
+static int parse_one_token(const char **arg, const char *token)
+{
+ const char *rest;
+ if (skip_prefix(*arg, token, &rest) && (!*rest || *rest == ',')) {
+ *arg = rest;
+ return 1;
+ }
+ return 0;
+}
+
+static int parse_ws_error_highlight(struct diff_options *opt, const char *arg)
+{
+ const char *orig_arg = arg;
+ unsigned val = 0;
+ while (*arg) {
+ if (parse_one_token(&arg, "none"))
+ val = 0;
+ else if (parse_one_token(&arg, "default"))
+ val = WSEH_NEW;
+ else if (parse_one_token(&arg, "all"))
+ val = WSEH_NEW | WSEH_OLD | WSEH_CONTEXT;
+ else if (parse_one_token(&arg, "new"))
+ val |= WSEH_NEW;
+ else if (parse_one_token(&arg, "old"))
+ val |= WSEH_OLD;
+ else if (parse_one_token(&arg, "context"))
+ val |= WSEH_CONTEXT;
+ else {
+ error("unknown value after ws-error-highlight=%.*s",
+ (int)(arg - orig_arg), orig_arg);
+ return 0;
+ }
+ if (*arg)
+ arg++;
+ }
+ opt->ws_error_highlight = val;
+ return 1;
+}
+
int diff_opt_parse(struct diff_options *options, const char **av, int ac)
{
const char *arg = av[0];
DIFF_OPT_SET(options, FIND_COPIES_HARDER);
else if (!strcmp(arg, "--follow"))
DIFF_OPT_SET(options, FOLLOW_RENAMES);
- else if (!strcmp(arg, "--no-follow"))
+ else if (!strcmp(arg, "--no-follow")) {
DIFF_OPT_CLR(options, FOLLOW_RENAMES);
- else if (!strcmp(arg, "--color"))
+ DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
+ } else if (!strcmp(arg, "--color"))
options->use_color = 1;
else if (skip_prefix(arg, "--color=", &arg)) {
int value = git_config_colorbool(NULL, arg);
DIFF_OPT_SET(options, SUBMODULE_LOG);
else if (skip_prefix(arg, "--submodule=", &arg))
return parse_submodule_opt(options, arg);
+ else if (skip_prefix(arg, "--ws-error-highlight=", &arg))
+ return parse_ws_error_highlight(options, arg);
/* misc options */
else if (!strcmp(arg, "-z"))
/*
* Copyright (c) 2005, Junio C Hamano
*/
+
#include "cache.h"
#include "lockfile.h"
- #include "sigchain.h"
-
- static struct lock_file *volatile lock_file_list;
-
- static void remove_lock_files(int skip_fclose)
- {
- pid_t me = getpid();
-
- while (lock_file_list) {
- if (lock_file_list->owner == me) {
- /* fclose() is not safe to call in a signal handler */
- if (skip_fclose)
- lock_file_list->fp = NULL;
- rollback_lock_file(lock_file_list);
- }
- lock_file_list = lock_file_list->next;
- }
- }
-
- static void remove_lock_files_on_exit(void)
- {
- remove_lock_files(0);
- }
-
- static void remove_lock_files_on_signal(int signo)
- {
- remove_lock_files(1);
- sigchain_pop(signo);
- raise(signo);
- }
/*
* path = absolute or relative path name
/* Make sure errno contains a meaningful value on error */
static int lock_file(struct lock_file *lk, const char *path, int flags)
{
- size_t pathlen = strlen(path);
-
- if (!lock_file_list) {
- /* One-time initialization */
- sigchain_push_common(remove_lock_files_on_signal);
- atexit(remove_lock_files_on_exit);
- }
-
- if (lk->active)
- die("BUG: cannot lock_file(\"%s\") using active struct lock_file",
- path);
- if (!lk->on_list) {
- /* Initialize *lk and add it to lock_file_list: */
- lk->fd = -1;
- lk->fp = NULL;
- lk->active = 0;
- lk->owner = 0;
- strbuf_init(&lk->filename, pathlen + LOCK_SUFFIX_LEN);
- lk->next = lock_file_list;
- lock_file_list = lk;
- lk->on_list = 1;
- } else if (lk->filename.len) {
- /* This shouldn't happen, but better safe than sorry. */
- die("BUG: lock_file(\"%s\") called with improperly-reset lock_file object",
- path);
- }
+ int fd;
+ struct strbuf filename = STRBUF_INIT;
- if (flags & LOCK_NO_DEREF) {
- strbuf_add_absolute_path(&lk->filename, path);
- } else {
- struct strbuf resolved_path = STRBUF_INIT;
-
- strbuf_add(&resolved_path, path, pathlen);
- resolve_symlink(&resolved_path);
- strbuf_add_absolute_path(&lk->filename, resolved_path.buf);
- strbuf_release(&resolved_path);
- }
+ strbuf_addstr(&filename, path);
+ if (!(flags & LOCK_NO_DEREF))
+ resolve_symlink(&filename);
- strbuf_addstr(&lk->filename, LOCK_SUFFIX);
- lk->fd = open(lk->filename.buf, O_RDWR | O_CREAT | O_EXCL, 0666);
- if (lk->fd < 0) {
- strbuf_reset(&lk->filename);
- return -1;
- }
- lk->owner = getpid();
- lk->active = 1;
- if (adjust_shared_perm(lk->filename.buf)) {
- int save_errno = errno;
- error("cannot fix permission bits on %s", lk->filename.buf);
- rollback_lock_file(lk);
- errno = save_errno;
- return -1;
- }
- return lk->fd;
+ strbuf_addstr(&filename, LOCK_SUFFIX);
+ fd = create_tempfile(&lk->tempfile, filename.buf);
+ strbuf_release(&filename);
+ return fd;
}
-static int sleep_microseconds(long us)
-{
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = us;
- return select(0, NULL, NULL, NULL, &tv);
-}
-
/*
* Constants defining the gaps between attempts to lock a file. The
* first backoff period is approximately INITIAL_BACKOFF_MS
{
int n = 1;
int multiplier = 1;
- long remaining_us = 0;
+ long remaining_ms = 0;
static int random_initialized = 0;
if (timeout_ms == 0)
return lock_file(lk, path, flags);
if (!random_initialized) {
- srandom((unsigned int)getpid());
+ srand((unsigned int)getpid());
random_initialized = 1;
}
- if (timeout_ms > 0) {
- /* avoid overflow */
- if (timeout_ms <= LONG_MAX / 1000)
- remaining_us = timeout_ms * 1000;
- else
- remaining_us = LONG_MAX;
- }
+ if (timeout_ms > 0)
+ remaining_ms = timeout_ms;
while (1) {
- long backoff_ms, wait_us;
+ long backoff_ms, wait_ms;
int fd;
fd = lock_file(lk, path, flags);
return fd; /* success */
else if (errno != EEXIST)
return -1; /* failure other than lock held */
- else if (timeout_ms > 0 && remaining_us <= 0)
+ else if (timeout_ms > 0 && remaining_ms <= 0)
return -1; /* failure due to timeout */
backoff_ms = multiplier * INITIAL_BACKOFF_MS;
/* back off for between 0.75*backoff_ms and 1.25*backoff_ms */
- wait_us = (750 + random() % 500) * backoff_ms;
- sleep_microseconds(wait_us);
- remaining_us -= wait_us;
+ wait_ms = (750 + rand() % 500) * backoff_ms / 1000;
+ sleep_millisec(wait_ms);
+ remaining_ms -= wait_ms;
/* Recursion: (n+1)^2 = n^2 + 2n + 1 */
multiplier += 2*n + 1;
return fd;
}
- FILE *fdopen_lock_file(struct lock_file *lk, const char *mode)
- {
- if (!lk->active)
- die("BUG: fdopen_lock_file() called for unlocked object");
- if (lk->fp)
- die("BUG: fdopen_lock_file() called twice for file '%s'", lk->filename.buf);
-
- lk->fp = fdopen(lk->fd, mode);
- return lk->fp;
- }
-
char *get_locked_file_path(struct lock_file *lk)
{
- if (!lk->active)
- die("BUG: get_locked_file_path() called for unlocked object");
- if (lk->filename.len <= LOCK_SUFFIX_LEN)
- die("BUG: get_locked_file_path() called for malformed lock object");
- return xmemdupz(lk->filename.buf, lk->filename.len - LOCK_SUFFIX_LEN);
- }
-
- int close_lock_file(struct lock_file *lk)
- {
- int fd = lk->fd;
- FILE *fp = lk->fp;
- int err;
-
- if (fd < 0)
- return 0;
-
- lk->fd = -1;
- if (fp) {
- lk->fp = NULL;
-
- /*
- * Note: no short-circuiting here; we want to fclose()
- * in any case!
- */
- err = ferror(fp) | fclose(fp);
- } else {
- err = close(fd);
- }
+ struct strbuf ret = STRBUF_INIT;
- if (err) {
- int save_errno = errno;
- rollback_lock_file(lk);
- errno = save_errno;
- return -1;
- }
-
- return 0;
- }
-
- int reopen_lock_file(struct lock_file *lk)
- {
- if (0 <= lk->fd)
- die(_("BUG: reopen a lockfile that is still open"));
- if (!lk->active)
- die(_("BUG: reopen a lockfile that has been committed"));
- lk->fd = open(lk->filename.buf, O_WRONLY);
- return lk->fd;
+ strbuf_addstr(&ret, get_tempfile_path(&lk->tempfile));
+ if (ret.len <= LOCK_SUFFIX_LEN ||
+ strcmp(ret.buf + ret.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
+ die("BUG: get_locked_file_path() called for malformed lock object");
+ /* remove ".lock": */
+ strbuf_setlen(&ret, ret.len - LOCK_SUFFIX_LEN);
+ return strbuf_detach(&ret, NULL);
}
- int commit_lock_file_to(struct lock_file *lk, const char *path)
+ int commit_lock_file(struct lock_file *lk)
{
- if (!lk->active)
- die("BUG: attempt to commit unlocked object to \"%s\"", path);
+ char *result_path = get_locked_file_path(lk);
- if (close_lock_file(lk))
- return -1;
-
- if (rename(lk->filename.buf, path)) {
+ if (commit_lock_file_to(lk, result_path)) {
int save_errno = errno;
- rollback_lock_file(lk);
+ free(result_path);
errno = save_errno;
return -1;
}
-
- lk->active = 0;
- strbuf_reset(&lk->filename);
+ free(result_path);
return 0;
}
-
- int commit_lock_file(struct lock_file *lk)
- {
- static struct strbuf result_file = STRBUF_INIT;
- int err;
-
- if (!lk->active)
- die("BUG: attempt to commit unlocked object");
-
- if (lk->filename.len <= LOCK_SUFFIX_LEN ||
- strcmp(lk->filename.buf + lk->filename.len - LOCK_SUFFIX_LEN, LOCK_SUFFIX))
- die("BUG: lockfile filename corrupt");
-
- /* remove ".lock": */
- strbuf_add(&result_file, lk->filename.buf,
- lk->filename.len - LOCK_SUFFIX_LEN);
- err = commit_lock_file_to(lk, result_file.buf);
- strbuf_reset(&result_file);
- return err;
- }
-
- void rollback_lock_file(struct lock_file *lk)
- {
- if (!lk->active)
- return;
-
- if (!close_lock_file(lk)) {
- unlink_or_warn(lk->filename.buf);
- lk->active = 0;
- strbuf_reset(&lk->filename);
- }
- }
*/
#define NO_THE_INDEX_COMPATIBILITY_MACROS
#include "cache.h"
+ #include "tempfile.h"
#include "lockfile.h"
#include "cache-tree.h"
#include "refs.h"
}
pos = -pos-1;
- untracked_cache_add_to_index(istate, ce->name);
+ if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
+ untracked_cache_add_to_index(istate, ce->name);
/*
* Inserting a merged entry ("stage 0") into the index
if (mmap_size < sizeof(struct cache_header) + 20)
die("index file smaller than expected");
- mmap = xmmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ mmap = xmmap(NULL, mmap_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mmap == MAP_FAILED)
die_errno("unable to map index file");
close(fd);
static int do_write_locked_index(struct index_state *istate, struct lock_file *lock,
unsigned flags)
{
- int ret = do_write_index(istate, lock->fd, 0);
+ int ret = do_write_index(istate, get_lock_file_fd(lock), 0);
if (ret)
return ret;
assert((flags & (COMMIT_LOCK | CLOSE_LOCK)) !=
return ret;
}
- static char *temporary_sharedindex;
-
- static void remove_temporary_sharedindex(void)
- {
- if (temporary_sharedindex) {
- unlink_or_warn(temporary_sharedindex);
- free(temporary_sharedindex);
- temporary_sharedindex = NULL;
- }
- }
-
- static void remove_temporary_sharedindex_on_signal(int signo)
- {
- remove_temporary_sharedindex();
- sigchain_pop(signo);
- raise(signo);
- }
+ static struct tempfile temporary_sharedindex;
static int write_shared_index(struct index_state *istate,
struct lock_file *lock, unsigned flags)
{
struct split_index *si = istate->split_index;
- static int installed_handler;
int fd, ret;
- temporary_sharedindex = git_pathdup("sharedindex_XXXXXX");
- fd = mkstemp(temporary_sharedindex);
+ fd = mks_tempfile(&temporary_sharedindex, git_path("sharedindex_XXXXXX"));
if (fd < 0) {
- free(temporary_sharedindex);
- temporary_sharedindex = NULL;
hashclr(si->base_sha1);
return do_write_locked_index(istate, lock, flags);
}
- if (!installed_handler) {
- atexit(remove_temporary_sharedindex);
- sigchain_push_common(remove_temporary_sharedindex_on_signal);
- }
move_cache_to_base_index(istate);
ret = do_write_index(si->base, fd, 1);
- close(fd);
if (ret) {
- remove_temporary_sharedindex();
+ delete_tempfile(&temporary_sharedindex);
return ret;
}
- ret = rename(temporary_sharedindex,
- git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
- free(temporary_sharedindex);
- temporary_sharedindex = NULL;
+ ret = rename_tempfile(&temporary_sharedindex,
+ git_path("sharedindex.%s", sha1_to_hex(si->base->sha1)));
if (!ret)
hashcpy(si->base_sha1, si->base->sha1);
return ret;
* 1: End-of-component
* 2: ., look for a preceding . to reject .. in refs
* 3: {, look for a preceding @ to reject @{ in refs
- * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
+ * 4: A bad character: ASCII control characters, and
+ * ":", "?", "[", "\", "^", "~", SP, or TAB
+ * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
*/
static unsigned char refname_disposition[256] = {
1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
- 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
+ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
*/
#define REF_NEEDS_COMMIT 0x20
+/*
+ * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a
+ * value to ref_update::flags
+ */
+
/*
* Try to read one refname component from the front of refname.
* Return the length of the component found, or -1 if the component is
*
* - any path component of it begins with ".", or
* - it has double dots "..", or
- * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
- * - it ends with a "/".
- * - it ends with ".lock"
- * - it contains a "\" (backslash)
+ * - it has ASCII control characters, or
+ * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
+ * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
+ * - it ends with a "/", or
+ * - it ends with ".lock", or
+ * - it contains a "@{" portion
*/
-static int check_refname_component(const char *refname, int flags)
+static int check_refname_component(const char *refname, int *flags)
{
const char *cp;
char last = '\0';
break;
case 4:
return -1;
+ case 5:
+ if (!(*flags & REFNAME_REFSPEC_PATTERN))
+ return -1; /* refspec can't be a pattern */
+
+ /*
+ * Unset the pattern flag so that we only accept
+ * a single asterisk for one side of refspec.
+ */
+ *flags &= ~ REFNAME_REFSPEC_PATTERN;
+ break;
}
last = ch;
}
while (1) {
/* We are at the start of a path component. */
- component_len = check_refname_component(refname, flags);
- if (component_len <= 0) {
- if ((flags & REFNAME_REFSPEC_PATTERN) &&
- refname[0] == '*' &&
- (refname[1] == '\0' || refname[1] == '/')) {
- /* Accept one wildcard as a full refname component. */
- flags &= ~REFNAME_REFSPEC_PATTERN;
- component_len = 1;
- } else {
- return -1;
- }
- }
+ component_len = check_refname_component(refname, &flags);
+ if (component_len <= 0)
+ return -1;
+
component_count++;
if (refname[component_len] == '\0')
break;
*/
static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
{
- const char *packed_refs_file;
+ char *packed_refs_file;
if (*refs->name)
- packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+ packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs");
else
- packed_refs_file = git_path("packed-refs");
+ packed_refs_file = git_pathdup("packed-refs");
if (refs->packed &&
!stat_validity_check(&refs->packed->validity, packed_refs_file))
fclose(f);
}
}
+ free(packed_refs_file);
return refs->packed;
}
return get_packed_ref_dir(get_packed_ref_cache(refs));
}
-void add_packed_ref(const char *refname, const unsigned char *sha1)
+/*
+ * Add a reference to the in-memory packed reference cache. This may
+ * only be called while the packed-refs file is locked (see
+ * lock_packed_refs()). To actually write the packed-refs file, call
+ * commit_packed_refs().
+ */
+static void add_packed_ref(const char *refname, const unsigned char *sha1)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(&ref_cache);
{
struct ref_cache *refs = dir->ref_cache;
DIR *d;
- const char *path;
struct dirent *de;
int dirnamelen = strlen(dirname);
struct strbuf refname;
+ struct strbuf path = STRBUF_INIT;
+ size_t path_baselen;
if (*refs->name)
- path = git_path_submodule(refs->name, "%s", dirname);
+ strbuf_git_path_submodule(&path, refs->name, "%s", dirname);
else
- path = git_path("%s", dirname);
+ strbuf_git_path(&path, "%s", dirname);
+ path_baselen = path.len;
- d = opendir(path);
- if (!d)
+ d = opendir(path.buf);
+ if (!d) {
+ strbuf_release(&path);
return;
+ }
strbuf_init(&refname, dirnamelen + 257);
strbuf_add(&refname, dirname, dirnamelen);
unsigned char sha1[20];
struct stat st;
int flag;
- const char *refdir;
if (de->d_name[0] == '.')
continue;
if (ends_with(de->d_name, ".lock"))
continue;
strbuf_addstr(&refname, de->d_name);
- refdir = *refs->name
- ? git_path_submodule(refs->name, "%s", refname.buf)
- : git_path("%s", refname.buf);
- if (stat(refdir, &st) < 0) {
+ strbuf_addstr(&path, de->d_name);
+ if (stat(path.buf, &st) < 0) {
; /* silently ignore */
} else if (S_ISDIR(st.st_mode)) {
strbuf_addch(&refname, '/');
create_dir_entry(refs, refname.buf,
refname.len, 1));
} else {
+ int read_ok;
+
if (*refs->name) {
hashclr(sha1);
flag = 0;
- if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) {
- hashclr(sha1);
- flag |= REF_ISBROKEN;
- }
- } else if (read_ref_full(refname.buf,
- RESOLVE_REF_READING,
- sha1, &flag)) {
+ read_ok = !resolve_gitlink_ref(refs->name,
+ refname.buf, sha1);
+ } else {
+ read_ok = !read_ref_full(refname.buf,
+ RESOLVE_REF_READING,
+ sha1, &flag);
+ }
+
+ if (!read_ok) {
hashclr(sha1);
flag |= REF_ISBROKEN;
+ } else if (is_null_sha1(sha1)) {
+ /*
+ * It is so astronomically unlikely
+ * that NULL_SHA1 is the SHA-1 of an
+ * actual object that we consider its
+ * appearance in a loose reference
+ * file to be repo corruption
+ * (probably due to a software bug).
+ */
+ flag |= REF_ISBROKEN;
}
+
if (check_refname_format(refname.buf,
REFNAME_ALLOW_ONELEVEL)) {
if (!refname_is_safe(refname.buf))
create_ref_entry(refname.buf, sha1, flag, 0));
}
strbuf_setlen(&refname, dirnamelen);
+ strbuf_setlen(&path, path_baselen);
}
strbuf_release(&refname);
+ strbuf_release(&path);
closedir(d);
}
{
int fd, len;
char buffer[128], *p;
- const char *path;
+ char *path;
if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
return -1;
path = *refs->name
- ? git_path_submodule(refs->name, "%s", refname)
- : git_path("%s", refname);
+ ? git_pathdup_submodule(refs->name, "%s", refname)
+ : git_pathdup("%s", refname);
fd = open(path, O_RDONLY);
+ free(path);
if (fd < 0)
return resolve_gitlink_packed_ref(refs, refname, sha1);
return ret;
}
-char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
+char *resolve_refdup(const char *refname, int resolve_flags,
+ unsigned char *sha1, int *flags)
{
- return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
+ return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
+ sha1, flags));
}
/* The argument to filter_refs */
int for_each_replace_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
+ return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
+ strlen(git_replace_ref_base), 0, cb_data);
}
int head_ref_namespaced(each_ref_fn fn, void *cb_data)
free(lock);
}
-/* This function should make sure errno is meaningful on error */
-static struct ref_lock *verify_lock(struct ref_lock *lock,
- const unsigned char *old_sha1, int mustexist)
+/*
+ * Verify that the reference locked by lock has the value old_sha1.
+ * Fail if the reference doesn't exist and mustexist is set. Return 0
+ * on success. On error, write an error message to err, set errno, and
+ * return a negative value.
+ */
+static int verify_lock(struct ref_lock *lock,
+ const unsigned char *old_sha1, int mustexist,
+ struct strbuf *err)
{
+ assert(err);
+
if (read_ref_full(lock->ref_name,
mustexist ? RESOLVE_REF_READING : 0,
lock->old_oid.hash, NULL)) {
int save_errno = errno;
- error("Can't verify ref %s", lock->ref_name);
- unlock_ref(lock);
+ strbuf_addf(err, "can't verify ref %s", lock->ref_name);
errno = save_errno;
- return NULL;
+ return -1;
}
if (hashcmp(lock->old_oid.hash, old_sha1)) {
- error("Ref %s is at %s but expected %s", lock->ref_name,
- oid_to_hex(&lock->old_oid), sha1_to_hex(old_sha1));
- unlock_ref(lock);
+ strbuf_addf(err, "ref %s is at %s but expected %s",
+ lock->ref_name,
+ sha1_to_hex(lock->old_oid.hash),
+ sha1_to_hex(old_sha1));
errno = EBUSY;
- return NULL;
+ return -1;
}
- return lock;
+ return 0;
}
-static int remove_empty_directories(const char *file)
+static int remove_empty_directories(struct strbuf *path)
{
- /* we want to create a file but there is a directory there;
+ /*
+ * we want to create a file but there is a directory there;
* if that is an empty directory (or a directory that contains
* only empty directories), remove them.
*/
- struct strbuf path;
- int result, save_errno;
-
- strbuf_init(&path, 20);
- strbuf_addstr(&path, file);
-
- result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
- save_errno = errno;
-
- strbuf_release(&path);
- errno = save_errno;
-
- return result;
+ return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
}
/*
unsigned int flags, int *type_p,
struct strbuf *err)
{
- const char *ref_file;
+ struct strbuf ref_file = STRBUF_INIT;
+ struct strbuf orig_ref_file = STRBUF_INIT;
const char *orig_refname = refname;
struct ref_lock *lock;
int last_errno = 0;
refname = resolve_ref_unsafe(refname, resolve_flags,
lock->old_oid.hash, &type);
if (!refname && errno == EISDIR) {
- /* we are trying to lock foo but we used to
+ /*
+ * we are trying to lock foo but we used to
* have foo/bar which now does not exist;
* it is normal for the empty directory 'foo'
* to remain.
*/
- ref_file = git_path("%s", orig_refname);
- if (remove_empty_directories(ref_file)) {
+ strbuf_git_path(&orig_ref_file, "%s", orig_refname);
+ if (remove_empty_directories(&orig_ref_file)) {
last_errno = errno;
-
if (!verify_refname_available(orig_refname, extras, skip,
get_loose_refs(&ref_cache), err))
strbuf_addf(err, "there are still refs under '%s'",
orig_refname);
-
goto error_return;
}
refname = resolve_ref_unsafe(orig_refname, resolve_flags,
}
lock->ref_name = xstrdup(refname);
lock->orig_ref_name = xstrdup(orig_refname);
- ref_file = git_path("%s", refname);
+ strbuf_git_path(&ref_file, "%s", refname);
retry:
- switch (safe_create_leading_directories_const(ref_file)) {
+ switch (safe_create_leading_directories_const(ref_file.buf)) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
/* fall through */
default:
last_errno = errno;
- strbuf_addf(err, "unable to create directory for %s", ref_file);
+ strbuf_addf(err, "unable to create directory for %s",
+ ref_file.buf);
goto error_return;
}
- if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) {
+ if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
last_errno = errno;
if (errno == ENOENT && --attempts_remaining > 0)
/*
*/
goto retry;
else {
- unable_to_lock_message(ref_file, errno, err);
+ unable_to_lock_message(ref_file.buf, errno, err);
goto error_return;
}
}
- return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+ if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) {
+ last_errno = errno;
+ goto error_return;
+ }
+ goto out;
error_return:
unlock_ref(lock);
+ lock = NULL;
+
+ out:
+ strbuf_release(&ref_file);
+ strbuf_release(&orig_ref_file);
errno = last_errno;
- return NULL;
+ return lock;
}
/*
return 0;
}
-/* This should return a meaningful errno on failure */
-int lock_packed_refs(int flags)
+/*
+ * Lock the packed-refs file for writing. Flags is passed to
+ * hold_lock_file_for_update(). Return 0 on success. On errors, set
+ * errno appropriately and return a nonzero value.
+ */
+static int lock_packed_refs(int flags)
{
static int timeout_configured = 0;
static int timeout_value = 1000;
}
/*
- * Commit the packed refs changes.
- * On error we must make sure that errno contains a meaningful value.
+ * Write the current version of the packed refs cache from memory to
+ * disk. The packed-refs file must already be locked for writing (see
+ * lock_packed_refs()). Return zero on success. On errors, set errno
+ * and return a nonzero value
*/
-int commit_packed_refs(void)
+static int commit_packed_refs(void)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(&ref_cache);
return error;
}
-void rollback_packed_refs(void)
+/*
+ * Rollback the lockfile for the packed-refs file, and discard the
+ * in-memory packed reference cache. (The packed-refs file will be
+ * read anew if it is needed again after this function is called.)
+ */
+static void rollback_packed_refs(void)
{
struct packed_ref_cache *packed_ref_cache =
get_packed_ref_cache(&ref_cache);
return 0;
}
-int repack_without_refs(struct string_list *refnames, struct strbuf *err)
+/*
+ * Rewrite the packed-refs file, omitting any refs listed in
+ * 'refnames'. On error, leave packed-refs unchanged, write an error
+ * message to 'err', and return a nonzero value.
+ *
+ * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
+ */
+static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
{
struct ref_dir *packed;
struct string_list_item *refname;
return 0;
}
-int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)
+static int is_per_worktree_ref(const char *refname)
+{
+ return !strcmp(refname, "HEAD");
+}
+
+static int is_pseudoref_syntax(const char *refname)
+{
+ const char *c;
+
+ for (c = refname; *c; c++) {
+ if (!isupper(*c) && *c != '-' && *c != '_')
+ return 0;
+ }
+
+ return 1;
+}
+
+enum ref_type ref_type(const char *refname)
+{
+ if (is_per_worktree_ref(refname))
+ return REF_TYPE_PER_WORKTREE;
+ if (is_pseudoref_syntax(refname))
+ return REF_TYPE_PSEUDOREF;
+ return REF_TYPE_NORMAL;
+}
+
+static int write_pseudoref(const char *pseudoref, const unsigned char *sha1,
+ const unsigned char *old_sha1, struct strbuf *err)
+{
+ const char *filename;
+ int fd;
+ static struct lock_file lock;
+ struct strbuf buf = STRBUF_INIT;
+ int ret = -1;
+
+ strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1));
+
+ filename = git_path("%s", pseudoref);
+ fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
+ if (fd < 0) {
+ strbuf_addf(err, "Could not open '%s' for writing: %s",
+ filename, strerror(errno));
+ return -1;
+ }
+
+ if (old_sha1) {
+ unsigned char actual_old_sha1[20];
+
+ if (read_ref(pseudoref, actual_old_sha1))
+ die("could not read ref '%s'", pseudoref);
+ if (hashcmp(actual_old_sha1, old_sha1)) {
+ strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
+ rollback_lock_file(&lock);
+ goto done;
+ }
+ }
+
+ if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
+ strbuf_addf(err, "Could not write to '%s'", filename);
+ rollback_lock_file(&lock);
+ goto done;
+ }
+
+ commit_lock_file(&lock);
+ ret = 0;
+done:
+ strbuf_release(&buf);
+ return ret;
+}
+
+static int delete_pseudoref(const char *pseudoref, const unsigned char *old_sha1)
+{
+ static struct lock_file lock;
+ const char *filename;
+
+ filename = git_path("%s", pseudoref);
+
+ if (old_sha1 && !is_null_sha1(old_sha1)) {
+ int fd;
+ unsigned char actual_old_sha1[20];
+
+ fd = hold_lock_file_for_update(&lock, filename,
+ LOCK_DIE_ON_ERROR);
+ if (fd < 0)
+ die_errno(_("Could not open '%s' for writing"), filename);
+ if (read_ref(pseudoref, actual_old_sha1))
+ die("could not read ref '%s'", pseudoref);
+ if (hashcmp(actual_old_sha1, old_sha1)) {
+ warning("Unexpected sha1 when deleting %s", pseudoref);
+ rollback_lock_file(&lock);
+ return -1;
+ }
+
+ unlink(filename);
+ rollback_lock_file(&lock);
+ } else {
+ unlink(filename);
+ }
+
+ return 0;
+}
+
+int delete_ref(const char *refname, const unsigned char *old_sha1,
+ unsigned int flags)
{
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
+ if (ref_type(refname) == REF_TYPE_PSEUDOREF)
+ return delete_pseudoref(refname, old_sha1);
+
transaction = ref_transaction_begin(&err);
if (!transaction ||
- ref_transaction_delete(transaction, refname,
- (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,
+ ref_transaction_delete(transaction, refname, old_sha1,
flags, NULL, &err) ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
return 0;
}
+int delete_refs(struct string_list *refnames)
+{
+ struct strbuf err = STRBUF_INIT;
+ int i, result = 0;
+
+ if (!refnames->nr)
+ return 0;
+
+ result = repack_without_refs(refnames, &err);
+ if (result) {
+ /*
+ * If we failed to rewrite the packed-refs file, then
+ * it is unsafe to try to remove loose refs, because
+ * doing so might expose an obsolete packed value for
+ * a reference that might even point at an object that
+ * has been garbage collected.
+ */
+ if (refnames->nr == 1)
+ error(_("could not delete reference %s: %s"),
+ refnames->items[0].string, err.buf);
+ else
+ error(_("could not delete references: %s"), err.buf);
+
+ goto out;
+ }
+
+ for (i = 0; i < refnames->nr; i++) {
+ const char *refname = refnames->items[i].string;
+
+ if (delete_ref(refname, NULL, 0))
+ result |= error(_("could not remove reference %s"), refname);
+ }
+
+out:
+ strbuf_release(&err);
+ return result;
+}
+
/*
* People using contrib's git-new-workdir have .git/logs/refs ->
* /some/other/path/.git/logs/refs, and that may live on another device.
static int rename_tmp_log(const char *newrefname)
{
int attempts_remaining = 4;
+ struct strbuf path = STRBUF_INIT;
+ int ret = -1;
retry:
- switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
+ strbuf_reset(&path);
+ strbuf_git_path(&path, "logs/%s", newrefname);
+ switch (safe_create_leading_directories_const(path.buf)) {
case SCLD_OK:
break; /* success */
case SCLD_VANISHED:
/* fall through */
default:
error("unable to create directory for %s", newrefname);
- return -1;
+ goto out;
}
- if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
+ if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
/*
* rename(a, b) when b is an existing
* directory ought to result in ISDIR, but
* Solaris 5.8 gives ENOTDIR. Sheesh.
*/
- if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+ if (remove_empty_directories(&path)) {
error("Directory not empty: logs/%s", newrefname);
- return -1;
+ goto out;
}
goto retry;
} else if (errno == ENOENT && --attempts_remaining > 0) {
} else {
error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
newrefname, strerror(errno));
- return -1;
+ goto out;
}
}
- return 0;
+ ret = 0;
+out:
+ strbuf_release(&path);
+ return ret;
}
static int rename_ref_available(const char *oldname, const char *newname)
return ret;
}
-static int write_ref_to_lockfile(struct ref_lock *lock, const unsigned char *sha1);
+static int write_ref_to_lockfile(struct ref_lock *lock,
+ const unsigned char *sha1, struct strbuf *err);
static int commit_ref_update(struct ref_lock *lock,
- const unsigned char *sha1, const char *logmsg);
+ const unsigned char *sha1, const char *logmsg,
+ int flags, struct strbuf *err);
int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
{
if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
delete_ref(newrefname, sha1, REF_NODEREF)) {
if (errno==EISDIR) {
- if (remove_empty_directories(git_path("%s", newrefname))) {
+ struct strbuf path = STRBUF_INIT;
+ int result;
+
+ strbuf_git_path(&path, "%s", newrefname);
+ result = remove_empty_directories(&path);
+ strbuf_release(&path);
+
+ if (result) {
error("Directory not empty: %s", newrefname);
goto rollback;
}
}
hashcpy(lock->old_oid.hash, orig_sha1);
- if (write_ref_to_lockfile(lock, orig_sha1) ||
- commit_ref_update(lock, orig_sha1, logmsg)) {
- error("unable to write current sha1 into %s", newrefname);
+ if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
+ commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) {
+ error("unable to write current sha1 into %s: %s", newrefname, err.buf);
+ strbuf_release(&err);
goto rollback;
}
flag = log_all_ref_updates;
log_all_ref_updates = 0;
- if (write_ref_to_lockfile(lock, orig_sha1) ||
- commit_ref_update(lock, orig_sha1, NULL))
- error("unable to write current sha1 into %s", oldrefname);
+ if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
+ commit_ref_update(lock, orig_sha1, NULL, 0, &err)) {
+ error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
+ strbuf_release(&err);
+ }
log_all_ref_updates = flag;
rollbacklog:
return cp - buf;
}
-/* This function must set a meaningful errno on failure */
-int log_ref_setup(const char *refname, struct strbuf *sb_logfile)
+static int should_autocreate_reflog(const char *refname)
+{
+ if (!log_all_ref_updates)
+ return 0;
+ return starts_with(refname, "refs/heads/") ||
+ starts_with(refname, "refs/remotes/") ||
+ starts_with(refname, "refs/notes/") ||
+ !strcmp(refname, "HEAD");
+}
+
+/*
+ * Create a reflog for a ref. If force_create = 0, the reflog will
+ * only be created for certain refs (those for which
+ * should_autocreate_reflog returns non-zero. Otherwise, create it
+ * regardless of the ref name. Fill in *err and return -1 on failure.
+ */
+static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
{
int logfd, oflags = O_APPEND | O_WRONLY;
- char *logfile;
-
- strbuf_git_path(sb_logfile, "logs/%s", refname);
- logfile = sb_logfile->buf;
- /* make sure the rest of the function can't change "logfile" */
- sb_logfile = NULL;
- if (log_all_ref_updates &&
- (starts_with(refname, "refs/heads/") ||
- starts_with(refname, "refs/remotes/") ||
- starts_with(refname, "refs/notes/") ||
- !strcmp(refname, "HEAD"))) {
- if (safe_create_leading_directories(logfile) < 0) {
- int save_errno = errno;
- error("unable to create directory for %s", logfile);
- errno = save_errno;
+
+ strbuf_git_path(logfile, "logs/%s", refname);
+ if (force_create || should_autocreate_reflog(refname)) {
+ if (safe_create_leading_directories(logfile->buf) < 0) {
+ strbuf_addf(err, "unable to create directory for %s: "
+ "%s", logfile->buf, strerror(errno));
return -1;
}
oflags |= O_CREAT;
}
- logfd = open(logfile, oflags, 0666);
+ logfd = open(logfile->buf, oflags, 0666);
if (logfd < 0) {
if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
return 0;
if (errno == EISDIR) {
if (remove_empty_directories(logfile)) {
- int save_errno = errno;
- error("There are still logs under '%s'",
- logfile);
- errno = save_errno;
+ strbuf_addf(err, "There are still logs under "
+ "'%s'", logfile->buf);
return -1;
}
- logfd = open(logfile, oflags, 0666);
+ logfd = open(logfile->buf, oflags, 0666);
}
if (logfd < 0) {
- int save_errno = errno;
- error("Unable to append to %s: %s", logfile,
- strerror(errno));
- errno = save_errno;
+ strbuf_addf(err, "unable to append to %s: %s",
+ logfile->buf, strerror(errno));
return -1;
}
}
- adjust_shared_perm(logfile);
+ adjust_shared_perm(logfile->buf);
close(logfd);
return 0;
}
+
+int safe_create_reflog(const char *refname, int force_create, struct strbuf *err)
+{
+ int ret;
+ struct strbuf sb = STRBUF_INIT;
+
+ ret = log_ref_setup(refname, &sb, err, force_create);
+ strbuf_release(&sb);
+ return ret;
+}
+
static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
const unsigned char *new_sha1,
const char *committer, const char *msg)
static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
const unsigned char *new_sha1, const char *msg,
- struct strbuf *sb_log_file)
+ struct strbuf *logfile, int flags,
+ struct strbuf *err)
{
int logfd, result, oflags = O_APPEND | O_WRONLY;
- char *log_file;
if (log_all_ref_updates < 0)
log_all_ref_updates = !is_bare_repository();
- result = log_ref_setup(refname, sb_log_file);
+ result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
+
if (result)
return result;
- log_file = sb_log_file->buf;
- /* make sure the rest of the function can't change "log_file" */
- sb_log_file = NULL;
- logfd = open(log_file, oflags);
+ logfd = open(logfile->buf, oflags);
if (logfd < 0)
return 0;
result = log_ref_write_fd(logfd, old_sha1, new_sha1,
git_committer_info(0), msg);
if (result) {
- int save_errno = errno;
+ strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+ strerror(errno));
close(logfd);
- error("Unable to append to %s", log_file);
- errno = save_errno;
return -1;
}
if (close(logfd)) {
- int save_errno = errno;
- error("Unable to append to %s", log_file);
- errno = save_errno;
+ strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
+ strerror(errno));
return -1;
}
return 0;
}
static int log_ref_write(const char *refname, const unsigned char *old_sha1,
- const unsigned char *new_sha1, const char *msg)
+ const unsigned char *new_sha1, const char *msg,
+ int flags, struct strbuf *err)
{
struct strbuf sb = STRBUF_INIT;
- int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb);
+ int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
+ err);
strbuf_release(&sb);
return ret;
}
/*
* Write sha1 into the open lockfile, then close the lockfile. On
- * errors, rollback the lockfile and set errno to reflect the problem.
+ * errors, rollback the lockfile, fill in *err and
+ * return -1.
*/
static int write_ref_to_lockfile(struct ref_lock *lock,
- const unsigned char *sha1)
+ const unsigned char *sha1, struct strbuf *err)
{
static char term = '\n';
struct object *o;
+ int fd;
o = parse_object(sha1);
if (!o) {
- error("Trying to write ref %s with nonexistent object %s",
- lock->ref_name, sha1_to_hex(sha1));
+ strbuf_addf(err,
+ "Trying to write ref %s with nonexistent object %s",
+ lock->ref_name, sha1_to_hex(sha1));
unlock_ref(lock);
- errno = EINVAL;
return -1;
}
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
- error("Trying to write non-commit object %s to branch %s",
- sha1_to_hex(sha1), lock->ref_name);
+ strbuf_addf(err,
+ "Trying to write non-commit object %s to branch %s",
+ sha1_to_hex(sha1), lock->ref_name);
unlock_ref(lock);
- errno = EINVAL;
return -1;
}
- if (write_in_full(lock->lk->fd, sha1_to_hex(sha1), 40) != 40 ||
- write_in_full(lock->lk->fd, &term, 1) != 1 ||
+ fd = get_lock_file_fd(lock->lk);
+ if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 ||
+ write_in_full(fd, &term, 1) != 1 ||
close_ref(lock) < 0) {
- int save_errno = errno;
- error("Couldn't write %s", get_lock_file_path(lock->lk));
+ strbuf_addf(err,
- "Couldn't write %s", lock->lk->filename.buf);
++ "Couldn't write %s", get_lock_file_path(lock->lk));
unlock_ref(lock);
- errno = save_errno;
return -1;
}
return 0;
* necessary, using the specified lockmsg (which can be NULL).
*/
static int commit_ref_update(struct ref_lock *lock,
- const unsigned char *sha1, const char *logmsg)
+ const unsigned char *sha1, const char *logmsg,
+ int flags, struct strbuf *err)
{
clear_loose_ref_cache(&ref_cache);
- if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg) < 0 ||
+ if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 ||
(strcmp(lock->ref_name, lock->orig_ref_name) &&
- log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg) < 0)) {
+ log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) {
+ char *old_msg = strbuf_detach(err, NULL);
+ strbuf_addf(err, "Cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
unlock_ref(lock);
return -1;
}
head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
head_sha1, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
- !strcmp(head_ref, lock->ref_name))
- log_ref_write("HEAD", lock->old_oid.hash, sha1, logmsg);
+ !strcmp(head_ref, lock->ref_name)) {
+ struct strbuf log_err = STRBUF_INIT;
+ if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
+ logmsg, 0, &log_err)) {
+ error("%s", log_err.buf);
+ strbuf_release(&log_err);
+ }
+ }
}
if (commit_ref(lock)) {
error("Couldn't set %s", lock->ref_name);
unlock_ref(lock);
return -1;
}
+
unlock_ref(lock);
return 0;
}
int create_symref(const char *ref_target, const char *refs_heads_master,
const char *logmsg)
{
- const char *lockpath;
+ char *lockpath = NULL;
char ref[1000];
int fd, len, written;
char *git_HEAD = git_pathdup("%s", ref_target);
unsigned char old_sha1[20], new_sha1[20];
+ struct strbuf err = STRBUF_INIT;
if (logmsg && read_ref(ref_target, old_sha1))
hashclr(old_sha1);
error("refname too long: %s", refs_heads_master);
goto error_free_return;
}
- lockpath = mkpath("%s.lock", git_HEAD);
+ lockpath = mkpathdup("%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);
error_unlink_return:
unlink_or_warn(lockpath);
error_free_return:
+ free(lockpath);
free(git_HEAD);
return -1;
}
+ free(lockpath);
#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);
+ if (logmsg && !read_ref(refs_heads_master, new_sha1) &&
+ log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) {
+ error("%s", err.buf);
+ strbuf_release(&err);
+ }
free(git_HEAD);
return 0;
hashcpy(cb->sha1, nsha1);
if (hashcmp(cb->osha1, nsha1))
warning("Log for ref %s has gap after %s.",
- cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822));
+ cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
}
else if (cb->date == cb->at_time)
hashcpy(cb->sha1, nsha1);
else if (hashcmp(nsha1, cb->sha1))
warning("Log for ref %s unexpectedly ended on %s.",
cb->refname, show_date(cb->date, cb->tz,
- DATE_RFC2822));
+ DATE_MODE(RFC2822)));
hashcpy(cb->osha1, osha1);
hashcpy(cb->nsha1, nsha1);
cb->found_it = 1;
const unsigned char *new_sha1, const unsigned char *old_sha1,
unsigned int flags, enum action_on_err onerr)
{
- struct ref_transaction *t;
+ struct ref_transaction *t = NULL;
struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- t = ref_transaction_begin(&err);
- if (!t ||
- ref_transaction_update(t, refname, new_sha1, old_sha1,
- flags, msg, &err) ||
- ref_transaction_commit(t, &err)) {
+ if (ref_type(refname) == REF_TYPE_PSEUDOREF) {
+ ret = write_pseudoref(refname, new_sha1, old_sha1, &err);
+ } else {
+ t = ref_transaction_begin(&err);
+ if (!t ||
+ ref_transaction_update(t, refname, new_sha1, old_sha1,
+ flags, msg, &err) ||
+ ref_transaction_commit(t, &err)) {
+ ret = 1;
+ ref_transaction_free(t);
+ }
+ }
+ if (ret) {
const char *str = "update_ref failed for ref '%s': %s";
- ref_transaction_free(t);
switch (onerr) {
case UPDATE_REFS_MSG_ON_ERR:
error(str, refname, err.buf);
return 1;
}
strbuf_release(&err);
- ref_transaction_free(t);
+ if (t)
+ ref_transaction_free(t);
return 0;
}
? TRANSACTION_NAME_CONFLICT
: TRANSACTION_GENERIC_ERROR;
reason = strbuf_detach(err, NULL);
- strbuf_addf(err, "Cannot lock ref '%s': %s",
+ strbuf_addf(err, "cannot lock ref '%s': %s",
update->refname, reason);
free(reason);
goto cleanup;
* value, so we don't need to write it.
*/
} else if (write_ref_to_lockfile(update->lock,
- update->new_sha1)) {
+ update->new_sha1,
+ err)) {
+ char *write_err = strbuf_detach(err, NULL);
+
/*
* The lock was freed upon failure of
* write_ref_to_lockfile():
*/
update->lock = NULL;
- strbuf_addf(err, "Cannot update the ref '%s'.",
- update->refname);
+ strbuf_addf(err,
+ "cannot update the ref '%s': %s",
+ update->refname, write_err);
+ free(write_err);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
} else {
if (update->flags & REF_NEEDS_COMMIT) {
if (commit_ref_update(update->lock,
- update->new_sha1, update->msg)) {
+ update->new_sha1, update->msg,
+ update->flags, err)) {
/* freed by commit_ref_update(): */
update->lock = NULL;
- strbuf_addf(err, "Cannot update the ref '%s'.",
- update->refname);
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
} else {
return ret;
}
+static int ref_present(const char *refname,
+ const struct object_id *oid, int flags, void *cb_data)
+{
+ struct string_list *affected_refnames = cb_data;
+
+ return string_list_has_string(affected_refnames, refname);
+}
+
+int initial_ref_transaction_commit(struct ref_transaction *transaction,
+ struct strbuf *err)
+{
+ struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
+ struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
+ int ret = 0, i;
+ int n = transaction->nr;
+ struct ref_update **updates = transaction->updates;
+ struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+
+ assert(err);
+
+ if (transaction->state != REF_TRANSACTION_OPEN)
+ die("BUG: commit called for transaction that is not open");
+
+ /* Fail if a refname appears more than once in the transaction: */
+ for (i = 0; i < n; i++)
+ string_list_append(&affected_refnames, updates[i]->refname);
+ string_list_sort(&affected_refnames);
+ if (ref_update_reject_duplicates(&affected_refnames, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+ /*
+ * It's really undefined to call this function in an active
+ * repository or when there are existing references: we are
+ * only locking and changing packed-refs, so (1) any
+ * simultaneous processes might try to change a reference at
+ * the same time we do, and (2) any existing loose versions of
+ * the references that we are setting would have precedence
+ * over our values. But some remote helpers create the remote
+ * "HEAD" and "master" branches before calling this function,
+ * so here we really only check that none of the references
+ * that we are creating already exists.
+ */
+ if (for_each_rawref(ref_present, &affected_refnames))
+ die("BUG: initial ref transaction called with existing refs");
+
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if ((update->flags & REF_HAVE_OLD) &&
+ !is_null_sha1(update->old_sha1))
+ die("BUG: initial ref transaction with old_sha1 set");
+ if (verify_refname_available(update->refname,
+ &affected_refnames, NULL,
+ loose_refs, err) ||
+ verify_refname_available(update->refname,
+ &affected_refnames, NULL,
+ packed_refs, err)) {
+ ret = TRANSACTION_NAME_CONFLICT;
+ goto cleanup;
+ }
+ }
+
+ if (lock_packed_refs(0)) {
+ strbuf_addf(err, "unable to lock packed-refs file: %s",
+ strerror(errno));
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+ for (i = 0; i < n; i++) {
+ struct ref_update *update = updates[i];
+
+ if ((update->flags & REF_HAVE_NEW) &&
+ !is_null_sha1(update->new_sha1))
+ add_packed_ref(update->refname, update->new_sha1);
+ }
+
+ if (commit_packed_refs()) {
+ strbuf_addf(err, "unable to commit packed-refs file: %s",
+ strerror(errno));
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto cleanup;
+ }
+
+cleanup:
+ transaction->state = REF_TRANSACTION_CLOSED;
+ string_list_clear(&affected_refnames, 0);
+ return ret;
+}
+
char *shorten_unambiguous_ref(const char *refname, int strict)
{
int i;
int ref_is_hidden(const char *refname)
{
- struct string_list_item *item;
+ int i;
if (!hide_refs)
return 0;
- for_each_string_list_item(item, hide_refs) {
+ for (i = hide_refs->nr - 1; i >= 0; i--) {
+ const char *match = hide_refs->items[i].string;
+ int neg = 0;
int len;
- if (!starts_with(refname, item->string))
+
+ if (*match == '!') {
+ neg = 1;
+ match++;
+ }
+
+ if (!starts_with(refname, match))
continue;
- len = strlen(item->string);
+ len = strlen(match);
if (!refname[len] || refname[len] == '/')
- return 1;
+ return !neg;
}
return 0;
}
cb.newlog = fdopen_lock_file(&reflog_lock, "w");
if (!cb.newlog) {
error("cannot fdopen %s (%s)",
- reflog_lock.filename.buf, strerror(errno));
+ get_lock_file_path(&reflog_lock), strerror(errno));
goto failure;
}
}
status |= error("couldn't write %s: %s", log_file,
strerror(errno));
} else if (update &&
- (write_in_full(lock->lk->fd,
+ (write_in_full(get_lock_file_fd(lock->lk),
sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
- write_str_in_full(lock->lk->fd, "\n") != 1 ||
- close_ref(lock) < 0)) {
+ write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
+ close_ref(lock) < 0)) {
status |= error("couldn't write %s",
- lock->lk->filename.buf);
+ get_lock_file_path(lock->lk));
rollback_lock_file(&reflog_lock);
} else if (commit_lock_file(&reflog_lock)) {
status |= error("unable to commit reflog '%s' (%s)",
#include "cache.h"
+ #include "tempfile.h"
#include "lockfile.h"
#include "commit.h"
#include "tag.h"
return is_shallow;
if (!path)
- path = git_path("shallow");
+ path = git_path_shallow();
/*
* fetch-pack sets '--shallow-file ""' as an indicator that no
* shallow file should be used. We could just open it and it
if (is_shallow == -1)
die("BUG: shallow must be initialized by now");
- if (!stat_validity_check(&shallow_stat, git_path("shallow")))
+ if (!stat_validity_check(&shallow_stat, git_path_shallow()))
die("shallow file has changed since we read it");
}
return write_shallow_commits_1(out, use_pack_protocol, extra, 0);
}
- static struct strbuf temporary_shallow = STRBUF_INIT;
-
- static void remove_temporary_shallow(void)
- {
- if (temporary_shallow.len) {
- unlink_or_warn(temporary_shallow.buf);
- strbuf_reset(&temporary_shallow);
- }
- }
-
- static void remove_temporary_shallow_on_signal(int signo)
- {
- remove_temporary_shallow();
- sigchain_pop(signo);
- raise(signo);
- }
+ static struct tempfile temporary_shallow;
const char *setup_temporary_shallow(const struct sha1_array *extra)
{
struct strbuf sb = STRBUF_INIT;
int fd;
- if (temporary_shallow.len)
- die("BUG: attempt to create two temporary shallow files");
-
if (write_shallow_commits(&sb, 0, extra)) {
- strbuf_addstr(&temporary_shallow, git_path("shallow_XXXXXX"));
- fd = xmkstemp(temporary_shallow.buf);
-
- atexit(remove_temporary_shallow);
- sigchain_push_common(remove_temporary_shallow_on_signal);
+ fd = xmks_tempfile(&temporary_shallow, git_path("shallow_XXXXXX"));
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
die_errno("failed to write to %s",
- temporary_shallow.buf);
- close(fd);
+ get_tempfile_path(&temporary_shallow));
+ close_tempfile(&temporary_shallow);
strbuf_release(&sb);
- return temporary_shallow.buf;
+ return get_tempfile_path(&temporary_shallow);
}
/*
* is_repository_shallow() sees empty string as "no shallow
* file".
*/
- return temporary_shallow.buf;
+ return get_tempfile_path(&temporary_shallow);
}
void setup_alternate_shallow(struct lock_file *shallow_lock,
struct strbuf sb = STRBUF_INIT;
int fd;
- fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
+ fd = hold_lock_file_for_update(shallow_lock, git_path_shallow(),
LOCK_DIE_ON_ERROR);
check_shallow_file_for_update();
if (write_shallow_commits(&sb, 0, extra)) {
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
die_errno("failed to write to %s",
- shallow_lock->filename.buf);
- *alternate_shallow_file = shallow_lock->filename.buf;
+ get_lock_file_path(shallow_lock));
+ *alternate_shallow_file = get_lock_file_path(shallow_lock);
} else
/*
* is_repository_shallow() sees empty string as "no
strbuf_release(&sb);
return;
}
- fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
+ fd = hold_lock_file_for_update(&shallow_lock, git_path_shallow(),
LOCK_DIE_ON_ERROR);
check_shallow_file_for_update();
if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
if (write_in_full(fd, sb.buf, sb.len) != sb.len)
die_errno("failed to write to %s",
- shallow_lock.filename.buf);
+ get_lock_file_path(&shallow_lock));
commit_lock_file(&shallow_lock);
} else {
- unlink(git_path("shallow"));
+ unlink(git_path_shallow());
rollback_lock_file(&shallow_lock);
}
strbuf_release(&sb);