From: Junio C Hamano Date: Fri, 2 Nov 2018 02:04:53 +0000 (+0900) Subject: Merge branch 'ag/rebase-i-in-c' X-Git-Tag: v2.20.0-rc0~94 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/b49ef560ed66449d24a3fdfe25972c390bb44951?ds=inline;hp=-c Merge branch 'ag/rebase-i-in-c' Rewrite of the remaining "rebase -i" machinery in C. * ag/rebase-i-in-c: rebase -i: move rebase--helper modes to rebase--interactive rebase -i: remove git-rebase--interactive.sh rebase--interactive2: rewrite the submodes of interactive rebase in C rebase -i: implement the main part of interactive rebase as a builtin rebase -i: rewrite init_basic_state() in C rebase -i: rewrite write_basic_state() in C rebase -i: rewrite the rest of init_revisions_and_shortrevisions() in C rebase -i: implement the logic to initialize $revisions in C rebase -i: remove unused modes and functions rebase -i: rewrite complete_action() in C t3404: todo list with commented-out commands only aborts sequencer: change the way skip_unnecessary_picks() returns its result sequencer: refactor append_todo_help() to write its message to a buffer rebase -i: rewrite checkout_onto() in C rebase -i: rewrite setup_reflog_action() in C sequencer: add a new function to silence a command, except if it fails rebase -i: rewrite the edit-todo functionality in C editor: add a function to launch the sequence editor rebase -i: rewrite append_todo_help() in C sequencer: make three functions and an enum from sequencer.c public --- b49ef560ed66449d24a3fdfe25972c390bb44951 diff --combined .gitignore index 4d5de166e8,406f26d050..0d77ea5894 --- a/.gitignore +++ b/.gitignore @@@ -1,6 -1,3 +1,6 @@@ +/fuzz_corpora +/fuzz-pack-headers +/fuzz-pack-idx /GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS @@@ -81,7 -78,6 +81,7 @@@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote @@@ -103,9 -99,8 +103,9 @@@ /git-mergetool--lib /git-mktag /git-mktree -/git-name-rev +/git-multi-pack-index /git-mv +/git-name-rev /git-notes /git-p4 /git-pack-redundant @@@ -118,12 -113,9 +118,11 @@@ /git-pull /git-push /git-quiltimport +/git-range-diff /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common - /git-rebase--helper /git-rebase--interactive /git-rebase--merge /git-rebase--preserve-merges @@@ -214,7 -206,6 +213,7 @@@ /config.mak.autogen /config.mak.append /configure +/.vscode/ /tags /TAGS /cscope* diff --combined Makefile index 95b93c709d,ca3a0888dd..bbfbb4292d --- a/Makefile +++ b/Makefile @@@ -400,7 -400,7 +400,7 @@@ all: # (defaults to "man") if you want to have a different default when # "git help" is called without a parameter specifying the format. # -# Define TEST_GIT_INDEX_VERSION to 2, 3 or 4 to run the test suite +# Define GIT_TEST_INDEX_VERSION to 2, 3 or 4 to run the test suite # with a different indexfile format version. If it isn't set the index # file format used is index-v[23]. # @@@ -484,11 -484,6 +484,11 @@@ # The DEVELOPER mode enables -Wextra with a few exceptions. By # setting this flag the exceptions are removed, and all of # -Wextra is used. +# +# pedantic: +# +# Enable -pedantic compilation. This also disables +# USE_PARENS_AROUND_GETTEXT_N to produce only relevant warnings. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@@ -569,7 -564,7 +569,7 @@@ SPATCH = spatc export TCL_PATH TCLTK_PATH SPARSE_FLAGS = -SPATCH_FLAGS = --all-includes +SPATCH_FLAGS = --all-includes --patch . @@@ -590,8 -585,6 +590,8 @@@ XDIFF_OBJS VCSSVN_OBJS = GENERATED_H = EXTRA_CPPFLAGS = +FUZZ_OBJS = +FUZZ_PROGRAMS = LIB_OBJS = PROGRAM_OBJS = PROGRAMS = @@@ -616,7 -609,7 +616,7 @@@ SCRIPT_SH += git-merge-one-file.s SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@@ -626,8 -619,6 +626,7 @@@ SCRIPT_SH += git-web--browse.s SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common - SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge SCRIPT_LIB += git-sh-setup @@@ -685,14 -676,6 +684,14 @@@ SCRIPTS = $(SCRIPT_SH_INS) ETAGS_TARGET = TAGS +FUZZ_OBJS += fuzz-pack-headers.o +FUZZ_OBJS += fuzz-pack-idx.o + +# Always build fuzz objects even if not testing, to prevent bit-rot. +all:: $(FUZZ_OBJS) + +FUZZ_PROGRAMS += $(patsubst %.o,%,$(FUZZ_OBJS)) + # Empty... EXTRA_PROGRAMS = @@@ -720,34 -703,26 +719,34 @@@ TEST_BUILTINS_OBJS += test-date. TEST_BUILTINS_OBJS += test-delta.o TEST_BUILTINS_OBJS += test-drop-caches.o TEST_BUILTINS_OBJS += test-dump-cache-tree.o +TEST_BUILTINS_OBJS += test-dump-fsmonitor.o TEST_BUILTINS_OBJS += test-dump-split-index.o +TEST_BUILTINS_OBJS += test-dump-untracked-cache.o TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-genrandom.o TEST_BUILTINS_OBJS += test-hashmap.o TEST_BUILTINS_OBJS += test-index-version.o +TEST_BUILTINS_OBJS += test-json-writer.o TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o TEST_BUILTINS_OBJS += test-match-trees.o TEST_BUILTINS_OBJS += test-mergesort.o TEST_BUILTINS_OBJS += test-mktemp.o TEST_BUILTINS_OBJS += test-online-cpus.o +TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-path-utils.o +TEST_BUILTINS_OBJS += test-pkt-line.o TEST_BUILTINS_OBJS += test-prio-queue.o +TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o +TEST_BUILTINS_OBJS += test-read-midx.o TEST_BUILTINS_OBJS += test-ref-store.o TEST_BUILTINS_OBJS += test-regex.o +TEST_BUILTINS_OBJS += test-repository.o TEST_BUILTINS_OBJS += test-revision-walking.o TEST_BUILTINS_OBJS += test-run-command.o TEST_BUILTINS_OBJS += test-scrap-cache-tree.o -TEST_BUILTINS_OBJS += test-sha1-array.o TEST_BUILTINS_OBJS += test-sha1.o +TEST_BUILTINS_OBJS += test-sha1-array.o TEST_BUILTINS_OBJS += test-sigchain.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o @@@ -755,13 -730,14 +754,13 @@@ TEST_BUILTINS_OBJS += test-submodule-co TEST_BUILTINS_OBJS += test-subprocess.o TEST_BUILTINS_OBJS += test-urlmatch-normalization.o TEST_BUILTINS_OBJS += test-wildmatch.o +TEST_BUILTINS_OBJS += test-windows-named-pipe.o TEST_BUILTINS_OBJS += test-write-cache.o -TEST_PROGRAMS_NEED_X += test-dump-fsmonitor -TEST_PROGRAMS_NEED_X += test-dump-untracked-cache +# Do not add more tests here unless they have extra dependencies. Add +# them in TEST_BUILTINS_OBJS above. TEST_PROGRAMS_NEED_X += test-fake-ssh TEST_PROGRAMS_NEED_X += test-line-buffer -TEST_PROGRAMS_NEED_X += test-parse-options -TEST_PROGRAMS_NEED_X += test-pkt-line TEST_PROGRAMS_NEED_X += test-svn-fe TEST_PROGRAMS_NEED_X += test-tool @@@ -851,7 -827,6 +850,7 @@@ LIB_OBJS += column. LIB_OBJS += combine-diff.o LIB_OBJS += commit.o LIB_OBJS += commit-graph.o +LIB_OBJS += commit-reach.o LIB_OBJS += compat/obstack.o LIB_OBJS += compat/terminal.o LIB_OBJS += config.o @@@ -864,7 -839,6 +863,7 @@@ LIB_OBJS += csum-file. LIB_OBJS += ctype.o LIB_OBJS += date.o LIB_OBJS += decorate.o +LIB_OBJS += delta-islands.o LIB_OBJS += diffcore-break.o LIB_OBJS += diffcore-delta.o LIB_OBJS += diffcore-order.o @@@ -884,7 -858,6 +883,7 @@@ LIB_OBJS += ewah/ewah_bitmap. LIB_OBJS += ewah/ewah_io.o LIB_OBJS += ewah/ewah_rlw.o LIB_OBJS += exec-cmd.o +LIB_OBJS += fetch-negotiator.o LIB_OBJS += fetch-object.o LIB_OBJS += fetch-pack.o LIB_OBJS += fsck.o @@@ -894,12 -867,9 +893,12 @@@ LIB_OBJS += gpg-interface. LIB_OBJS += graph.o LIB_OBJS += grep.o LIB_OBJS += hashmap.o +LIB_OBJS += linear-assignment.o LIB_OBJS += help.o LIB_OBJS += hex.o LIB_OBJS += ident.o +LIB_OBJS += interdiff.o +LIB_OBJS += json-writer.o LIB_OBJS += kwset.o LIB_OBJS += levenshtein.o LIB_OBJS += line-log.o @@@ -919,10 -889,7 +918,10 @@@ LIB_OBJS += merge. LIB_OBJS += merge-blobs.o LIB_OBJS += merge-recursive.o LIB_OBJS += mergesort.o +LIB_OBJS += midx.o LIB_OBJS += name-hash.o +LIB_OBJS += negotiator/default.o +LIB_OBJS += negotiator/skipping.o LIB_OBJS += notes.o LIB_OBJS += notes-cache.o LIB_OBJS += notes-merge.o @@@ -952,9 -919,9 +951,10 @@@ LIB_OBJS += progress. LIB_OBJS += prompt.o LIB_OBJS += protocol.o LIB_OBJS += quote.o +LIB_OBJS += range-diff.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o + LIB_OBJS += rebase-interactive.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += refs/files-backend.o @@@ -1080,7 -1047,6 +1080,7 @@@ BUILTIN_OBJS += builtin/merge-recursive BUILTIN_OBJS += builtin/merge-tree.o BUILTIN_OBJS += builtin/mktag.o BUILTIN_OBJS += builtin/mktree.o +BUILTIN_OBJS += builtin/multi-pack-index.o BUILTIN_OBJS += builtin/mv.o BUILTIN_OBJS += builtin/name-rev.o BUILTIN_OBJS += builtin/notes.o @@@ -1092,10 -1058,8 +1092,10 @@@ BUILTIN_OBJS += builtin/prune-packed. BUILTIN_OBJS += builtin/prune.o BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o +BUILTIN_OBJS += builtin/range-diff.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o - BUILTIN_OBJS += builtin/rebase--helper.o + BUILTIN_OBJS += builtin/rebase--interactive.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o BUILTIN_OBJS += builtin/remote.o @@@ -1808,7 -1772,6 +1808,7 @@@ ifndef QUIET_MSGFMT = @echo ' ' MSGFMT $@; QUIET_GCOV = @echo ' ' GCOV $@; QUIET_SP = @echo ' ' SP $<; + QUIET_HDR = @echo ' ' HDR $<; QUIET_RC = @echo ' ' RC $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ @@@ -2070,7 -2033,7 +2070,7 @@@ $(BUILT_INS): git$ command-list.h: generate-cmdlist.sh command-list.txt -command-list.h: $(wildcard Documentation/git*.txt) +command-list.h: $(wildcard Documentation/git*.txt) Documentation/*config.txt $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh command-list.txt >$@+ && mv $@+ $@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):$(GIT_VERSION):\ @@@ -2265,7 -2228,6 +2265,7 @@@ TEST_OBJS := $(patsubst %$X,%.o,$(TEST_ OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ $(XDIFF_OBJS) \ $(VCSSVN_OBJS) \ + $(FUZZ_OBJS) \ common-main.o \ git.o ifndef NO_CURL @@@ -2436,7 -2398,6 +2436,6 @@@ XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS LOCALIZED_C = $(C_OBJ:o=c) $(LIB_H) $(GENERATED_H) LOCALIZED_SH = $(SCRIPT_SH) LOCALIZED_SH += git-parse-remote.sh - LOCALIZED_SH += git-rebase--interactive.sh LOCALIZED_SH += git-rebase--preserve-merges.sh LOCALIZED_SH += git-sh-setup.sh LOCALIZED_PERL = $(SCRIPT_PERL) @@@ -2623,8 -2584,8 +2622,8 @@@ endi ifdef GIT_INTEROP_MAKE_OPTS @echo GIT_INTEROP_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_INTEROP_MAKE_OPTS)))'\' >>$@+ endif -ifdef TEST_GIT_INDEX_VERSION - @echo TEST_GIT_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(TEST_GIT_INDEX_VERSION)))'\' >>$@+ +ifdef GIT_TEST_INDEX_VERSION + @echo GIT_TEST_INDEX_VERSION=\''$(subst ','\'',$(subst ','\'',$(GIT_TEST_INDEX_VERSION)))'\' >>$@+ endif @if cmp $@+ $@ >/dev/null 2>&1; then $(RM) $@+; else mv $@+ $@; fi @@@ -2692,17 -2653,6 +2691,17 @@@ $(SP_OBJ): %.sp: %.c GIT-CFLAGS FORC .PHONY: sparse $(SP_OBJ) sparse: $(SP_OBJ) +GEN_HDRS := command-list.h unicode-width.h +EXCEPT_HDRS := $(GEN_HDRS) compat% xdiff% +CHK_HDRS = $(filter-out $(EXCEPT_HDRS),$(patsubst ./%,%,$(LIB_H))) +HCO = $(patsubst %.h,%.hco,$(CHK_HDRS)) + +$(HCO): %.hco: %.h FORCE + $(QUIET_HDR)$(CC) -include git-compat-util.h -I. -o /dev/null -c -xc $< + +.PHONY: hdr-check $(HCO) +hdr-check: $(HCO) + .PHONY: style style: git clang-format --style file --diff --extensions c,h @@@ -2718,16 -2668,10 +2717,16 @@@ check: command-list. fi C_SOURCES = $(patsubst %.o,%.c,$(C_OBJ)) -%.cocci.patch: %.cocci $(C_SOURCES) +ifdef DC_SHA1_SUBMODULE +COCCI_SOURCES = $(filter-out sha1collisiondetection/%,$(C_SOURCES)) +else +COCCI_SOURCES = $(filter-out sha1dc/%,$(C_SOURCES)) +endif + +%.cocci.patch: %.cocci $(COCCI_SOURCES) @echo ' ' SPATCH $<; \ ret=0; \ - for f in $(C_SOURCES); do \ + for f in $(COCCI_SOURCES); do \ $(SPATCH) --sp-file $< $$f $(SPATCH_FLAGS) || \ { ret=$$?; break; }; \ done >$@+ 2>$@.log; \ @@@ -2741,9 -2685,7 +2740,9 @@@ then \ echo ' ' SPATCH result: $@; \ fi -coccicheck: $(patsubst %.cocci,%.cocci.patch,$(wildcard contrib/coccinelle/*.cocci)) +coccicheck: $(addsuffix .patch,$(wildcard contrib/coccinelle/*.cocci)) + +.PHONY: coccicheck ### Installation rules @@@ -2955,22 -2897,19 +2954,22 @@@ profile-clean $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) -clean: profile-clean coverage-clean +cocciclean: + $(RM) contrib/coccinelle/*.cocci.patch* + +clean: profile-clean coverage-clean cocciclean $(RM) *.res $(RM) $(OBJECTS) $(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(NO_INSTALL) + $(RM) $(FUZZ_PROGRAMS) $(RM) -r bin-wrappers $(dep_dirs) $(RM) -r po/build/ $(RM) *.pyc *.pyo */*.pyc */*.pyo command-list.h $(ETAGS_TARGET) tags cscope* $(RM) -r $(GIT_TARNAME) .doc-tmp-dir $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(RM) $(htmldocs).tar.gz $(manpages).tar.gz - $(RM) contrib/coccinelle/*.cocci.patch* $(MAKE) -C Documentation/ clean ifndef NO_PERL $(MAKE) -C gitweb clean @@@ -2986,7 -2925,7 +2985,7 @@@ endi $(RM) GIT-USER-AGENT GIT-PREFIX $(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS -.PHONY: all install profile-clean clean strip +.PHONY: all install profile-clean cocciclean clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell .PHONY: FORCE cscope @@@ -3089,24 -3028,3 +3088,24 @@@ cover_db: coverage-repor cover_db_html: cover_db cover -report html -outputdir cover_db_html cover_db + +### Fuzz testing +# +# Building fuzz targets generally requires a special set of compiler flags that +# are not necessarily appropriate for general builds, and that vary greatly +# depending on the compiler version used. +# +# An example command to build against libFuzzer from LLVM 4.0.0: +# +# make CC=clang CXX=clang++ \ +# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \ +# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \ +# fuzz-all +# +.PHONY: fuzz-all + +$(FUZZ_PROGRAMS): all + $(QUIET_LINK)$(CXX) $(CFLAGS) $(LIB_OBJS) $(BUILTIN_OBJS) \ + $(XDIFF_OBJS) $(EXTLIBS) git.o $@.o $(LIB_FUZZING_ENGINE) -o $@ + +fuzz-all: $(FUZZ_PROGRAMS) diff --combined builtin.h index 6fb66f5ba4,7feb689d87..6538932e99 --- a/builtin.h +++ b/builtin.h @@@ -191,7 -191,6 +191,7 @@@ extern int cmd_merge_recursive(int argc extern int cmd_merge_tree(int argc, const char **argv, const char *prefix); extern int cmd_mktag(int argc, const char **argv, const char *prefix); extern int cmd_mktree(int argc, const char **argv, const char *prefix); +extern int cmd_multi_pack_index(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_notes(int argc, const char **argv, const char *prefix); @@@ -202,10 -201,8 +202,10 @@@ extern int cmd_prune(int argc, const ch extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); +extern int cmd_range_diff(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); - extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); + extern int cmd_rebase__interactive(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_remote(int argc, const char **argv, const char *prefix); diff --combined cache.h index f7fabdde8f,d70ae49ca2..8b1ee42ae9 --- a/cache.h +++ b/cache.h @@@ -15,7 -15,6 +15,7 @@@ #include "path.h" #include "sha1-array.h" #include "repository.h" +#include "mem-pool.h" #include typedef struct git_zstream { @@@ -157,7 -156,6 +157,7 @@@ struct cache_entry struct stat_data ce_stat_data; unsigned int ce_mode; unsigned int ce_flags; + unsigned int mem_pool_allocated; unsigned int ce_namelen; unsigned int index; /* for link extension */ struct object_id oid; @@@ -220,7 -218,6 +220,7 @@@ /* Forward structure decls */ struct pathspec; struct child_process; +struct tree; /* * Copy the sha1 and stat state of a cache entry from one to @@@ -230,7 -227,6 +230,7 @@@ static inline void copy_cache_entry(str const struct cache_entry *src) { unsigned int state = dst->ce_flags & CE_HASHED; + int mem_pool_allocated = dst->mem_pool_allocated; /* Don't copy hash chain and name */ memcpy(&dst->ce_stat_data, &src->ce_stat_data, @@@ -239,9 -235,6 +239,9 @@@ /* Restore the hash state */ dst->ce_flags = (dst->ce_flags & ~CE_HASHED) | state; + + /* Restore the mem_pool_allocated flag */ + dst->mem_pool_allocated = mem_pool_allocated; } static inline unsigned create_ce_flags(unsigned stage) @@@ -335,7 -328,6 +335,7 @@@ struct index_state struct untracked_cache *untracked; uint64_t fsmonitor_last_update; struct ewah_bitmap *fsmonitor_dirty; + struct mem_pool *ce_mem_pool; }; extern struct index_state the_index; @@@ -347,60 -339,6 +347,60 @@@ extern void remove_name_hash(struct ind extern void free_name_hash(struct index_state *istate); +/* Cache entry creation and cleanup */ + +/* + * Create cache_entry intended for use in the specified index. Caller + * is responsible for discarding the cache_entry with + * `discard_cache_entry`. + */ +struct cache_entry *make_cache_entry(struct index_state *istate, + unsigned int mode, + const struct object_id *oid, + const char *path, + int stage, + unsigned int refresh_options); + +struct cache_entry *make_empty_cache_entry(struct index_state *istate, + size_t name_len); + +/* + * Create a cache_entry that is not intended to be added to an index. + * Caller is responsible for discarding the cache_entry + * with `discard_cache_entry`. + */ +struct cache_entry *make_transient_cache_entry(unsigned int mode, + const struct object_id *oid, + const char *path, + int stage); + +struct cache_entry *make_empty_transient_cache_entry(size_t name_len); + +/* + * Discard cache entry. + */ +void discard_cache_entry(struct cache_entry *ce); + +/* + * Check configuration if we should perform extra validation on cache + * entries. + */ +int should_validate_cache_entries(void); + +/* + * Duplicate a cache_entry. Allocate memory for the new entry from a + * memory_pool. Takes into account cache_entry fields that are meant + * for managing the underlying memory allocation of the cache_entry. + */ +struct cache_entry *dup_cache_entry(const struct cache_entry *ce, struct index_state *istate); + +/* + * Validate the cache entries in the index. This is an internal + * consistency check that the cache_entry structs are allocated from + * the expected memory pool. + */ +void validate_cache_entries(const struct index_state *istate); + #ifndef NO_THE_INDEX_COMPATIBILITY_MACROS #define active_cache (the_index.cache) #define active_nr (the_index.cache_nr) @@@ -410,7 -348,7 +410,7 @@@ #define read_cache() read_index(&the_index) #define read_cache_from(path) read_index_from(&the_index, (path), (get_git_dir())) -#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec)) +#define read_cache_preload(pathspec) read_index_preload(&the_index, (pathspec), 0) #define is_cache_unborn() is_index_unborn(&the_index) #define read_cache_unmerged() read_index_unmerged(&the_index) #define discard_cache() discard_index(&the_index) @@@ -659,9 -597,7 +659,9 @@@ extern int daemonize(void) /* Initialize and use the cache information */ struct lock_file; extern int read_index(struct index_state *); -extern int read_index_preload(struct index_state *, const struct pathspec *pathspec); +extern int read_index_preload(struct index_state *, + const struct pathspec *pathspec, + unsigned int refresh_flags); extern int do_read_index(struct index_state *istate, const char *path, int must_exist); /* for testting only! */ extern int read_index_from(struct index_state *, const char *path, @@@ -699,15 -635,12 +699,15 @@@ extern void move_index_extensions(struc extern int unmerged_index(const struct index_state *); /** - * 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. + * Returns 1 if istate differs from tree, 0 otherwise. If tree is NULL, + * compares istate to HEAD. If tree is NULL and on an unborn branch, + * returns 1 if there are entries in istate, 0 otherwise. If an strbuf is + * provided, the space-separated list of files that differ will be appended + * to it. */ -extern int index_has_changes(struct strbuf *sb); +extern int index_has_changes(struct index_state *istate, + struct tree *tree, + struct strbuf *sb); extern int verify_path(const char *path, unsigned mode); extern int strcmp_offset(const char *s1, const char *s2, size_t *first_change); @@@ -765,6 -698,7 +765,6 @@@ extern int remove_file_from_index(struc extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); -extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options); extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip); extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b); extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce); @@@ -783,16 -717,14 +783,16 @@@ extern void *read_blob_data_from_index( #define CE_MATCH_REFRESH 0x10 /* don't refresh_fsmonitor state or do stat comparison even if CE_FSMONITOR_VALID is true */ #define CE_MATCH_IGNORE_FSMONITOR 0X20 +extern int is_racy_timestamp(const struct index_state *istate, + const struct cache_entry *ce); extern int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); #define HASH_WRITE_OBJECT 1 #define HASH_FORMAT_CHECK 2 #define HASH_RENORMALIZE 4 -extern int index_fd(struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); -extern int index_path(struct object_id *oid, const char *path, struct stat *st, unsigned flags); +extern int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); +extern int index_path(struct index_state *istate, struct object_id *oid, const char *path, struct stat *st, unsigned flags); /* * Record to sd the data from st that we use to check whether a file @@@ -818,9 -750,8 +818,9 @@@ extern void fill_stat_cache_info(struc #define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ #define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ +#define REFRESH_PROGRESS 0x0040 /* show progress bar if stderr is tty */ extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg); -extern struct cache_entry *refresh_cache_entry(struct cache_entry *, unsigned int); +extern struct cache_entry *refresh_cache_entry(struct index_state *, struct cache_entry *, unsigned int); /* * Opportunistically update the index but do not complain if we can't. @@@ -873,13 -804,16 +873,13 @@@ void reset_shared_repository(void) * Do replace refs need to be checked this run? This variable is * initialized to true unless --no-replace-object is used or * $GIT_NO_REPLACE_OBJECTS is set, but is set to false by some - * commands that do not want replace references to be active. As an - * optimization it is also set to false if replace references have - * been sought but there were none. + * commands that do not want replace references to be active. */ -extern int check_replace_refs; +extern int read_replace_refs; extern char *git_replace_ref_base; extern int fsync_object_files; extern int core_preload_index; -extern int core_commit_graph; extern int core_apply_sparse_checkout; extern int precomposed_unicode; extern int protect_hfs; @@@ -922,6 -856,15 +922,6 @@@ enum log_refs_config }; extern enum log_refs_config log_all_ref_updates; -enum branch_track { - BRANCH_TRACK_UNSPECIFIED = -1, - BRANCH_TRACK_NEVER = 0, - BRANCH_TRACK_REMOTE, - BRANCH_TRACK_ALWAYS, - BRANCH_TRACK_EXPLICIT, - BRANCH_TRACK_OVERRIDE -}; - enum rebase_setup_type { AUTOREBASE_NEVER = 0, AUTOREBASE_LOCAL, @@@ -938,6 -881,7 +938,6 @@@ enum push_default_type PUSH_DEFAULT_UNSPECIFIED }; -extern enum branch_track git_branch_track; extern enum rebase_setup_type autorebase; extern enum push_default_type push_default; @@@ -1028,17 -972,7 +1028,17 @@@ extern const struct object_id null_oid static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) { - return memcmp(sha1, sha2, GIT_SHA1_RAWSZ); + /* + * This is a temporary optimization hack. By asserting the size here, + * we let the compiler know that it's always going to be 20, which lets + * it turn this fixed-size memcmp into a few inline instructions. + * + * This will need to be extended or ripped out when we learn about + * hashes of different sizes. + */ + if (the_hash_algo->rawsz != 20) + BUG("hash size not yet supported by hashcmp"); + return memcmp(sha1, sha2, the_hash_algo->rawsz); } static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2) @@@ -1046,29 -980,19 +1046,29 @@@ return hashcmp(oid1->hash, oid2->hash); } +static inline int hasheq(const unsigned char *sha1, const unsigned char *sha2) +{ + return !hashcmp(sha1, sha2); +} + +static inline int oideq(const struct object_id *oid1, const struct object_id *oid2) +{ + return hasheq(oid1->hash, oid2->hash); +} + static inline int is_null_sha1(const unsigned char *sha1) { - return !hashcmp(sha1, null_sha1); + return hasheq(sha1, null_sha1); } static inline int is_null_oid(const struct object_id *oid) { - return !hashcmp(oid->hash, null_sha1); + return hasheq(oid->hash, null_sha1); } static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src) { - memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ); + memcpy(sha_dst, sha_src, the_hash_algo->rawsz); } static inline void oidcpy(struct object_id *dst, const struct object_id *src) @@@ -1085,7 -1009,7 +1085,7 @@@ static inline struct object_id *oiddup( static inline void hashclr(unsigned char *hash) { - memset(hash, 0, GIT_SHA1_RAWSZ); + memset(hash, 0, the_hash_algo->rawsz); } static inline void oidclr(struct object_id *oid) @@@ -1100,22 -1024,22 +1100,22 @@@ static inline void oidread(struct objec static inline int is_empty_blob_sha1(const unsigned char *sha1) { - return !hashcmp(sha1, the_hash_algo->empty_blob->hash); + return hasheq(sha1, the_hash_algo->empty_blob->hash); } static inline int is_empty_blob_oid(const struct object_id *oid) { - return !oidcmp(oid, the_hash_algo->empty_blob); + return oideq(oid, the_hash_algo->empty_blob); } static inline int is_empty_tree_sha1(const unsigned char *sha1) { - return !hashcmp(sha1, the_hash_algo->empty_tree->hash); + return hasheq(sha1, the_hash_algo->empty_tree->hash); } static inline int is_empty_tree_oid(const struct object_id *oid) { - return !oidcmp(oid, the_hash_algo->empty_tree); + return oideq(oid, the_hash_algo->empty_tree); } const char *empty_tree_oid_hex(void); @@@ -1438,20 -1362,18 +1438,20 @@@ extern void *read_object_with_reference extern struct object *peel_to_type(const char *name, int namelen, struct object *o, enum object_type); +enum date_mode_type { + DATE_NORMAL = 0, + DATE_RELATIVE, + DATE_SHORT, + DATE_ISO8601, + DATE_ISO8601_STRICT, + DATE_RFC2822, + DATE_STRFTIME, + DATE_RAW, + DATE_UNIX +}; + struct date_mode { - enum date_mode_type { - DATE_NORMAL = 0, - DATE_RELATIVE, - DATE_SHORT, - DATE_ISO8601, - DATE_ISO8601_STRICT, - DATE_RFC2822, - DATE_STRFTIME, - DATE_RAW, - DATE_UNIX - } type; + enum date_mode_type type; const char *strftime_fmt; int local; }; @@@ -1487,6 -1409,7 +1487,7 @@@ extern const char *fmt_name(const char extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); + extern const char *git_sequence_editor(void); extern const char *git_pager(int stdout_is_tty); extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); @@@ -1533,7 -1456,6 +1534,7 @@@ struct checkout unsigned force:1, quiet:1, not_new:1, + clone:1, refresh_cache:1; }; #define CHECKOUT_INIT { NULL, "" } @@@ -1591,6 -1513,62 +1592,6 @@@ extern int odb_mkstemp(struct strbuf *t */ extern int odb_pack_keep(const char *name); -/* - * Iterate over the files in the loose-object parts of the object - * directory "path", triggering the following callbacks: - * - * - loose_object is called for each loose object we find. - * - * - loose_cruft is called for any files that do not appear to be - * loose objects. Note that we only look in the loose object - * directories "objects/[0-9a-f]{2}/", so we will not report - * "objects/foobar" as cruft. - * - * - loose_subdir is called for each top-level hashed subdirectory - * of the object directory (e.g., "$OBJDIR/f0"). It is called - * after the objects in the directory are processed. - * - * Any callback that is NULL will be ignored. Callbacks returning non-zero - * will end the iteration. - * - * In the "buf" variant, "path" is a strbuf which will also be used as a - * scratch buffer, but restored to its original contents before - * the function returns. - */ -typedef int each_loose_object_fn(const struct object_id *oid, - const char *path, - void *data); -typedef int each_loose_cruft_fn(const char *basename, - const char *path, - void *data); -typedef int each_loose_subdir_fn(unsigned int nr, - const char *path, - void *data); -int for_each_file_in_obj_subdir(unsigned int subdir_nr, - struct strbuf *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); -int for_each_loose_file_in_objdir(const char *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); -int for_each_loose_file_in_objdir_buf(struct strbuf *path, - each_loose_object_fn obj_cb, - each_loose_cruft_fn cruft_cb, - each_loose_subdir_fn subdir_cb, - void *data); - -/* - * Iterate over loose objects in both the local - * repository and any alternates repositories (unless the - * LOCAL_ONLY flag is set). - */ -#define FOR_EACH_OBJECT_LOCAL_ONLY 0x1 -extern int for_each_loose_object(each_loose_object_fn, void *, unsigned flags); - /* * Set this to 0 to prevent sha1_object_info_extended() from fetching missing * blobs. This has a difference only if extensions.partialClone is set. @@@ -1710,7 -1688,7 +1711,7 @@@ void shift_tree_by(const struct object_ /* All WS_* -- when extended, adapt diff.c emit_symbol */ #define WS_RULE_MASK 07777 extern unsigned whitespace_rule_cfg; -extern unsigned whitespace_rule(const char *); +extern unsigned whitespace_rule(struct index_state *, const char *); extern unsigned parse_whitespace_rule(const char *); extern unsigned ws_check(const char *line, int len, unsigned ws_rule); extern void ws_check_emit(const char *line, int len, unsigned ws_rule, FILE *stream, const char *set, const char *reset, const char *ws); @@@ -1732,12 -1710,10 +1733,12 @@@ extern struct startup_info *startup_inf /* merge.c */ struct commit_list; -int try_merge_command(const char *strategy, size_t xopts_nr, +int try_merge_command(struct repository *r, + const char *strategy, size_t xopts_nr, const char **xopts, struct commit_list *common, const char *head_arg, struct commit_list *remotes); -int checkout_fast_forward(const struct object_id *from, +int checkout_fast_forward(struct repository *r, + const struct object_id *from, const struct object_id *to, int overwrite_ignore); diff --combined git-legacy-rebase.sh index 5d92648014,0000000000..75a08b2683 mode 100755,000000..100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@@ -1,708 -1,0 +1,745 @@@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano. +# + +SUBDIRECTORY_OK=Yes +OPTIONS_KEEPDASHDASH= +OPTIONS_STUCKLONG=t +OPTIONS_SPEC="\ +git rebase [-i] [options] [--exec ] [--onto ] [] [] +git rebase [-i] [options] [--exec ] [--onto ] --root [] +git rebase --continue | --abort | --skip | --edit-todo +-- + Available options are +v,verbose! display a diffstat of what changed upstream +q,quiet! be quiet. implies --no-stat +autostash automatically stash/stash pop before and after +fork-point use 'merge-base --fork-point' to refine upstream +onto=! rebase onto given branch instead of upstream +r,rebase-merges? try to rebase merges instead of skipping them +p,preserve-merges! try to recreate merges instead of ignoring them +s,strategy=! use the given merge strategy +X,strategy-option=! pass the argument through to the merge strategy +no-ff! cherry-pick all commits, even if unchanged +f,force-rebase! cherry-pick all commits, even if unchanged +m,merge! use merging strategies to rebase +i,interactive! let the user edit the list of commits to rebase +x,exec=! add exec lines after each commit of the editable list +k,keep-empty preserve empty commits during rebase +allow-empty-message allow rebasing commits with empty messages +stat! display a diffstat of what changed upstream +n,no-stat! do not show diffstat of what changed upstream +verify allow pre-rebase hook to run +rerere-autoupdate allow rerere to update index with resolved conflicts +root! rebase all reachable commits up to the root(s) +autosquash move commits that begin with squash!/fixup! under -i +signoff add a Signed-off-by: line to each commit +committer-date-is-author-date! passed to 'git am' +ignore-date! passed to 'git am' +whitespace=! passed to 'git apply' +ignore-whitespace! passed to 'git apply' +C=! passed to 'git apply' +S,gpg-sign? GPG-sign commits + Actions: +continue! continue +abort! abort and check out the original branch +skip! skip current patch and continue +edit-todo! edit the todo list during an interactive rebase +quit! abort but keep HEAD where it is +show-current-patch! show the patch file being applied or merged +" +. git-sh-setup +set_reflog_action rebase +require_work_tree_exists +cd_to_toplevel + +LF=' +' +ok_to_skip_pre_rebase= + +squash_onto= +unset onto +unset restrict_revision +cmd= +strategy= +strategy_opts= +do_merge= +merge_dir="$GIT_DIR"/rebase-merge +apply_dir="$GIT_DIR"/rebase-apply +verbose= +diffstat= +test "$(git config --bool rebase.stat)" = true && diffstat=t +autostash="$(git config --bool rebase.autostash || echo false)" +fork_point=auto +git_am_opt= +git_format_patch_opt= +rebase_root= +force_rebase= +allow_rerere_autoupdate= +# Non-empty if a rebase was in progress when 'git rebase' was invoked +in_progress= +# One of {am, merge, interactive} +type= +# One of {"$GIT_DIR"/rebase-apply, "$GIT_DIR"/rebase-merge} +state_dir= +# One of {'', continue, skip, abort}, as parsed from command line +action= +rebase_merges= +rebase_cousins= +preserve_merges= +autosquash= +keep_empty= +allow_empty_message=--allow-empty-message +signoff= +test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t +case "$(git config --bool commit.gpgsign)" in +true) gpg_sign_opt=-S ;; +*) gpg_sign_opt= ;; +esac +. git-rebase--common + +read_basic_state () { + test -f "$state_dir/head-name" && + test -f "$state_dir/onto" && + head_name=$(cat "$state_dir"/head-name) && + onto=$(cat "$state_dir"/onto) && + # We always write to orig-head, but interactive rebase used to write to + # head. Fall back to reading from head to cover for the case that the + # user upgraded git with an ongoing interactive rebase. + if test -f "$state_dir"/orig-head + then + orig_head=$(cat "$state_dir"/orig-head) + else + orig_head=$(cat "$state_dir"/head) + fi && + GIT_QUIET=$(cat "$state_dir"/quiet) && + test -f "$state_dir"/verbose && verbose=t + test -f "$state_dir"/strategy && strategy="$(cat "$state_dir"/strategy)" + test -f "$state_dir"/strategy_opts && + strategy_opts="$(cat "$state_dir"/strategy_opts)" + test -f "$state_dir"/allow_rerere_autoupdate && + allow_rerere_autoupdate="$(cat "$state_dir"/allow_rerere_autoupdate)" + test -f "$state_dir"/gpg_sign_opt && + gpg_sign_opt="$(cat "$state_dir"/gpg_sign_opt)" + test -f "$state_dir"/signoff && { + signoff="$(cat "$state_dir"/signoff)" + force_rebase=t + } +} + +finish_rebase () { + rm -f "$(git rev-parse --git-path REBASE_HEAD)" + apply_autostash && + { git gc --auto || true; } && + rm -rf "$state_dir" +} + ++run_interactive () { ++ GIT_CHERRY_PICK_HELP="$resolvemsg" ++ export GIT_CHERRY_PICK_HELP ++ ++ test -n "$keep_empty" && keep_empty="--keep-empty" ++ test -n "$rebase_merges" && rebase_merges="--rebase-merges" ++ test -n "$rebase_cousins" && rebase_cousins="--rebase-cousins" ++ test -n "$autosquash" && autosquash="--autosquash" ++ test -n "$verbose" && verbose="--verbose" ++ test -n "$force_rebase" && force_rebase="--no-ff" ++ test -n "$restrict_revision" && \ ++ restrict_revision="--restrict-revision=^$restrict_revision" ++ test -n "$upstream" && upstream="--upstream=$upstream" ++ test -n "$onto" && onto="--onto=$onto" ++ test -n "$squash_onto" && squash_onto="--squash-onto=$squash_onto" ++ test -n "$onto_name" && onto_name="--onto-name=$onto_name" ++ test -n "$head_name" && head_name="--head-name=$head_name" ++ test -n "$strategy" && strategy="--strategy=$strategy" ++ test -n "$strategy_opts" && strategy_opts="--strategy-opts=$strategy_opts" ++ test -n "$switch_to" && switch_to="--switch-to=$switch_to" ++ test -n "$cmd" && cmd="--cmd=$cmd" ++ test -n "$action" && action="--$action" ++ ++ exec git rebase--interactive "$action" "$keep_empty" "$rebase_merges" "$rebase_cousins" \ ++ "$upstream" "$onto" "$squash_onto" "$restrict_revision" \ ++ "$allow_empty_message" "$autosquash" "$verbose" \ ++ "$force_rebase" "$onto_name" "$head_name" "$strategy" \ ++ "$strategy_opts" "$cmd" "$switch_to" \ ++ "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" ++} ++ +run_specific_rebase () { + if [ "$interactive_rebase" = implied ]; then + GIT_EDITOR=: + export GIT_EDITOR + autosquash= + fi - . git-rebase--$type + - if test -z "$preserve_merges" ++ if test -n "$interactive_rebase" -a -z "$preserve_merges" + then - git_rebase__$type ++ run_interactive + else - git_rebase__preserve_merges ++ . git-rebase--$type ++ ++ if test -z "$preserve_merges" ++ then ++ git_rebase__$type ++ else ++ git_rebase__preserve_merges ++ fi + fi + + ret=$? + if test $ret -eq 0 + then + finish_rebase - elif test $ret -eq 2 # special exit status for rebase -i ++ elif test $ret -eq 2 # special exit status for rebase -p + then + apply_autostash && + rm -rf "$state_dir" && + die "Nothing to do" + fi + exit $ret +} + +run_pre_rebase_hook () { + if test -z "$ok_to_skip_pre_rebase" && + test -x "$(git rev-parse --git-path hooks/pre-rebase)" + then + "$(git rev-parse --git-path hooks/pre-rebase)" ${1+"$@"} || + die "$(gettext "The pre-rebase hook refused to rebase.")" + fi +} + +test -f "$apply_dir"/applying && + die "$(gettext "It looks like 'git am' is in progress. Cannot rebase.")" + +if test -d "$apply_dir" +then + type=am + state_dir="$apply_dir" +elif test -d "$merge_dir" +then + if test -d "$merge_dir"/rewritten + then + type=preserve-merges + interactive_rebase=explicit + preserve_merges=t + elif test -f "$merge_dir"/interactive + then + type=interactive + interactive_rebase=explicit + else + type=merge + fi + state_dir="$merge_dir" +fi +test -n "$type" && in_progress=t + +total_argc=$# +while test $# != 0 +do + case "$1" in + --no-verify) + ok_to_skip_pre_rebase=yes + ;; + --verify) + ok_to_skip_pre_rebase= + ;; + --continue|--skip|--abort|--quit|--edit-todo|--show-current-patch) + test $total_argc -eq 2 || usage + action=${1##--} + ;; + --onto=*) + onto="${1#--onto=}" + ;; + --exec=*) + cmd="${cmd}exec ${1#--exec=}${LF}" + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --interactive) + interactive_rebase=explicit + ;; + --keep-empty) + keep_empty=yes + ;; + --allow-empty-message) + allow_empty_message=--allow-empty-message + ;; + --no-keep-empty) + keep_empty= + ;; + --rebase-merges) + rebase_merges=t + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --rebase-merges=*) + rebase_merges=t + case "${1#*=}" in + rebase-cousins) rebase_cousins=t;; + no-rebase-cousins) rebase_cousins=;; + *) die "Unknown mode: $1";; + esac + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --preserve-merges) + preserve_merges=t + test -z "$interactive_rebase" && interactive_rebase=implied + ;; + --autosquash) + autosquash=t + ;; + --no-autosquash) + autosquash= + ;; + --fork-point) + fork_point=t + ;; + --no-fork-point) + fork_point= + ;; + --merge) + do_merge=t + ;; + --strategy-option=*) + strategy_opts="$strategy_opts $(git rev-parse --sq-quote "--${1#--strategy-option=}" | sed -e s/^.//)" + do_merge=t + test -z "$strategy" && strategy=recursive + ;; + --strategy=*) + strategy="${1#--strategy=}" + do_merge=t + ;; + --no-stat) + diffstat= + ;; + --stat) + diffstat=t + ;; + --autostash) + autostash=true + ;; + --no-autostash) + autostash=false + ;; + --verbose) + verbose=t + diffstat=t + GIT_QUIET= + ;; + --quiet) + GIT_QUIET=t + git_am_opt="$git_am_opt -q" + verbose= + diffstat= + ;; + --whitespace=*) + git_am_opt="$git_am_opt --whitespace=${1#--whitespace=}" + case "${1#--whitespace=}" in + fix|strip) + force_rebase=t + ;; + esac + ;; + --ignore-whitespace) + git_am_opt="$git_am_opt $1" + ;; + --signoff) + signoff=--signoff + ;; + --no-signoff) + signoff= + ;; + --committer-date-is-author-date|--ignore-date) + git_am_opt="$git_am_opt $1" + force_rebase=t + ;; + -C*) + git_am_opt="$git_am_opt $1" + ;; + --root) + rebase_root=t + ;; + --force-rebase|--no-ff) + force_rebase=t + ;; + --rerere-autoupdate|--no-rerere-autoupdate) + allow_rerere_autoupdate="$1" + ;; + --gpg-sign) + gpg_sign_opt=-S + ;; + --gpg-sign=*) + gpg_sign_opt="-S${1#--gpg-sign=}" + ;; + --) + shift + break + ;; + *) + usage + ;; + esac + shift +done +test $# -gt 2 && usage + +if test -n "$action" +then + test -z "$in_progress" && die "$(gettext "No rebase in progress?")" + # Only interactive rebase uses detailed reflog messages + if test -n "$interactive_rebase" && test "$GIT_REFLOG_ACTION" = rebase + then + GIT_REFLOG_ACTION="rebase -i ($action)" + export GIT_REFLOG_ACTION + fi +fi + +if test "$action" = "edit-todo" && test -z "$interactive_rebase" +then + die "$(gettext "The --edit-todo action can only be used during interactive rebase.")" +fi + +case "$action" in +continue) + # Sanity check + git rev-parse --verify HEAD >/dev/null || + die "$(gettext "Cannot read HEAD")" + git update-index --ignore-submodules --refresh && + git diff-files --quiet --ignore-submodules || { + echo "$(gettext "You must edit all merge conflicts and then +mark them as resolved using git add")" + exit 1 + } + read_basic_state + run_specific_rebase + ;; +skip) + output git reset --hard HEAD || exit $? + read_basic_state + run_specific_rebase + ;; +abort) + git rerere clear + read_basic_state + case "$head_name" in + refs/*) + git symbolic-ref -m "rebase: aborting" HEAD $head_name || + die "$(eval_gettext "Could not move back to \$head_name")" + ;; + esac + output git reset --hard $orig_head + finish_rebase + exit + ;; +quit) + exec rm -rf "$state_dir" + ;; +edit-todo) + run_specific_rebase + ;; +show-current-patch) + run_specific_rebase + die "BUG: run_specific_rebase is not supposed to return here" + ;; +esac + +# Make sure no rebase is in progress +if test -n "$in_progress" +then + state_dir_base=${state_dir##*/} + cmd_live_rebase="git rebase (--continue | --abort | --skip)" + cmd_clear_stale_rebase="rm -fr \"$state_dir\"" + die " +$(eval_gettext 'It seems that there is already a $state_dir_base directory, and +I wonder if you are in the middle of another rebase. If that is the +case, please try + $cmd_live_rebase +If that is not the case, please + $cmd_clear_stale_rebase +and run me again. I am stopping in case you still have something +valuable there.')" +fi + +if test -n "$rebase_root" && test -z "$onto" +then + test -z "$interactive_rebase" && interactive_rebase=implied +fi + +if test -n "$keep_empty" +then + test -z "$interactive_rebase" && interactive_rebase=implied +fi + +if test -n "$interactive_rebase" +then + if test -z "$preserve_merges" + then + type=interactive + else + type=preserve-merges + fi + + state_dir="$merge_dir" +elif test -n "$do_merge" +then + type=merge + state_dir="$merge_dir" +else + type=am + state_dir="$apply_dir" +fi + +if test -t 2 && test -z "$GIT_QUIET" +then + git_format_patch_opt="$git_format_patch_opt --progress" +fi + +if test -n "$git_am_opt"; then + incompatible_opts=$(echo " $git_am_opt " | \ + sed -e 's/ -q / /g' -e 's/^ \(.*\) $/\1/') + if test -n "$interactive_rebase" + then + if test -n "$incompatible_opts" + then + die "$(gettext "error: cannot combine interactive options (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + --onto) with am options ($incompatible_opts)")" + fi + fi + if test -n "$do_merge"; then + if test -n "$incompatible_opts" + then + die "$(gettext "error: cannot combine merge options (--merge, --strategy, --strategy-option) with am options ($incompatible_opts)")" + fi + fi +fi + +if test -n "$signoff" +then + test -n "$preserve_merges" && + die "$(gettext "error: cannot combine '--signoff' with '--preserve-merges'")" + git_am_opt="$git_am_opt $signoff" + force_rebase=t +fi + +if test -n "$preserve_merges" +then + # Note: incompatibility with --signoff handled in signoff block above + # Note: incompatibility with --interactive is just a strong warning; + # git-rebase.txt caveats with "unless you know what you are doing" + test -n "$rebase_merges" && + die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")" +fi + +if test -n "$rebase_merges" +then + test -n "$strategy_opts" && + die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy-option'")" + test -n "$strategy" && + die "$(gettext "error: cannot combine '--rebase-merges' with '--strategy'")" +fi + +if test -z "$rebase_root" +then + case "$#" in + 0) + if ! upstream_name=$(git rev-parse --symbolic-full-name \ + --verify -q @{upstream} 2>/dev/null) + then + . git-parse-remote + error_on_missing_default_upstream "rebase" "rebase" \ + "against" "git rebase $(gettext '')" + fi + + test "$fork_point" = auto && fork_point=t + ;; + *) upstream_name="$1" + if test "$upstream_name" = "-" + then + upstream_name="@{-1}" + fi + shift + ;; + esac + upstream=$(peel_committish "${upstream_name}") || + die "$(eval_gettext "invalid upstream '\$upstream_name'")" + upstream_arg="$upstream_name" +else + if test -z "$onto" + then + empty_tree=$(git hash-object -t tree /dev/null) + onto=$(git commit-tree $empty_tree or "detached HEAD" +switch_to= +case "$#" in +1) + # Is it "rebase other $branchname" or "rebase other $commit"? + branch_name="$1" + switch_to="$1" + + # Is it a local branch? + if git show-ref --verify --quiet -- "refs/heads/$branch_name" && + orig_head=$(git rev-parse -q --verify "refs/heads/$branch_name") + then + head_name="refs/heads/$branch_name" + # If not is it a valid ref (branch or commit)? + elif orig_head=$(git rev-parse -q --verify "$branch_name") + then + head_name="detached HEAD" + + else + die "$(eval_gettext "fatal: no such branch/commit '\$branch_name'")" + fi + ;; +0) + # Do not need to switch branches, we are already on it. + if branch_name=$(git symbolic-ref -q HEAD) + then + head_name=$branch_name + branch_name=$(expr "z$branch_name" : 'zrefs/heads/\(.*\)') + else + head_name="detached HEAD" + branch_name=HEAD + fi + orig_head=$(git rev-parse --verify HEAD) || exit + ;; +*) + die "BUG: unexpected number of arguments left to parse" + ;; +esac + +if test "$fork_point" = t +then + new_upstream=$(git merge-base --fork-point "$upstream_name" \ + "${switch_to:-HEAD}") + if test -n "$new_upstream" + then + restrict_revision=$new_upstream + fi +fi + +if test "$autostash" = true && ! (require_clean_work_tree) 2>/dev/null +then + stash_sha1=$(git stash create "autostash") || + die "$(gettext 'Cannot autostash')" + + mkdir -p "$state_dir" && + echo $stash_sha1 >"$state_dir/autostash" && + stash_abbrev=$(git rev-parse --short $stash_sha1) && + echo "$(eval_gettext 'Created autostash: $stash_abbrev')" && + git reset --hard +fi + +require_clean_work_tree "rebase" "$(gettext "Please commit or stash them.")" + +# Now we are rebasing commits $upstream..$orig_head (or with --root, +# everything leading up to $orig_head) on top of $onto + +# Check if we are already based on $onto with linear history, +# but this should be done only when upstream and onto are the same +# and if this is not an interactive rebase. +mb=$(git merge-base "$onto" "$orig_head") +if test -z "$interactive_rebase" && test "$upstream" = "$onto" && + test "$mb" = "$onto" && test -z "$restrict_revision" && + # linear history? + ! (git rev-list --parents "$onto".."$orig_head" | sane_grep " .* ") > /dev/null +then + if test -z "$force_rebase" + then + # Lazily switch to the target branch if needed... + test -z "$switch_to" || + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $switch_to" \ + git checkout -q "$switch_to" -- + if test "$branch_name" = "HEAD" && + ! git symbolic-ref -q HEAD + then + say "$(eval_gettext "HEAD is up to date.")" + else + say "$(eval_gettext "Current branch \$branch_name is up to date.")" + fi + finish_rebase + exit 0 + else + if test "$branch_name" = "HEAD" && + ! git symbolic-ref -q HEAD + then + say "$(eval_gettext "HEAD is up to date, rebase forced.")" + else + say "$(eval_gettext "Current branch \$branch_name is up to date, rebase forced.")" + fi + fi +fi + +# If a hook exists, give it a chance to interrupt +run_pre_rebase_hook "$upstream_arg" "$@" + +if test -n "$diffstat" +then + if test -n "$verbose" + then + echo "$(eval_gettext "Changes from \$mb to \$onto:")" + fi + # We want color (if set), but no pager + GIT_PAGER='' git diff --stat --summary "$mb" "$onto" +fi + +test -n "$interactive_rebase" && run_specific_rebase + +# Detach HEAD and reset the tree +say "$(gettext "First, rewinding head to replay your work on top of it...")" + +GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION: checkout $onto_name" \ + git checkout -q "$onto^0" || die "could not detach HEAD" +git update-ref ORIG_HEAD $orig_head + +# If the $onto is a proper descendant of the tip of the branch, then +# we just fast-forwarded. +if test "$mb" = "$orig_head" +then + say "$(eval_gettext "Fast-forwarded \$branch_name to \$onto_name.")" + move_to_original_branch + finish_rebase + exit 0 +fi + +if test -n "$rebase_root" +then + revisions="$onto..$orig_head" +else + revisions="${restrict_revision-$upstream}..$orig_head" +fi + +run_specific_rebase diff --combined git-rebase--preserve-merges.sh index c214c5e4d6,d43b4b582e..afbb65765d --- a/git-rebase--preserve-merges.sh +++ b/git-rebase--preserve-merges.sh @@@ -711,11 -711,11 +711,11 @@@ do_rest () } expand_todo_ids() { - git rebase--helper --expand-ids + git rebase--interactive --expand-ids } collapse_todo_ids() { - git rebase--helper --shorten-ids + git rebase--interactive --shorten-ids } # Switch to the branch in $into and notify it in the reflog @@@ -876,8 -876,8 +876,8 @@@ init_revisions_and_shortrevisions () complete_action() { test -s "$todo" || echo noop >> "$todo" - test -z "$autosquash" || git rebase--helper --rearrange-squash || exit - test -n "$cmd" && git rebase--helper --add-exec-commands "$cmd" + test -z "$autosquash" || git rebase--interactive --rearrange-squash || exit + test -n "$cmd" && git rebase--interactive --add-exec-commands --cmd "$cmd" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) todocount=${todocount##* } @@@ -891,9 -891,9 +891,9 @@@ $comment_char $(eval_ngettext EOF append_todo_help gettext " - However, if you remove everything, the rebase will be aborted. +However, if you remove everything, the rebase will be aborted. - " | git stripspace --comment-lines >>"$todo" +" | git stripspace --comment-lines >>"$todo" if test -z "$keep_empty" then @@@ -912,7 -912,7 +912,7 @@@ has_action "$todo" || return 2 - git rebase--helper --check-todo-list || { + git rebase--interactive --check-todo-list || { ret=$? checkout_onto exit $ret diff --combined git.c index 8e52276831,81aabd1423..2f604a41ea --- a/git.c +++ b/git.c @@@ -164,7 -164,7 +164,7 @@@ static int handle_options(const char ** if (envchanged) *envchanged = 1; } else if (!strcmp(cmd, "--no-replace-objects")) { - check_replace_refs = 0; + read_replace_refs = 0; setenv(NO_REPLACE_OBJECTS_ENVIRONMENT, "1", 1); if (envchanged) *envchanged = 1; @@@ -318,9 -318,6 +318,9 @@@ static int handle_alias(int *argcp, con alias_command = (*argv)[0]; alias_string = alias_lookup(alias_command); if (alias_string) { + if (*argcp > 1 && !strcmp((*argv)[1], "-h")) + fprintf_ln(stderr, _("'%s' is aliased to '%s'"), + alias_command, alias_string); if (alias_string[0] == '!') { struct child_process child = CHILD_PROCESS_INIT; int nongit_ok; @@@ -417,10 -414,7 +417,10 @@@ static int run_builtin(struct cmd_struc trace_argv_printf(argv, "trace: built-in: git"); + validate_cache_entries(&the_index); status = p->fn(argc, argv, prefix); + validate_cache_entries(&the_index); + if (status) return status; @@@ -511,7 -505,6 +511,7 @@@ static struct cmd_struct commands[] = { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT }, { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT }, { "mktree", cmd_mktree, RUN_SETUP }, + { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP_GENTLY }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "notes", cmd_notes, RUN_SETUP }, @@@ -524,15 -517,8 +524,15 @@@ { "prune-packed", cmd_prune_packed, RUN_SETUP }, { "pull", cmd_pull, RUN_SETUP | NEED_WORK_TREE }, { "push", cmd_push, RUN_SETUP }, + { "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER }, { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, + /* + * NEEDSWORK: Until the rebase is independent and needs no redirection + * to rebase shell script this is kept as is, then should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "rebase", cmd_rebase }, - { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, + { "rebase--interactive", cmd_rebase__interactive, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, @@@ -684,8 -670,6 +684,8 @@@ static void execv_dashed_external(cons static int run_argv(int *argcp, const char ***argv) { int done_alias = 0; + struct string_list cmd_list = STRING_LIST_INIT_NODUP; + struct string_list_item *seen; while (1) { /* @@@ -703,37 -687,17 +703,37 @@@ /* .. then try the external ones */ execv_dashed_external(*argv); - /* It could be an alias -- this works around the insanity + seen = unsorted_string_list_lookup(&cmd_list, *argv[0]); + if (seen) { + int i; + struct strbuf sb = STRBUF_INIT; + for (i = 0; i < cmd_list.nr; i++) { + struct string_list_item *item = &cmd_list.items[i]; + + strbuf_addf(&sb, "\n %s", item->string); + if (item == seen) + strbuf_addstr(&sb, " <=="); + else if (i == cmd_list.nr - 1) + strbuf_addstr(&sb, " ==>"); + } + die(_("alias loop detected: expansion of '%s' does" + " not terminate:%s"), cmd_list.items[0].string, sb.buf); + } + + string_list_append(&cmd_list, *argv[0]); + + /* + * It could be an alias -- this works around the insanity * of overriding "git log" with "git show" by having * alias.log = show */ - if (done_alias) - break; if (!handle_alias(argcp, argv)) break; done_alias = 1; } + string_list_clear(&cmd_list, 0); + return done_alias; } diff --combined sequencer.c index 0c164d5f98,8dd6db5a01..3c86c7694b --- a/sequencer.c +++ b/sequencer.c @@@ -30,7 -30,7 +30,8 @@@ #include "oidset.h" #include "commit-slab.h" #include "alias.h" +#include "commit-reach.h" + #include "rebase-interactive.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@@ -53,7 -53,10 +54,10 @@@ static GIT_PATH_FUNC(rebase_path, "reba * the lines are processed, they are removed from the front of this * file and written to the tail of 'done'. */ - static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") + GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") + static GIT_PATH_FUNC(rebase_path_todo_backup, + "rebase-merge/git-rebase-todo.backup") + /* * The rebase command lines that have already been processed. A line * is moved here when it is first handled, before any associated user @@@ -64,12 -67,12 +68,12 @@@ static GIT_PATH_FUNC(rebase_path_done, * The file to keep track of how many commands were already processed (e.g. * for the prompt). */ -static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum"); +static GIT_PATH_FUNC(rebase_path_msgnum, "rebase-merge/msgnum") /* * The file to keep track of how many commands are to be processed in total * (e.g. for the prompt). */ -static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end"); +static GIT_PATH_FUNC(rebase_path_msgtotal, "rebase-merge/end") /* * The commit message that is planned to be used for any changes that * need to be committed following a user interaction. @@@ -141,7 -144,7 +145,7 @@@ static GIT_PATH_FUNC(rebase_path_refs_t /* * The following files are written by git-rebase just after parsing the - * command-line (and are only consumed, not modified, by the sequencer). + * command-line. */ static GIT_PATH_FUNC(rebase_path_gpg_sign_opt, "rebase-merge/gpg_sign_opt") static GIT_PATH_FUNC(rebase_path_orig_head, "rebase-merge/orig-head") @@@ -153,6 -156,7 +157,7 @@@ static GIT_PATH_FUNC(rebase_path_autost static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate") + static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") static int git_sequencer_config(const char *k, const char *v, void *cb) { @@@ -226,16 -230,13 +231,16 @@@ static const char *get_todo_path(const * Returns 3 when sob exists within conforming footer as last entry */ static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, - int ignore_footer) + size_t ignore_footer) { + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; struct trailer_info info; - int i; + size_t i; int found_sob = 0, found_sob_last = 0; - trailer_info_get(&info, sb->buf); + opts.no_divider = 1; + + trailer_info_get(&info, sb->buf, &opts); if (info.trailer_start == info.trailer_end) return 0; @@@ -311,7 -312,7 +316,7 @@@ static const char *action_name(const st case REPLAY_INTERACTIVE_REBASE: return N_("rebase -i"); } - die(_("Unknown action: %d"), opts->action); + die(_("unknown action: %d"), opts->action); } struct commit_message { @@@ -377,8 -378,8 +382,8 @@@ static void print_advice(int show_hint } } - static int write_message(const void *buf, size_t len, const char *filename, - int append_eol) + int write_message(const void *buf, size_t len, const char *filename, + int append_eol) { struct lock_file msg_file = LOCK_INIT; @@@ -437,7 -438,7 +442,7 @@@ static int read_oneliner(struct strbuf static struct tree *empty_tree(void) { - return lookup_tree(the_hash_algo->empty_tree); + return lookup_tree(the_repository, the_repository->hash_algo->empty_tree); } static int error_dirty_index(struct replay_opts *opts) @@@ -474,8 -475,8 +479,8 @@@ static int fast_forward_to(const struc struct strbuf sb = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - read_cache(); - if (checkout_fast_forward(from, to, 1)) + read_index(&the_index); + if (checkout_fast_forward(the_repository, from, to, 1)) return -1; /* the callee should have complained already */ strbuf_addf(&sb, _("%s: fast-forward"), _(action_name(opts))); @@@ -598,7 -599,7 +603,7 @@@ static int is_index_unchanged(void if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) return error(_("could not resolve HEAD commit")); - head_commit = lookup_commit(&head_oid); + head_commit = lookup_commit(the_repository, &head_oid); /* * If head_commit is NULL, check_commit, called from @@@ -614,7 -615,7 +619,7 @@@ if (!(cache_tree_oid = get_cache_tree_oid())) return -1; - return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit)); + return oideq(cache_tree_oid, get_commit_tree_oid(head_commit)); } static int write_author_script(const char *message) @@@ -643,7 -644,7 +648,7 @@@ missing_author else if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='"); while (*message && *message != '\n' && *message != '\r') if (skip_prefix(message, "> ", &message)) @@@ -651,37 -652,18 +656,37 @@@ else if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@"); while (*message && *message != '\n' && *message != '\r') if (*message != '\'') strbuf_addch(&buf, *(message++)); else - strbuf_addf(&buf, "'\\\\%c'", *(message++)); + strbuf_addf(&buf, "'\\%c'", *(message++)); + strbuf_addch(&buf, '\''); res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1); strbuf_release(&buf); return res; } + +/* + * write_author_script() used to fail to terminate the last line with a "'" and + * also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". We check for + * the terminating "'" on the last line to see how "'" has been escaped in case + * git was upgraded while rebase was stopped. + */ +static int quoting_is_broken(const char *s, size_t n) +{ + /* Skip any empty lines in case the file was hand edited */ + while (n > 0 && s[--n] == '\n') + ; /* empty */ + if (n > 0 && s[n] != '\'') + return 1; + + return 0; +} + /* * Read a list of environment variable assignments (such as the author-script * file) into an environment block. Returns -1 on error, 0 otherwise. @@@ -689,18 -671,14 +694,18 @@@ static int read_env_script(struct argv_array *env) { struct strbuf script = STRBUF_INIT; - int i, count = 0; - char *p, *p2; + int i, count = 0, sq_bug; + const char *p2; + char *p; if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) return -1; - + /* write_author_script() used to quote incorrectly */ + sq_bug = quoting_is_broken(script.buf, script.len); for (p = script.buf; *p; p++) - if (skip_prefix(p, "'\\\\''", (const char **)&p2)) + if (sq_bug && skip_prefix(p, "'\\\\''", &p2)) + strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); + else if (skip_prefix(p, "'\\''", &p2)) strbuf_splice(&script, p - script.buf, p2 - p, "'", 1); else if (*p == '\'') strbuf_splice(&script, p-- - script.buf, 1, "", 0); @@@ -735,51 -713,43 +740,51 @@@ static const char *read_author_ident(st const char *keys[] = { "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE=" }; - char *in, *out, *eol; - int i = 0, len; + struct strbuf out = STRBUF_INIT; + char *in, *eol; + const char *val[3]; + int i = 0; if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0) return NULL; /* dequote values and construct ident line in-place */ - for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { + for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { if (!skip_prefix(in, keys[i], (const char **)&in)) { - warning("could not parse '%s' (looking for '%s'", + warning(_("could not parse '%s' (looking for '%s')"), rebase_path_author_script(), keys[i]); return NULL; } eol = strchrnul(in, '\n'); *eol = '\0'; - sq_dequote(in); - len = strlen(in); - - if (i > 0) /* separate values by spaces */ - *(out++) = ' '; - if (i == 1) /* email needs to be surrounded by <...> */ - *(out++) = '<'; - memmove(out, in, len); - out += len; - if (i == 1) /* email needs to be surrounded by <...> */ - *(out++) = '>'; + if (!sq_dequote(in)) { + warning(_("bad quoting on %s value in '%s'"), + keys[i], rebase_path_author_script()); + return NULL; + } + val[i] = in; in = eol + 1; } if (i < 3) { - warning("could not parse '%s' (looking for '%s')", + warning(_("could not parse '%s' (looking for '%s')"), rebase_path_author_script(), keys[i]); return NULL; } - buf->len = out - buf->buf; + /* validate date since fmt_ident() will die() on bad value */ + if (parse_date(val[2], &out)){ + warning(_("invalid date format '%s' in '%s'"), + val[2], rebase_path_author_script()); + strbuf_release(&out); + return NULL; + } + + strbuf_reset(&out); + strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0)); + strbuf_swap(buf, &out); + strbuf_release(&out); return buf->buf; } @@@ -804,6 -774,23 +809,23 @@@ N_("you have staged changes in your wor #define VERIFY_MSG (1<<4) #define CREATE_ROOT_COMMIT (1<<5) + static int run_command_silent_on_success(struct child_process *cmd) + { + struct strbuf buf = STRBUF_INIT; + int rc; + + cmd->stdout_to_stderr = 1; + rc = pipe_command(cmd, + NULL, 0, + NULL, 0, + &buf, 0); + + if (rc) + fputs(buf.buf, stderr); + strbuf_release(&buf); + return rc; + } + /* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original @@@ -824,18 -811,11 +846,18 @@@ static int run_git_commit(const char *d if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) { struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; - const char *author = is_rebase_i(opts) ? - read_author_ident(&script) : NULL; + const char *author = NULL; struct object_id root_commit, *cache_tree_oid; int res = 0; + if (is_rebase_i(opts)) { + author = read_author_ident(&script); + if (!author) { + strbuf_release(&script); + return -1; + } + } + if (!defmsg) BUG("root commit without message"); @@@ -865,18 -845,11 +887,11 @@@ cmd.git_cmd = 1; - if (is_rebase_i(opts)) { - if (!(flags & EDIT_MSG)) { - cmd.stdout_to_stderr = 1; - cmd.err = -1; - } - - if (read_env_script(&cmd.env_array)) { - const char *gpg_opt = gpg_sign_opt_quoted(opts); + if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); - return error(_(staged_changes_advice), - gpg_opt, gpg_opt); - } + return error(_(staged_changes_advice), + gpg_opt, gpg_opt); } argv_array_push(&cmd.args, "commit"); @@@ -903,24 -876,13 +918,13 @@@ if ((flags & ALLOW_EMPTY)) argv_array_push(&cmd.args, "--allow-empty"); - if (opts->allow_empty_message) + if (!(flags & EDIT_MSG)) argv_array_push(&cmd.args, "--allow-empty-message"); - if (cmd.err == -1) { - /* hide stderr on success */ - struct strbuf buf = STRBUF_INIT; - int rc = pipe_command(&cmd, - NULL, 0, - /* stdout is already redirected */ - NULL, 0, - &buf, 0); - if (rc) - fputs(buf.buf, stderr); - strbuf_release(&buf); - return rc; - } - - return run_command(&cmd); + if (is_rebase_i(opts) && !(flags & EDIT_MSG)) + return run_command_silent_on_success(&cmd); + else + return run_command(&cmd); } static int rest_is_empty(const struct strbuf *sb, int start) @@@ -1143,7 -1105,7 +1147,7 @@@ void print_commit_summary(const char *p struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; - commit = lookup_commit(oid); + commit = lookup_commit(the_repository, oid); if (!commit) die(_("couldn't look up newly created commit")); if (parse_commit(commit)) @@@ -1176,7 -1138,7 +1180,7 @@@ strbuf_release(&author_ident); strbuf_release(&committer_ident); - init_revisions(&rev, prefix); + repo_init_revisions(the_repository, &rev, prefix); setup_revisions(0, NULL, &rev, NULL); rev.diff = 1; @@@ -1218,10 -1180,10 +1222,10 @@@ static int parse_head(struct commit **h if (get_oid("HEAD", &oid)) { current_head = NULL; } else { - current_head = lookup_commit_reference(&oid); + current_head = lookup_commit_reference(the_repository, &oid); if (!current_head) return error(_("could not parse HEAD")); - if (oidcmp(&oid, ¤t_head->object.oid)) { + if (!oideq(&oid, ¤t_head->object.oid)) { warning(_("HEAD %s is not a commit!"), oid_to_hex(&oid)); } @@@ -1286,14 -1248,14 +1290,14 @@@ static int try_to_commit(struct strbuf commit_list_insert(current_head, &parents); } - if (write_cache_as_tree(&tree, 0, NULL)) { + if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) { res = error(_("git write-tree failed to write a tree")); goto out; } - if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ? - get_commit_tree_oid(current_head) : - the_hash_algo->empty_tree, &tree)) { + if (!(flags & ALLOW_EMPTY) && oideq(current_head ? + get_commit_tree_oid(current_head) : + the_hash_algo->empty_tree, &tree)) { res = 1; /* run 'git commit' to display error message */ goto out; } @@@ -1317,7 -1279,7 +1321,7 @@@ if (cleanup != COMMIT_MSG_CLEANUP_NONE) strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL); - if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) { + if ((flags & EDIT_MSG) && message_is_empty(msg, cleanup)) { res = 1; /* run 'git commit' to display error message */ goto out; } @@@ -1398,7 -1360,7 +1402,7 @@@ static int is_original_commit_empty(str ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } - return !oidcmp(ptree_oid, get_commit_tree_oid(commit)); + return oideq(ptree_oid, get_commit_tree_oid(commit)); } /* @@@ -1487,7 -1449,7 +1491,7 @@@ static const char *command_to_string(co { if (command < TODO_COMMENT) return todo_command_info[command].str; - die("Unknown command: %d", command); + die(_("unknown command: %d"), command); } static char command_to_char(const enum todo_command command) @@@ -1553,7 -1515,7 +1557,7 @@@ static int update_squash_messages(enum if (get_oid("HEAD", &head)) return error(_("need a HEAD to fixup")); - if (!(head_commit = lookup_commit_reference(&head))) + if (!(head_commit = lookup_commit_reference(the_repository, &head))) return error(_("could not read HEAD")); if (!(head_message = get_commit_buffer(head_commit, NULL))) return error(_("could not read HEAD's commit message")); @@@ -1585,13 -1547,13 +1589,13 @@@ unlink(rebase_path_fixup_msg()); strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("This is the commit message #%d:"), - ++opts->current_fixup_count); + ++opts->current_fixup_count + 1); strbuf_addstr(&buf, "\n\n"); strbuf_addstr(&buf, body); } else if (command == TODO_FIXUP) { strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("The commit message #%d will be skipped:"), - ++opts->current_fixup_count); + ++opts->current_fixup_count + 1); strbuf_addstr(&buf, "\n\n"); strbuf_add_commented_lines(&buf, body, strlen(body)); } else @@@ -1672,13 -1634,13 +1676,13 @@@ static int do_pick_commit(enum todo_com * that represents the "current" state for merge-recursive * to work on. */ - if (write_cache_as_tree(&head, 0, NULL)) + if (write_index_as_tree(&head, &the_index, get_index_file(), 0, NULL)) return error(_("your index file is unmerged.")); } else { unborn = get_oid("HEAD", &head); /* Do we want to generate a root commit? */ if (is_pick_or_similar(command) && opts->have_squash_onto && - !oidcmp(&head, &opts->squash_onto)) { + oideq(&head, &opts->squash_onto)) { if (is_fixup(command)) return error(_("cannot fixup root commit")); flags |= CREATE_ROOT_COMMIT; @@@ -1721,7 -1683,7 +1725,7 @@@ oid_to_hex(&commit->object.oid)); if (opts->allow_ff && !is_fixup(command) && - ((parent && !oidcmp(&parent->object.oid, &head)) || + ((parent && oideq(&parent->object.oid, &head)) || (!parent && unborn))) { if (is_rebase_i(opts)) write_author_script(msg.message); @@@ -1831,7 -1793,7 +1835,7 @@@ commit_list_insert(base, &common); commit_list_insert(next, &remotes); - res |= try_merge_command(opts->strategy, + res |= try_merge_command(the_repository, opts->strategy, opts->xopts_nr, (const char **)opts->xopts, common, oid_to_hex(&head), remotes); free_commit_list(common); @@@ -1860,7 -1822,7 +1864,7 @@@ : _("could not apply %s... %s"), short_commit_name(commit), msg.subject); print_advice(res == 1, opts); - rerere(opts->allow_rerere_auto); + repo_rerere(the_repository, opts->allow_rerere_auto); goto leave; } @@@ -1906,6 -1868,8 +1910,6 @@@ static int prepare_revs(struct replay_o if (prepare_revision_walk(opts->revs)) return error(_("revision walk setup failed")); - if (!opts->revs->commits) - return error(_("empty commit set passed")); return 0; } @@@ -1913,7 -1877,7 +1917,7 @@@ static int read_and_refresh_cache(struc { struct lock_file index_lock = LOCK_INIT; int index_fd = hold_locked_index(&index_lock, 0); - if (read_index_preload(&the_index, NULL) < 0) { + if (read_index_preload(&the_index, NULL, 0) < 0) { rollback_lock_file(&index_lock); return error(_("git %s: failed to read the index"), _(action_name(opts))); @@@ -2049,7 -2013,7 +2053,7 @@@ static int parse_insn_line(struct todo_ if (status < 0) return -1; - item->commit = lookup_commit_reference(&commit_oid); + item->commit = lookup_commit_reference(the_repository, &commit_oid); return !item->commit; } @@@ -2244,21 -2208,14 +2248,14 @@@ static int populate_opts_cb(const char return 0; } - static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) + void parse_strategy_opts(struct replay_opts *opts, char *raw_opts) { int i; - char *strategy_opts_string; - - strbuf_reset(buf); - if (!read_oneliner(buf, rebase_path_strategy(), 0)) - return; - opts->strategy = strbuf_detach(buf, NULL); - if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) - return; + char *strategy_opts_string = raw_opts; - strategy_opts_string = buf->buf; if (*strategy_opts_string == ' ') strategy_opts_string++; + opts->xopts_nr = split_cmdline(strategy_opts_string, (const char ***)&opts->xopts); for (i = 0; i < opts->xopts_nr; i++) { @@@ -2269,6 -2226,18 +2266,18 @@@ } } + static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) + { + strbuf_reset(buf); + if (!read_oneliner(buf, rebase_path_strategy(), 0)) + return; + opts->strategy = strbuf_detach(buf, NULL); + if (!read_oneliner(buf, rebase_path_strategy_opts(), 0)) + return; + + parse_strategy_opts(opts, buf->buf); + } + static int read_populate_opts(struct replay_opts *opts) { if (is_rebase_i(opts)) { @@@ -2336,6 -2305,55 +2345,55 @@@ return 0; } + static void write_strategy_opts(struct replay_opts *opts) + { + int i; + struct strbuf buf = STRBUF_INIT; + + for (i = 0; i < opts->xopts_nr; ++i) + strbuf_addf(&buf, " --%s", opts->xopts[i]); + + write_file(rebase_path_strategy_opts(), "%s\n", buf.buf); + strbuf_release(&buf); + } + + int write_basic_state(struct replay_opts *opts, const char *head_name, + const char *onto, const char *orig_head) + { + const char *quiet = getenv("GIT_QUIET"); + + if (head_name) + write_file(rebase_path_head_name(), "%s\n", head_name); + if (onto) + write_file(rebase_path_onto(), "%s\n", onto); + if (orig_head) + write_file(rebase_path_orig_head(), "%s\n", orig_head); + + if (quiet) + write_file(rebase_path_quiet(), "%s\n", quiet); + else + write_file(rebase_path_quiet(), "\n"); + + if (opts->verbose) + write_file(rebase_path_verbose(), ""); + if (opts->strategy) + write_file(rebase_path_strategy(), "%s\n", opts->strategy); + if (opts->xopts_nr > 0) + write_strategy_opts(opts); + + if (opts->allow_rerere_auto == RERERE_AUTOUPDATE) + write_file(rebase_path_allow_rerere_autoupdate(), "--rerere-autoupdate\n"); + else if (opts->allow_rerere_auto == RERERE_NOAUTOUPDATE) + write_file(rebase_path_allow_rerere_autoupdate(), "--no-rerere-autoupdate\n"); + + if (opts->gpg_sign) + write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign); + if (opts->signoff) + write_file(rebase_path_signoff(), "--signoff\n"); + + return 0; + } + static int walk_revs_populate_todo(struct todo_list *todo_list, struct replay_opts *opts) { @@@ -2363,10 -2381,6 +2421,10 @@@ short_commit_name(commit), subject_len, subject); unuse_commit_buffer(commit, commit_buffer); } + + if (!todo_list->nr) + return error(_("empty commit set passed")); + return 0; } @@@ -2426,7 -2440,7 +2484,7 @@@ static int rollback_is_safe(void if (get_oid("HEAD", &actual_head)) oidclr(&actual_head); - return !oidcmp(&actual_head, &expected_head); + return oideq(&actual_head, &expected_head); } static int reset_for_rollback(const struct object_id *oid) @@@ -2599,7 -2613,7 +2657,7 @@@ static int make_patch(struct commit *co strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - init_revisions(&log_tree_opt, NULL); + repo_init_revisions(the_repository, &log_tree_opt, NULL); log_tree_opt.abbrev = 0; log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_PATCH; @@@ -2643,39 -2657,23 +2701,39 @@@ static int error_with_patch(struct comm const char *subject, int subject_len, struct replay_opts *opts, int exit_code, int to_amend) { - if (make_patch(commit, opts)) - return -1; + if (commit) { + if (make_patch(commit, opts)) + return -1; + } else if (copy_file(rebase_path_message(), + git_path_merge_msg(the_repository), 0666)) + return error(_("unable to copy '%s' to '%s'"), + git_path_merge_msg(the_repository), rebase_path_message()); if (to_amend) { if (intend_to_amend()) return -1; - fprintf(stderr, "You can amend the commit now, with\n" - "\n" - " git commit --amend %s\n" - "\n" - "Once you are satisfied with your changes, run\n" - "\n" - " git rebase --continue\n", gpg_sign_opt_quoted(opts)); - } else if (exit_code) - fprintf(stderr, "Could not apply %s... %.*s\n", - short_commit_name(commit), subject_len, subject); + fprintf(stderr, + _("You can amend the commit now, with\n" + "\n" + " git commit --amend %s\n" + "\n" + "Once you are satisfied with your changes, run\n" + "\n" + " git rebase --continue\n"), + gpg_sign_opt_quoted(opts)); + } else if (exit_code) { + if (commit) + fprintf_ln(stderr, _("Could not apply %s... %.*s"), + short_commit_name(commit), subject_len, subject); + else + /* + * We don't have the hash of the parent so + * just print the line from the todo file. + */ + fprintf_ln(stderr, _("Could not merge %.*s"), + subject_len, subject); + } return exit_code; } @@@ -2703,8 -2701,6 +2761,8 @@@ static int do_exec(const char *command_ fprintf(stderr, "Executing: %s\n", command_line); child_argv[0] = command_line; argv_array_pushf(&child_env, "GIT_DIR=%s", absolute_path(get_git_dir())); + argv_array_pushf(&child_env, "GIT_WORK_TREE=%s", + absolute_path(get_git_work_tree())); status = run_command_v_opt_cd_env(child_argv, RUN_USING_SHELL, NULL, child_env.argv); @@@ -2788,7 -2784,7 +2846,7 @@@ static int do_label(const char *name, i struct object_id head_oid; if (len == 1 && *name == '#') - return error("Illegal label name: '%.*s'", len, name); + return error(_("illegal label name: '%.*s'"), len, name); strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name); @@@ -2910,26 -2906,6 +2968,26 @@@ static int do_reset(const char *name, i return ret; } +static struct commit *lookup_label(const char *label, int len, + struct strbuf *buf) +{ + struct commit *commit; + + strbuf_reset(buf); + strbuf_addf(buf, "refs/rewritten/%.*s", len, label); + commit = lookup_commit_reference_by_name(buf->buf); + if (!commit) { + /* fall back to non-rewritten ref or commit */ + strbuf_splice(buf, 0, strlen("refs/rewritten/"), "", 0); + commit = lookup_commit_reference_by_name(buf->buf); + } + + if (!commit) + error(_("could not resolve '%s'"), buf->buf); + + return commit; +} + static int do_merge(struct commit *commit, const char *arg, int arg_len, int flags, struct replay_opts *opts) { @@@ -2938,9 -2914,8 +2996,9 @@@ struct strbuf ref_name = STRBUF_INIT; struct commit *head_commit, *merge_commit, *i; struct commit_list *bases, *j, *reversed = NULL; + struct commit_list *to_merge = NULL, **tail = &to_merge; struct merge_options o; - int merge_arg_len, oneline_offset, can_fast_forward, ret; + int merge_arg_len, oneline_offset, can_fast_forward, ret, k; static struct lock_file lock; const char *p; @@@ -2955,51 -2930,38 +3013,51 @@@ goto leave_merge; } - oneline_offset = arg_len; - merge_arg_len = strcspn(arg, " \t\n"); - p = arg + merge_arg_len; - p += strspn(p, " \t\n"); - if (*p == '#' && (!p[1] || isspace(p[1]))) { - p += 1 + strspn(p + 1, " \t\n"); - oneline_offset = p - arg; - } else if (p - arg < arg_len) - BUG("octopus merges are not supported yet: '%s'", p); - - strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg); - merge_commit = lookup_commit_reference_by_name(ref_name.buf); - if (!merge_commit) { - /* fall back to non-rewritten ref or commit */ - strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0); - merge_commit = lookup_commit_reference_by_name(ref_name.buf); + /* + * For octopus merges, the arg starts with the list of revisions to be + * merged. The list is optionally followed by '#' and the oneline. + */ + merge_arg_len = oneline_offset = arg_len; + for (p = arg; p - arg < arg_len; p += strspn(p, " \t\n")) { + if (!*p) + break; + if (*p == '#' && (!p[1] || isspace(p[1]))) { + p += 1 + strspn(p + 1, " \t\n"); + oneline_offset = p - arg; + break; + } + k = strcspn(p, " \t\n"); + if (!k) + continue; + merge_commit = lookup_label(p, k, &ref_name); + if (!merge_commit) { + ret = error(_("unable to parse '%.*s'"), k, p); + goto leave_merge; + } + tail = &commit_list_insert(merge_commit, tail)->next; + p += k; + merge_arg_len = p - arg; } - if (!merge_commit) { - ret = error(_("could not resolve '%s'"), ref_name.buf); + if (!to_merge) { + ret = error(_("nothing to merge: '%.*s'"), arg_len, arg); goto leave_merge; } if (opts->have_squash_onto && - !oidcmp(&head_commit->object.oid, &opts->squash_onto)) { + oideq(&head_commit->object.oid, &opts->squash_onto)) { /* * When the user tells us to "merge" something into a * "[new root]", let's simply fast-forward to the merge head. */ rollback_lock_file(&lock); - ret = fast_forward_to(&merge_commit->object.oid, - &head_commit->object.oid, 0, opts); + if (to_merge->next) + ret = error(_("octopus merge cannot be executed on " + "top of a [new root]")); + else + ret = fast_forward_to(&to_merge->item->object.oid, + &head_commit->object.oid, 0, + opts); goto leave_merge; } @@@ -3035,8 -2997,7 +3093,8 @@@ p = arg + oneline_offset; len = arg_len - oneline_offset; } else { - strbuf_addf(&buf, "Merge branch '%.*s'", + strbuf_addf(&buf, "Merge %s '%.*s'", + to_merge->next ? "branches" : "branch", merge_arg_len, arg); p = buf.buf; len = buf.len; @@@ -3056,87 -3017,39 +3114,87 @@@ * commit, we cannot fast-forward. */ can_fast_forward = opts->allow_ff && commit && commit->parents && - !oidcmp(&commit->parents->item->object.oid, - &head_commit->object.oid); + oideq(&commit->parents->item->object.oid, + &head_commit->object.oid); /* - * If the merge head is different from the original one, we cannot + * If any merge head is different from the original one, we cannot * fast-forward. */ if (can_fast_forward) { - struct commit_list *second_parent = commit->parents->next; + struct commit_list *p = commit->parents->next; - if (second_parent && !second_parent->next && - oidcmp(&merge_commit->object.oid, - &second_parent->item->object.oid)) + for (j = to_merge; j && p; j = j->next, p = p->next) + if (!oideq(&j->item->object.oid, + &p->item->object.oid)) { + can_fast_forward = 0; + break; + } + /* + * If the number of merge heads differs from the original merge + * commit, we cannot fast-forward. + */ + if (j || p) can_fast_forward = 0; } - if (can_fast_forward && commit->parents->next && - !commit->parents->next->next && - !oidcmp(&commit->parents->next->item->object.oid, - &merge_commit->object.oid)) { + if (can_fast_forward) { rollback_lock_file(&lock); ret = fast_forward_to(&commit->object.oid, &head_commit->object.oid, 0, opts); goto leave_merge; } + if (to_merge->next) { + /* Octopus merge */ + struct child_process cmd = CHILD_PROCESS_INIT; + + if (read_env_script(&cmd.env_array)) { + const char *gpg_opt = gpg_sign_opt_quoted(opts); + + ret = error(_(staged_changes_advice), gpg_opt, gpg_opt); + goto leave_merge; + } + + cmd.git_cmd = 1; + argv_array_push(&cmd.args, "merge"); + argv_array_push(&cmd.args, "-s"); + argv_array_push(&cmd.args, "octopus"); + argv_array_push(&cmd.args, "--no-edit"); + argv_array_push(&cmd.args, "--no-ff"); + argv_array_push(&cmd.args, "--no-log"); + argv_array_push(&cmd.args, "--no-stat"); + argv_array_push(&cmd.args, "-F"); + argv_array_push(&cmd.args, git_path_merge_msg(the_repository)); + if (opts->gpg_sign) + argv_array_push(&cmd.args, opts->gpg_sign); + + /* Add the tips to be merged */ + for (j = to_merge; j; j = j->next) + argv_array_push(&cmd.args, + oid_to_hex(&j->item->object.oid)); + + strbuf_release(&ref_name); + unlink(git_path_cherry_pick_head(the_repository)); + rollback_lock_file(&lock); + + rollback_lock_file(&lock); + ret = run_command(&cmd); + + /* force re-reading of the cache */ + if (!ret && (discard_cache() < 0 || read_cache() < 0)) + ret = error(_("could not read index")); + goto leave_merge; + } + + merge_commit = to_merge->item; write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, git_path_merge_head(the_repository), 0); write_message("no-ff", 5, git_path_merge_mode(the_repository), 0); bases = get_merge_bases(head_commit, merge_commit); - if (bases && !oidcmp(&merge_commit->object.oid, - &bases->item->object.oid)) { + if (bases && oideq(&merge_commit->object.oid, + &bases->item->object.oid)) { ret = 0; /* skip merging an ancestor of HEAD */ goto leave_merge; @@@ -3179,7 -3092,7 +3237,7 @@@ rollback_lock_file(&lock); if (ret) - rerere(opts->allow_rerere_auto); + repo_rerere(the_repository, opts->allow_rerere_auto); else /* * In case of problems, we now want to return a positive @@@ -3192,7 -3105,6 +3250,7 @@@ leave_merge: strbuf_release(&ref_name); rollback_lock_file(&lock); + free_commit_list(to_merge); return ret; } @@@ -3286,6 -3198,55 +3344,55 @@@ static const char *reflog_message(struc return buf.buf; } + static int run_git_checkout(struct replay_opts *opts, const char *commit, + const char *action) + { + struct child_process cmd = CHILD_PROCESS_INIT; + + cmd.git_cmd = 1; + + argv_array_push(&cmd.args, "checkout"); + argv_array_push(&cmd.args, commit); + argv_array_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action); + + if (opts->verbose) + return run_command(&cmd); + else + return run_command_silent_on_success(&cmd); + } + + int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit) + { + const char *action; + + if (commit && *commit) { + action = reflog_message(opts, "start", "checkout %s", commit); + if (run_git_checkout(opts, commit, action)) + return error(_("could not checkout %s"), commit); + } + + return 0; + } + + static int checkout_onto(struct replay_opts *opts, + const char *onto_name, const char *onto, + const char *orig_head) + { + struct object_id oid; + const char *action = reflog_message(opts, "start", "checkout %s", onto_name); + + if (get_oid(orig_head, &oid)) + return error(_("%s: not a valid OID"), orig_head); + + if (run_git_checkout(opts, onto, action)) { + apply_autostash(opts); + sequencer_remove_state(opts); + return error(_("could not detach HEAD")); + } + + return update_ref(NULL, "ORIG_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); + } + static const char rescheduled_advice[] = N_("Could not execute the todo command\n" "\n" @@@ -3382,9 -3343,9 +3489,9 @@@ static int pick_commits(struct todo_lis */ if (item->command == TODO_REWORD && !get_oid("HEAD", &oid) && - (!oidcmp(&item->commit->object.oid, &oid) || + (oideq(&item->commit->object.oid, &oid) || (opts->have_squash_onto && - !oidcmp(&opts->squash_onto, &oid)))) + oideq(&opts->squash_onto, &oid)))) to_amend = 1; return res | error_with_patch(item->commit, @@@ -3510,7 -3471,7 +3617,7 @@@ cleanup_head_ref struct object_id orig, head; memset(&log_tree_opt, 0, sizeof(log_tree_opt)); - init_revisions(&log_tree_opt, NULL); + repo_init_revisions(the_repository, &log_tree_opt, NULL); log_tree_opt.diff = 1; log_tree_opt.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; @@@ -3599,7 -3560,7 +3706,7 @@@ static int commit_staged_changes(struc if (get_oid_hex(rev.buf, &to_amend)) return error(_("invalid contents: '%s'"), rebase_path_amend()); - if (!is_clean && oidcmp(&head, &to_amend)) + if (!is_clean && !oideq(&head, &to_amend)) return error(_("\nYou have uncommitted changes in your " "working tree. Please, commit them\n" "first and then run 'git rebase " @@@ -3611,20 -3572,9 +3718,20 @@@ * the commit message and if there was a squash, let the user * edit it. */ - if (is_clean && !oidcmp(&head, &to_amend) && - opts->current_fixup_count > 0 && - file_exists(rebase_path_stopped_sha())) { + if (!is_clean || !opts->current_fixup_count) + ; /* this is not the final fixup */ + else if (!oideq(&head, &to_amend) || + !file_exists(rebase_path_stopped_sha())) { + /* was a final fixup or squash done manually? */ + if (!is_fixup(peek_command(todo_list, 0))) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; + } + } else { + /* we are in a fixup/squash chain */ const char *p = opts->current_fixups.buf; int len = opts->current_fixups.len; @@@ -3789,7 -3739,7 +3896,7 @@@ int sequencer_pick_revisions(struct rep continue; if (!get_oid(name, &oid)) { - if (!lookup_commit_reference_gently(&oid, 1)) { + if (!lookup_commit_reference_gently(the_repository, &oid, 1)) { enum object_type type = oid_object_info(the_repository, &oid, NULL); @@@ -3815,10 -3765,8 +3922,10 @@@ if (prepare_revision_walk(opts->revs)) return error(_("revision walk setup failed")); cmit = get_revision(opts->revs); - if (!cmit || get_revision(opts->revs)) - return error("BUG: expected exactly one commit from walk"); + if (!cmit) + return error(_("empty commit set passed")); + if (get_revision(opts->revs)) + BUG("unexpected extra commit from walk"); return single_pick(cmit, opts); } @@@ -3843,7 -3791,7 +3950,7 @@@ return res; } -void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag) { unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; @@@ -4060,6 -4008,7 +4167,6 @@@ static int make_script_with_merges(stru */ while ((commit = get_revision(revs))) { struct commit_list *to_merge; - int is_octopus; const char *p1, *p2; struct object_id *oid; int is_empty; @@@ -4091,6 -4040,11 +4198,6 @@@ continue; } - is_octopus = to_merge && to_merge->next; - - if (is_octopus) - BUG("Octopus merges not yet supported"); - /* Create a label */ strbuf_reset(&label); if (skip_prefix(oneline.buf, "Merge ", &p1) && @@@ -4112,17 -4066,13 +4219,17 @@@ strbuf_addf(&buf, "%s -C %s", cmd_merge, oid_to_hex(&commit->object.oid)); - /* label the tip of merged branch */ - oid = &to_merge->item->object.oid; - strbuf_addch(&buf, ' '); + /* label the tips of merged branches */ + for (; to_merge; to_merge = to_merge->next) { + oid = &to_merge->item->object.oid; + strbuf_addch(&buf, ' '); + + if (!oidset_contains(&interesting, oid)) { + strbuf_addstr(&buf, label_oid(oid, NULL, + &state)); + continue; + } - if (!oidset_contains(&interesting, oid)) - strbuf_addstr(&buf, label_oid(oid, NULL, &state)); - else { tips_tail = &commit_list_insert(to_merge->item, tips_tail)->next; @@@ -4146,7 -4096,9 +4253,7 @@@ struct object_id *oid = &parent->item->object.oid; if (!oidset_contains(&interesting, oid)) continue; - if (!oidset_contains(&child_seen, oid)) - oidset_insert(&child_seen, oid); - else + if (oidset_insert(&child_seen, oid)) label_oid(oid, "branch-point", &state); } @@@ -4173,7 -4125,7 +4280,7 @@@ entry = oidmap_get(&state.commit2label, &commit->object.oid); if (entry) - fprintf(out, "\n# Branch %s\n", entry->string); + fprintf(out, "\n%c Branch %s\n", comment_line_char, entry->string); else fprintf(out, "\n"); @@@ -4254,7 -4206,7 +4361,7 @@@ int sequencer_make_script(FILE *out, in const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; int rebase_merges = flags & TODO_LIST_REBASE_MERGES; - init_revisions(&revs, NULL); + repo_init_revisions(the_repository, &revs, NULL); revs.verbose_header = 1; if (!rebase_merges) revs.max_parents = 1; @@@ -4311,9 -4263,10 +4418,9 @@@ int sequencer_add_exec_commands(const c { const char *todo_file = rebase_path_todo(); struct todo_list todo_list = TODO_LIST_INIT; - struct todo_item *item; struct strbuf *buf = &todo_list.buf; size_t offset = 0, commands_len = strlen(commands); - int i, first; + int i, insert; if (strbuf_read_file(&todo_list.buf, todo_file, 0) < 0) return error(_("could not read '%s'."), todo_file); @@@ -4323,40 -4276,19 +4430,40 @@@ return error(_("unusable todo list: '%s'"), todo_file); } - first = 1; - /* insert before every pick except the first one */ - for (item = todo_list.items, i = 0; i < todo_list.nr; i++, item++) { - if (item->command == TODO_PICK && !first) { - strbuf_insert(buf, item->offset_in_buf + offset, - commands, commands_len); + /* + * Insert after every pick. Here, fixup/squash chains + * are considered part of the pick, so we insert the commands *after* + * those chains if there are any. + */ + insert = -1; + for (i = 0; i < todo_list.nr; i++) { + enum todo_command command = todo_list.items[i].command; + + if (insert >= 0) { + /* skip fixup/squash chains */ + if (command == TODO_COMMENT) + continue; + else if (is_fixup(command)) { + insert = i + 1; + continue; + } + strbuf_insert(buf, + todo_list.items[insert].offset_in_buf + + offset, commands, commands_len); offset += commands_len; + insert = -1; } - first = 0; + + if (command == TODO_PICK || command == TODO_MERGE) + insert = i + 1; } - /* append final */ - strbuf_add(buf, commands, commands_len); + /* insert or append final */ + if (insert >= 0 && insert < todo_list.nr) + strbuf_insert(buf, todo_list.items[insert].offset_in_buf + + offset, commands, commands_len); + else if (insert >= 0 || !offset) + strbuf_add(buf, commands, commands_len); i = write_message(buf->buf, buf->len, todo_file, 0); todo_list_release(&todo_list); @@@ -4420,24 -4352,20 +4527,20 @@@ int transform_todos(unsigned flags return i; } - enum check_level { - CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR - }; - - static enum check_level get_missing_commit_check_level(void) + enum missing_commit_check_level get_missing_commit_check_level(void) { const char *value; if (git_config_get_value("rebase.missingcommitscheck", &value) || !strcasecmp("ignore", value)) - return CHECK_IGNORE; + return MISSING_COMMIT_CHECK_IGNORE; if (!strcasecmp("warn", value)) - return CHECK_WARN; + return MISSING_COMMIT_CHECK_WARN; if (!strcasecmp("error", value)) - return CHECK_ERROR; + return MISSING_COMMIT_CHECK_ERROR; warning(_("unrecognized setting %s for option " "rebase.missingCommitsCheck. Ignoring."), value); - return CHECK_IGNORE; + return MISSING_COMMIT_CHECK_IGNORE; } define_commit_slab(commit_seen, unsigned char); @@@ -4449,7 -4377,7 +4552,7 @@@ */ int check_todo_list(void) { - enum check_level check_level = get_missing_commit_check_level(); + enum missing_commit_check_level check_level = get_missing_commit_check_level(); struct strbuf todo_file = STRBUF_INIT; struct todo_list todo_list = TODO_LIST_INIT; struct strbuf missing = STRBUF_INIT; @@@ -4466,7 -4394,7 +4569,7 @@@ advise_to_edit_todo = res = parse_insn_buffer(todo_list.buf.buf, &todo_list); - if (res || check_level == CHECK_IGNORE) + if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) goto leave_check; /* Mark the commits in git-rebase-todo as seen */ @@@ -4501,7 -4429,7 +4604,7 @@@ if (!missing.len) goto leave_check; - if (check_level == CHECK_ERROR) + if (check_level == MISSING_COMMIT_CHECK_ERROR) advise_to_edit_todo = res = 1; fprintf(stderr, @@@ -4547,17 -4475,17 +4650,17 @@@ static int rewrite_file(const char *pat } /* skip picking commits whose parents are unchanged */ - int skip_unnecessary_picks(void) + static int skip_unnecessary_picks(struct object_id *output_oid) { const char *todo_file = rebase_path_todo(); struct strbuf buf = STRBUF_INIT; struct todo_list todo_list = TODO_LIST_INIT; - struct object_id onto_oid, *oid = &onto_oid, *parent_oid; + struct object_id *parent_oid; int fd, i; if (!read_oneliner(&buf, rebase_path_onto(), 0)) return error(_("could not read 'onto'")); - if (get_oid(buf.buf, &onto_oid)) { + if (get_oid(buf.buf, output_oid)) { strbuf_release(&buf); return error(_("need a HEAD to fixup")); } @@@ -4587,9 -4515,9 +4690,9 @@@ if (item->commit->parents->next) break; /* merge commit */ parent_oid = &item->commit->parents->item->object.oid; - if (!oideq(parent_oid, oid)) - if (hashcmp(parent_oid->hash, output_oid->hash)) ++ if (!oideq(parent_oid, output_oid)) break; - oid = &item->commit->object.oid; + oidcpy(output_oid, &item->commit->object.oid); } if (i > 0) { int offset = get_item_line_offset(&todo_list, i); @@@ -4618,15 -4546,114 +4721,114 @@@ todo_list.current = i; if (is_fixup(peek_command(&todo_list, 0))) - record_in_rewritten(oid, peek_command(&todo_list, 0)); + record_in_rewritten(output_oid, peek_command(&todo_list, 0)); } todo_list_release(&todo_list); - printf("%s\n", oid_to_hex(oid)); return 0; } + int complete_action(struct replay_opts *opts, unsigned flags, + const char *shortrevisions, const char *onto_name, + const char *onto, const char *orig_head, const char *cmd, + unsigned autosquash) + { + const char *shortonto, *todo_file = rebase_path_todo(); + struct todo_list todo_list = TODO_LIST_INIT; + struct strbuf *buf = &(todo_list.buf); + struct object_id oid; + struct stat st; + + get_oid(onto, &oid); + shortonto = find_unique_abbrev(&oid, DEFAULT_ABBREV); + + if (!lstat(todo_file, &st) && st.st_size == 0 && + write_message("noop\n", 5, todo_file, 0)) + return -1; + + if (autosquash && rearrange_squash()) + return -1; + + if (cmd && *cmd) + sequencer_add_exec_commands(cmd); + + if (strbuf_read_file(buf, todo_file, 0) < 0) + return error_errno(_("could not read '%s'."), todo_file); + + if (parse_insn_buffer(buf->buf, &todo_list)) { + todo_list_release(&todo_list); + return error(_("unusable todo list: '%s'"), todo_file); + } + + if (count_commands(&todo_list) == 0) { + apply_autostash(opts); + sequencer_remove_state(opts); + todo_list_release(&todo_list); + + return error(_("nothing to do")); + } + + strbuf_addch(buf, '\n'); + strbuf_commented_addf(buf, Q_("Rebase %s onto %s (%d command)", + "Rebase %s onto %s (%d commands)", + count_commands(&todo_list)), + shortrevisions, shortonto, count_commands(&todo_list)); + append_todo_help(0, flags & TODO_LIST_KEEP_EMPTY, buf); + + if (write_message(buf->buf, buf->len, todo_file, 0)) { + todo_list_release(&todo_list); + return -1; + } + + if (copy_file(rebase_path_todo_backup(), todo_file, 0666)) + return error(_("could not copy '%s' to '%s'."), todo_file, + rebase_path_todo_backup()); + + if (transform_todos(flags | TODO_LIST_SHORTEN_IDS)) + return error(_("could not transform the todo list")); + + strbuf_reset(buf); + + if (launch_sequence_editor(todo_file, buf, NULL)) { + apply_autostash(opts); + sequencer_remove_state(opts); + todo_list_release(&todo_list); + + return -1; + } + + strbuf_stripspace(buf, 1); + if (buf->len == 0) { + apply_autostash(opts); + sequencer_remove_state(opts); + todo_list_release(&todo_list); + + return error(_("nothing to do")); + } + + todo_list_release(&todo_list); + + if (check_todo_list()) { + checkout_onto(opts, onto_name, onto, orig_head); + return -1; + } + + if (transform_todos(flags & ~(TODO_LIST_SHORTEN_IDS))) + return error(_("could not transform the todo list")); + + if (opts->allow_ff && skip_unnecessary_picks(&oid)) + return error(_("could not skip unnecessary pick commands")); + + if (checkout_onto(opts, onto_name, oid_to_hex(&oid), orig_head)) + return -1; + ; + if (require_clean_work_tree("rebase", "", 1, 1)) + return -1; + + return sequencer_continue(opts); + } + struct subject2item_entry { struct hashmap_entry entry; int i; diff --combined sequencer.h index c986bc8251,aab280f276..660cff5050 --- a/sequencer.h +++ b/sequencer.h @@@ -1,13 -1,9 +1,14 @@@ #ifndef SEQUENCER_H #define SEQUENCER_H +#include "cache.h" +#include "strbuf.h" + +struct commit; + const char *git_path_commit_editmsg(void); const char *git_path_seq_dir(void); + const char *rebase_path_todo(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) @@@ -62,6 -58,15 +63,15 @@@ struct replay_opts }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } + enum missing_commit_check_level { + MISSING_COMMIT_CHECK_IGNORE = 0, + MISSING_COMMIT_CHECK_WARN, + MISSING_COMMIT_CHECK_ERROR + }; + + int write_message(const void *buf, size_t len, const char *filename, + int append_eol); + /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); int sequencer_pick_revisions(struct replay_opts *opts); @@@ -84,20 -89,17 +94,24 @@@ int sequencer_make_script(FILE *out, in int sequencer_add_exec_commands(const char *command); int transform_todos(unsigned flags); + enum missing_commit_check_level get_missing_commit_check_level(void); int check_todo_list(void); - int skip_unnecessary_picks(void); + int complete_action(struct replay_opts *opts, unsigned flags, + const char *shortrevisions, const char *onto_name, + const char *onto, const char *orig_head, const char *cmd, + unsigned autosquash); int rearrange_squash(void); extern const char sign_off_header[]; -void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); +/* + * Append a signoff to the commit message in "msgbuf". The ignore_footer + * parameter specifies the number of bytes at the end of msgbuf that should + * not be considered at all. I.e., they are not checked for existing trailers, + * and the new signoff will be spliced into the buffer before those bytes. + */ +void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag); + void append_conflicts_hint(struct strbuf *msgbuf); int message_is_empty(const struct strbuf *sb, enum commit_msg_cleanup_mode cleanup_mode); @@@ -110,8 -112,14 +124,14 @@@ int update_head_with_reflog(const struc void commit_post_rewrite(const struct commit *current_head, const struct object_id *new_head); + int prepare_branch_to_be_rebased(struct replay_opts *opts, const char *commit); + #define SUMMARY_INITIAL_COMMIT (1 << 0) #define SUMMARY_SHOW_AUTHOR_DATE (1 << 1) void print_commit_summary(const char *prefix, const struct object_id *oid, unsigned int flags); #endif + + void parse_strategy_opts(struct replay_opts *opts, char *raw_opts); + int write_basic_state(struct replay_opts *opts, const char *head_name, + const char *onto, const char *orig_head); diff --combined strbuf.h index bf18fddb5b,66da9822fd..9981f782b2 --- a/strbuf.h +++ b/strbuf.h @@@ -87,7 -87,7 +87,7 @@@ struct object_id * Initialize the structure. The second parameter can be zero or a bigger * number to allocate memory, in case you want to prevent further reallocs. */ -extern void strbuf_init(struct strbuf *, size_t); +void strbuf_init(struct strbuf *sb, size_t alloc); /** * Release a string buffer and the memory it used. After this call, the @@@ -97,7 -97,7 +97,7 @@@ * To clear a strbuf in preparation for further use without the overhead * of free()ing and malloc()ing again, use strbuf_reset() instead. */ -extern void strbuf_release(struct strbuf *); +void strbuf_release(struct strbuf *sb); /** * Detach the string from the strbuf and returns it; you now own the @@@ -107,7 -107,7 +107,7 @@@ * The strbuf that previously held the string is reset to `STRBUF_INIT` so * it can be reused after calling this function. */ -extern char *strbuf_detach(struct strbuf *, size_t *); +char *strbuf_detach(struct strbuf *sb, size_t *sz); /** * Attach a string to a buffer. You should specify the string to attach, @@@ -117,7 -117,7 +117,7 @@@ * malloc()ed, and after attaching, the pointer cannot be relied upon * anymore, and neither be free()d directly. */ -extern void strbuf_attach(struct strbuf *, void *, size_t, size_t); +void strbuf_attach(struct strbuf *sb, void *str, size_t len, size_t mem); /** * Swap the contents of two string buffers. @@@ -148,7 -148,7 +148,7 @@@ static inline size_t strbuf_avail(cons * This is never a needed operation, but can be critical for performance in * some cases. */ -extern void strbuf_grow(struct strbuf *, size_t); +void strbuf_grow(struct strbuf *sb, size_t amount); /** * Set the length of the buffer to a given value. This function does *not* @@@ -183,30 -183,30 +183,30 @@@ static inline void strbuf_setlen(struc * Strip whitespace from the beginning (`ltrim`), end (`rtrim`), or both side * (`trim`) of a string. */ -extern void strbuf_trim(struct strbuf *); -extern void strbuf_rtrim(struct strbuf *); -extern void strbuf_ltrim(struct strbuf *); +void strbuf_trim(struct strbuf *sb); +void strbuf_rtrim(struct strbuf *sb); +void strbuf_ltrim(struct strbuf *sb); /* Strip trailing directory separators */ -extern void strbuf_trim_trailing_dir_sep(struct strbuf *); +void strbuf_trim_trailing_dir_sep(struct strbuf *sb); /** * Replace the contents of the strbuf with a reencoded form. Returns -1 * on error, 0 on success. */ -extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to); +int strbuf_reencode(struct strbuf *sb, const char *from, const char *to); /** * Lowercase each character in the buffer using `tolower`. */ -extern void strbuf_tolower(struct strbuf *sb); +void strbuf_tolower(struct strbuf *sb); /** * Compare two buffers. Returns an integer less than, equal to, or greater * than zero if the first buffer is found, respectively, to be less than, * to match, or be greater than the second buffer. */ -extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); +int strbuf_cmp(const struct strbuf *first, const struct strbuf *second); /** @@@ -233,38 -233,37 +233,38 @@@ static inline void strbuf_addch(struct /** * Add a character the specified number of times to the buffer. */ -extern void strbuf_addchars(struct strbuf *sb, int c, size_t n); +void strbuf_addchars(struct strbuf *sb, int c, size_t n); /** * Insert data to the given position of the buffer. The remaining contents * will be shifted, not overwritten. */ -extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t); +void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t); /** * Remove given amount of data from a given position of the buffer. */ -extern void strbuf_remove(struct strbuf *, size_t pos, size_t len); +void strbuf_remove(struct strbuf *sb, size_t pos, size_t len); /** * Remove the bytes between `pos..pos+len` and replace it with the given * data. */ -extern void strbuf_splice(struct strbuf *, size_t pos, size_t len, - const void *, size_t); +void strbuf_splice(struct strbuf *sb, size_t pos, size_t len, + const void *data, size_t data_len); /** * Add a NUL-terminated string to the buffer. Each line will be prepended * by a comment character and a blank. */ -extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size); +void strbuf_add_commented_lines(struct strbuf *out, + const char *buf, size_t size); /** * Add data of given length to the buffer. */ -extern void strbuf_add(struct strbuf *, const void *, size_t); +void strbuf_add(struct strbuf *sb, const void *data, size_t len); /** * Add a NUL-terminated string to the buffer. @@@ -283,7 -282,7 +283,7 @@@ static inline void strbuf_addstr(struc /** * Copy the contents of another buffer at the end of the current one. */ -extern void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); +void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); /** * This function can be used to expand a format string containing @@@ -309,13 -308,8 +309,13 @@@ * parameters to the callback, `strbuf_expand()` passes a context pointer, * which can be used by the programmer of the callback as she sees fit. */ -typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context); -extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context); +typedef size_t (*expand_fn_t) (struct strbuf *sb, + const char *placeholder, + void *context); +void strbuf_expand(struct strbuf *sb, + const char *format, + expand_fn_t fn, + void *context); /** * Used as callback for `strbuf_expand()`, expects an array of @@@ -327,9 -321,7 +327,9 @@@ struct strbuf_expand_dict_entry const char *placeholder; const char *value; }; -extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); +size_t strbuf_expand_dict_cb(struct strbuf *sb, + const char *placeholder, + void *context); /** * Append the contents of one strbuf to another, quoting any @@@ -337,29 -329,29 +337,29 @@@ * destination. This is useful for literal data to be fed to either * strbuf_expand or to the *printf family of functions. */ -extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); +void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); /** * Append the given byte size as a human-readable string (i.e. 12.23 KiB, * 3.50 MiB). */ -extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes); +void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes); /** * Add a formatted string to the buffer. */ __attribute__((format (printf,2,3))) -extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); +void strbuf_addf(struct strbuf *sb, const char *fmt, ...); /** * Add a formatted string prepended by a comment character and a * blank to the buffer. */ __attribute__((format (printf, 2, 3))) -extern void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...); +void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...); __attribute__((format (printf,2,0))) -extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); +void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); /** * Add the time specified by `tm`, as formatted by `strftime`. @@@ -369,9 -361,9 +369,9 @@@ * `suppress_tz_name`, when set, expands %Z internally to the empty * string rather than passing it to `strftime`. */ -extern void strbuf_addftime(struct strbuf *sb, const char *fmt, - const struct tm *tm, int tz_offset, - int suppress_tz_name); +void strbuf_addftime(struct strbuf *sb, const char *fmt, + const struct tm *tm, int tz_offset, + int suppress_tz_name); /** * Read a given size of data from a FILE* pointer to the buffer. @@@ -381,14 -373,14 +381,14 @@@ * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline_*()` * family of functions have the same behaviour as well. */ -extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); +size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *file); /** * Read the contents of a given file descriptor. The third argument can be * used to give a hint about the file size, to avoid reallocs. If read fails, * any partial read is undone. */ -extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); +ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint); /** * Read the contents of a given file descriptor partially by using only one @@@ -396,7 -388,7 +396,7 @@@ * file size, to avoid reallocs. Returns the number of new bytes appended to * the sb. */ -extern ssize_t strbuf_read_once(struct strbuf *, int fd, size_t hint); +ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint); /** * Read the contents of a file, specified by its path. The third argument @@@ -404,19 -396,19 +404,19 @@@ * Return the number of bytes read or a negative value if some error * occurred while opening or reading the file. */ -extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); +ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); /** * Read the target of a symbolic link, specified by its path. The third * argument can be used to give a hint about the size, to avoid reallocs. */ -extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); +int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); /** * Write the whole content of the strbuf to the stream not stopping at * NUL bytes. */ -extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream); +ssize_t strbuf_write(struct strbuf *sb, FILE *stream); /** * Read a line from a FILE *, overwriting the existing contents of @@@ -430,10 -422,10 +430,10 @@@ typedef int (*strbuf_getline_fn)(struct strbuf *, FILE *); /* Uses LF as the line terminator */ -extern int strbuf_getline_lf(struct strbuf *sb, FILE *fp); +int strbuf_getline_lf(struct strbuf *sb, FILE *fp); /* Uses NUL as the line terminator */ -extern int strbuf_getline_nul(struct strbuf *sb, FILE *fp); +int strbuf_getline_nul(struct strbuf *sb, FILE *fp); /* * Similar to strbuf_getline_lf(), but additionally treats a CR that @@@ -442,14 -434,14 +442,14 @@@ * that can come from platforms whose native text format is CRLF * terminated. */ -extern int strbuf_getline(struct strbuf *, FILE *); +int strbuf_getline(struct strbuf *sb, FILE *file); /** * Like `strbuf_getline`, but keeps the trailing terminator (if * any) in the buffer. */ -extern int strbuf_getwholeline(struct strbuf *, FILE *, int); +int strbuf_getwholeline(struct strbuf *sb, FILE *file, int term); /** * Like `strbuf_getwholeline`, but operates on a file descriptor. @@@ -457,19 -449,19 +457,19 @@@ * use it unless you need the correct position in the file * descriptor. */ -extern int strbuf_getwholeline_fd(struct strbuf *, int, int); +int strbuf_getwholeline_fd(struct strbuf *sb, int fd, int term); /** * Set the buffer to the path of the current working directory. */ -extern int strbuf_getcwd(struct strbuf *sb); +int strbuf_getcwd(struct strbuf *sb); /** * Add a path to a buffer, converting a relative path to an * absolute one in the process. Symbolic links are not * resolved. */ -extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path); +void strbuf_add_absolute_path(struct strbuf *sb, const char *path); /** * Canonize `path` (make it absolute, resolve symlinks, remove extra @@@ -483,7 -475,7 +483,7 @@@ * Callers that don't mind links should use the more lightweight * strbuf_add_absolute_path() instead. */ -extern void strbuf_add_real_path(struct strbuf *sb, const char *path); +void strbuf_add_real_path(struct strbuf *sb, const char *path); /** @@@ -491,13 -483,13 +491,13 @@@ * normalize_path_copy() for details. If an error occurs, the contents of "sb" * are left untouched, and -1 is returned. */ -extern int strbuf_normalize_path(struct strbuf *sb); +int strbuf_normalize_path(struct strbuf *sb); /** * Strip whitespace from a buffer. The second parameter controls if * comments are considered contents to be removed or not. */ -extern void strbuf_stripspace(struct strbuf *buf, int skip_comments); +void strbuf_stripspace(struct strbuf *buf, int skip_comments); static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) { @@@ -526,8 -518,8 +526,8 @@@ * For lighter-weight alternatives, see string_list_split() and * string_list_split_in_place(). */ -extern struct strbuf **strbuf_split_buf(const char *, size_t, - int terminator, int max); +struct strbuf **strbuf_split_buf(const char *str, size_t len, + int terminator, int max); static inline struct strbuf **strbuf_split_str(const char *str, int terminator, int max) @@@ -536,7 -528,7 +536,7 @@@ } static inline struct strbuf **strbuf_split_max(const struct strbuf *sb, - int terminator, int max) + int terminator, int max) { return strbuf_split_buf(sb->buf, sb->len, terminator, max); } @@@ -557,23 -549,23 +557,23 @@@ static inline struct strbuf **strbuf_sp * 'element1, element2, ..., elementN' * to str. If only one element, just write "element1" to str. */ -extern void strbuf_add_separated_string_list(struct strbuf *str, - const char *sep, - struct string_list *slist); +void strbuf_add_separated_string_list(struct strbuf *str, + const char *sep, + struct string_list *slist); /** * Free a NULL-terminated list of strbufs (for example, the return * values of the strbuf_split*() functions). */ -extern void strbuf_list_free(struct strbuf **); +void strbuf_list_free(struct strbuf **list); /** * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to * the strbuf `sb`. */ -extern void strbuf_add_unique_abbrev(struct strbuf *sb, - const struct object_id *oid, - int abbrev_len); +void strbuf_add_unique_abbrev(struct strbuf *sb, + const struct object_id *oid, + int abbrev_len); /** * Launch the user preferred editor to edit a file and fill the buffer @@@ -582,21 -574,17 +582,23 @@@ * run in. If the buffer is NULL the editor is launched as usual but the * file's contents are not read into the buffer upon completion. */ - int launch_editor(const char *path, - struct strbuf *buffer, -extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env); -extern int launch_sequence_editor(const char *path, struct strbuf *buffer, - const char *const *env); ++int launch_editor(const char *path, struct strbuf *buffer, + const char *const *env); + ++int launch_sequence_editor(const char *path, struct strbuf *buffer, ++ const char *const *env); + -extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size); +void strbuf_add_lines(struct strbuf *sb, + const char *prefix, + const char *buf, + size_t size); /** * Append s to sb, with the characters '<', '>', '&' and '"' converted * into XML entities. */ -extern void strbuf_addstr_xml_quoted(struct strbuf *sb, const char *s); +void strbuf_addstr_xml_quoted(struct strbuf *sb, + const char *s); /** * "Complete" the contents of `sb` by ensuring that either it ends with the @@@ -626,8 -614,8 +628,8 @@@ static inline void strbuf_complete_line * If "allowed" is non-zero, restrict the set of allowed expansions. See * interpret_branch_name() for details. */ -extern void strbuf_branchname(struct strbuf *sb, const char *name, - unsigned allowed); +void strbuf_branchname(struct strbuf *sb, const char *name, + unsigned allowed); /* * Like strbuf_branchname() above, but confirm that the result is @@@ -635,15 -623,15 +637,15 @@@ * * The return value is "0" if the result is valid, and "-1" otherwise. */ -extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name); +int strbuf_check_branch_ref(struct strbuf *sb, const char *name); -extern void strbuf_addstr_urlencode(struct strbuf *, const char *, - int reserved); +void strbuf_addstr_urlencode(struct strbuf *sb, const char *name, + int reserved); __attribute__((format (printf,1,2))) -extern int printf_ln(const char *fmt, ...); +int printf_ln(const char *fmt, ...); __attribute__((format (printf,2,3))) -extern int fprintf_ln(FILE *fp, const char *fmt, ...); +int fprintf_ln(FILE *fp, const char *fmt, ...); char *xstrdup_tolower(const char *); char *xstrdup_toupper(const char *); diff --combined t/t3404-rebase-interactive.sh index ff89b6341a,90c3ca70e6..d60e59ecdb --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@@ -75,6 -75,16 +75,16 @@@ test_expect_success 'rebase --keep-empt test_line_count = 6 actual ' + cat > expect <actual 2>&1 && + test_i18ncmp expect actual + ' + test_expect_success 'rebase -i with the exec command' ' git checkout master && ( @@@ -119,15 -129,6 +129,15 @@@ test_expect_success 'rebase -i with exe ) ' +test_expect_success 'rebase -i sets work tree properly' ' + test_when_finished "rm -rf subdir" && + test_when_finished "test_might_fail git rebase --abort" && + mkdir subdir && + git rebase -x "(cd subdir && git rev-parse --show-toplevel)" HEAD^ \ + >actual && + ! grep "/subdir$" actual +' + test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git checkout master && set_fake_editor && @@@ -273,18 -274,11 +283,18 @@@ test_expect_success 'retain authorship ' test_expect_success 'retain authorship w/ conflicts' ' + oGIT_AUTHOR_NAME=$GIT_AUTHOR_NAME && + test_when_finished "GIT_AUTHOR_NAME=\$oGIT_AUTHOR_NAME" && + git reset --hard twerp && test_commit a conflict a conflict-a && git reset --hard twerp && - GIT_AUTHOR_NAME=AttributeMe \ + + GIT_AUTHOR_NAME=AttributeMe && + export GIT_AUTHOR_NAME && test_commit b conflict b conflict-b && + GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME && + set_fake_editor && test_must_fail git rebase -i conflict-a && echo resolved >conflict && @@@ -525,7 -519,7 +535,7 @@@ test_expect_success 'interrupted squas one=$(git rev-parse HEAD~3) && set_fake_editor && test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && - (echo one; echo two; echo four) > conflict && + test_write_lines one two four > conflict && git add conflict && test_must_fail git rebase --continue && echo resolved > conflict && @@@ -539,10 -533,10 +549,10 @@@ test_expect_success 'interrupted squas one=$(git rev-parse HEAD~3) && set_fake_editor && test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 && - (echo one; echo four) > conflict && + test_write_lines one four > conflict && git add conflict && test_must_fail git rebase --continue && - (echo one; echo two; echo four) > conflict && + test_write_lines one two four > conflict && git add conflict && test_must_fail git rebase --continue && echo resolved > conflict && @@@ -795,15 -789,16 +805,15 @@@ test_expect_success 'always cherry-pic git tag original-no-ff-branch && set_fake_editor && git rebase -i --no-ff A && - touch empty && for p in 0 1 2 do test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) && git diff HEAD~$p original-no-ff-branch~$p > out && - test_cmp empty out + test_must_be_empty out done && test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) && git diff HEAD~3 original-no-ff-branch~3 > out && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'set up commits with funny messages' ' @@@ -1245,7 -1240,7 +1255,7 @@@ rebase_setup_and_clean () test_might_fail git branch -D $1 && test_might_fail git rebase --abort " && - git checkout -b $1 master + git checkout -b $1 ${2:-master} } test_expect_success 'drop' ' @@@ -1422,24 -1417,4 +1432,24 @@@ test_expect_success 'rebase -i --gpg-si test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err ' +test_expect_success 'valid author header after --root swap' ' + rebase_setup_and_clean author-header no-conflict-branch && + set_fake_editor && + git commit --amend --author="Au ${SQ}thor " --no-edit && + git cat-file commit HEAD | grep ^author >expected && + FAKE_LINES="5 1" git rebase -i --root && + git cat-file commit HEAD^ | grep ^author >actual && + test_cmp expected actual +' + +test_expect_success 'valid author header when author contains single quote' ' + rebase_setup_and_clean author-header no-conflict-branch && + set_fake_editor && + git commit --amend --author="Au ${SQ}thor " --no-edit && + git cat-file commit HEAD | grep ^author >expected && + FAKE_LINES="2" git rebase -i HEAD~2 && + git cat-file commit HEAD | grep ^author >actual && + test_cmp expected actual +' + test_done