From: Junio C Hamano Date: Wed, 8 Dec 2010 19:24:12 +0000 (-0800) Subject: Merge branch 'jh/notes-merge' X-Git-Tag: v1.7.4-rc0~82 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/657072f3ac5519f146a947253c9959909b38c293?ds=inline;hp=-c Merge branch 'jh/notes-merge' * jh/notes-merge: (23 commits) Provide 'git merge --abort' as a synonym to 'git reset --merge' cmd_merge(): Parse options before checking MERGE_HEAD Provide 'git notes get-ref' to easily retrieve current notes ref git notes merge: Add testcases for merging notes trees at different fanouts git notes merge: Add another auto-resolving strategy: "cat_sort_uniq" git notes merge: --commit should fail if underlying notes ref has moved git notes merge: List conflicting notes in notes merge commit message git notes merge: Manual conflict resolution, part 2/2 git notes merge: Manual conflict resolution, part 1/2 Documentation: Preliminary docs on 'git notes merge' git notes merge: Add automatic conflict resolvers (ours, theirs, union) git notes merge: Handle real, non-conflicting notes merges builtin/notes.c: Refactor creation of notes commits. git notes merge: Initial implementation handling trivial merges only builtin/notes.c: Split notes ref DWIMmery into a separate function notes.c: Use two newlines (instead of one) when concatenating notes (trivial) t3303: Indent with tabs instead of spaces for consistency notes.h/c: Propagate combine_notes_fn return value to add_note() and beyond notes.h/c: Allow combine_notes functions to remove notes notes.c: Reorder functions in preparation for next commit ... Conflicts: builtin.h --- 657072f3ac5519f146a947253c9959909b38c293 diff --combined Documentation/git-merge.txt index d43416d299,47b59a551d..c1efaaa5c5 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@@ -13,6 -13,7 +13,7 @@@ SYNOPSI [-s ] [-X ] [--[no-]rerere-autoupdate] [-m ] ... 'git merge' HEAD ... + 'git merge' --abort DESCRIPTION ----------- @@@ -47,6 -48,14 +48,14 @@@ The second syntax ( `HEAD` ...`. + The third syntax ("`git merge --abort`") can only be run after the + merge has resulted in conflicts. 'git merge --abort' will abort the + merge process and try to reconstruct the pre-merge state. However, + if there were uncommitted changes when the merge started (and + especially if those changes were further modified after the merge + was started), 'git merge --abort' will in some cases be unable to + reconstruct the original (pre-merge) changes. Therefore: + *Warning*: Running 'git merge' with uncommitted changes is discouraged: while possible, it leaves you in a state that is hard to back out of in the case of a conflict. @@@ -59,19 -68,31 +68,31 @@@ include::merge-options.txt[ -m :: Set the commit message to be used for the merge commit (in case one is created). - - If `--log` is specified, a shortlog of the commits being merged - will be appended to the specified message. - - The 'git fmt-merge-msg' command can be - used to give a good default for automated 'git merge' - invocations. ++ +If `--log` is specified, a shortlog of the commits being merged +will be appended to the specified message. ++ +The 'git fmt-merge-msg' command can be +used to give a good default for automated 'git merge' +invocations. --rerere-autoupdate:: --no-rerere-autoupdate:: Allow the rerere mechanism to update the index with the result of auto-conflict resolution if possible. + --abort:: + Abort the current conflict resolution process, and + try to reconstruct the pre-merge state. + + + If there were uncommitted worktree changes present when the merge + started, 'git merge --abort' will in some cases be unable to + reconstruct these changes. It is therefore recommended to always + commit or stash your changes before running 'git merge'. + + + 'git merge --abort' is equivalent to 'git reset --merge' when + `MERGE_HEAD` is present. + ...:: Commits, usually other branch heads, to merge into our branch. You need at least one . Specifying more than one @@@ -142,7 -163,7 +163,7 @@@ happens i.e. matching `HEAD`. If you tried a merge which resulted in complex conflicts and - want to start over, you can recover with `git reset --merge`. + want to start over, you can recover with `git merge --abort`. HOW CONFLICTS ARE PRESENTED --------------------------- @@@ -213,8 -234,8 +234,8 @@@ After seeing a conflict, you can do tw * Decide not to merge. The only clean-ups you need are to reset the index file to the `HEAD` commit to reverse 2. and to clean - up working tree changes made by 2. and 3.; `git-reset --hard` can - be used for this. + up working tree changes made by 2. and 3.; `git merge --abort` + can be used for this. * Resolve the conflicts. Git will mark the conflicts in the working tree. Edit the files into shape and diff --combined Makefile index 7eb948d7c8,14c0ff166f..91567c651e --- a/Makefile +++ b/Makefile @@@ -68,13 -68,6 +68,13 @@@ all: # # Define NO_MKSTEMPS if you don't have mkstemps in the C library. # +# Define NO_STRTOK_R if you don't have strtok_r in the C library. +# +# Define NO_FNMATCH if you don't have fnmatch in the C library. +# +# Define NO_FNMATCH_CASEFOLD if your fnmatch function doesn't have the +# FNM_CASEFOLD GNU extension. +# # Define NO_LIBGEN_H if you don't have libgen.h. # # Define NEEDS_LIBGEN if your libgen needs -lgen when linking @@@ -275,7 -268,6 +275,7 @@@ STRIP ?= stri # infodir # htmldir # ETC_GITCONFIG (but not sysconfdir) +# ETC_GITATTRIBUTES # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. @@@ -294,11 -286,9 +294,11 @@@ htmldir = share/doc/git-do ifeq ($(prefix),/usr) sysconfdir = /etc ETC_GITCONFIG = $(sysconfdir)/gitconfig +ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes else sysconfdir = $(prefix)/etc ETC_GITCONFIG = etc/gitconfig +ETC_GITATTRIBUTES = etc/gitattributes endif lib = lib # DESTDIR= @@@ -318,7 -308,6 +318,7 @@@ TCL_PATH = tcls TCLTK_PATH = wish PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = +GCOV = gcov export TCL_PATH TCLTK_PATH @@@ -398,15 -387,12 +398,15 @@@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH $(patsubst %.py,%,$(SCRIPT_PYTHON)) \ git-instaweb +ETAGS_TARGET = TAGS + # Empty... EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) +PROGRAM_OBJS += daemon.o PROGRAM_OBJS += fast-import.o PROGRAM_OBJS += imap-send.o PROGRAM_OBJS += shell.o @@@ -422,17 -408,12 +422,17 @@@ TEST_PROGRAMS_NEED_X += test-dat TEST_PROGRAMS_NEED_X += test-delta TEST_PROGRAMS_NEED_X += test-dump-cache-tree TEST_PROGRAMS_NEED_X += test-genrandom +TEST_PROGRAMS_NEED_X += test-line-buffer TEST_PROGRAMS_NEED_X += test-match-trees +TEST_PROGRAMS_NEED_X += test-obj-pool TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-path-utils TEST_PROGRAMS_NEED_X += test-run-command TEST_PROGRAMS_NEED_X += test-sha1 TEST_PROGRAMS_NEED_X += test-sigchain +TEST_PROGRAMS_NEED_X += test-string-pool +TEST_PROGRAMS_NEED_X += test-svn-fe +TEST_PROGRAMS_NEED_X += test-treap TEST_PROGRAMS_NEED_X += test-index-version TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@@ -487,7 -468,6 +487,7 @@@ export PYTHON_PAT LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a +VCSSVN_LIB=vcs-svn/lib.a LIB_H += advice.h LIB_H += archive.h @@@ -502,8 -482,6 +502,8 @@@ LIB_H += compat/bswap. LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += compat/win32/pthread.h +LIB_H += compat/win32/syslog.h +LIB_H += compat/win32/sys/poll.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@@ -525,6 -503,7 +525,7 @@@ LIB_H += mailmap. LIB_H += merge-recursive.h LIB_H += notes.h LIB_H += notes-cache.h + LIB_H += notes-merge.h LIB_H += object.h LIB_H += pack.h LIB_H += pack-refs.h @@@ -615,6 -594,7 +616,7 @@@ LIB_OBJS += merge-recursive. LIB_OBJS += name-hash.o LIB_OBJS += notes.o LIB_OBJS += notes-cache.o + LIB_OBJS += notes-merge.o LIB_OBJS += object.o LIB_OBJS += pack-check.o LIB_OBJS += pack-refs.o @@@ -670,7 -650,6 +672,7 @@@ LIB_OBJS += write_or_die. LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o +LIB_OBJS += zlib.o BUILTIN_OBJS += builtin/add.o BUILTIN_OBJS += builtin/annotate.o @@@ -855,7 -834,6 +857,7 @@@ ifeq ($(uname_S),SunOS NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease ifeq ($(uname_R),5.6) SOCKLEN_T = int NO_HSTRERROR = YesPlease @@@ -997,7 -975,6 +999,7 @@@ ifeq ($(uname_S),IRIX # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease + NO_REGEX = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH = /usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@@ -1016,7 -993,6 +1018,7 @@@ ifeq ($(uname_S),IRIX64 # NO_MMAP. If you suspect that your compiler is not affected by this # issue, comment out the NO_MMAP statement. NO_MMAP = YesPlease + NO_REGEX = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease SHELL_PATH=/usr/gnu/bin/bash NEEDS_LIBGEN = YesPlease @@@ -1061,8 -1037,6 +1063,8 @@@ ifeq ($(uname_S),Windows NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease @@@ -1075,6 -1049,7 +1077,6 @@@ NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease RUNTIME_PREFIX = YesPlease - NO_POSIX_ONLY_PROGRAMS = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_NSEC = YesPlease USE_WIN32_MMAP = YesPlease @@@ -1085,15 -1060,14 +1087,15 @@@ NO_CURL = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS = compat/msvc.o compat/winansi.o compat/win32/pthread.o compat/win32/syslog.o compat/win32/sys/poll.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib PTHREAD_LIBS = @@@ -1107,25 -1081,6 +1109,25 @@@ els endif X = .exe endif +ifeq ($(uname_S),Interix) + NO_SYS_POLL_H = YesPlease + NO_INTTYPES_H = YesPlease + NO_INITGROUPS = YesPlease + NO_IPV6 = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_STRTOUMAX = YesPlease + NO_NSEC = YesPlease + NO_MKSTEMPS = YesPlease + ifeq ($(uname_R),3.5) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + ifeq ($(uname_R),5.2) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif +endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease @@@ -1136,8 -1091,6 +1138,8 @@@ NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease @@@ -1148,6 -1101,7 +1150,6 @@@ NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease RUNTIME_PREFIX = YesPlease - NO_POSIX_ONLY_PROGRAMS = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_NSEC = YesPlease USE_WIN32_MMAP = YesPlease @@@ -1157,15 -1111,10 +1159,15 @@@ NO_REGEX = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32 + ETAGS_TARGET = ETAGS + NO_INET_PTON = YesPlease + NO_INET_NTOP = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \ - compat/win32/pthread.o + COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/sys/poll.o EXTLIBS += -lws2_32 PTHREAD_LIBS = X = .exe @@@ -1280,6 -1229,9 +1282,6 @@@ ifdef ZLIB_PAT endif EXTLIBS += -lz -ifndef NO_POSIX_ONLY_PROGRAMS - PROGRAM_OBJS += daemon.o -endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR @@@ -1369,21 -1321,6 +1371,21 @@@ endi ifdef NO_STRTOULL COMPAT_CFLAGS += -DNO_STRTOULL endif +ifdef NO_STRTOK_R + COMPAT_CFLAGS += -DNO_STRTOK_R + COMPAT_OBJS += compat/strtok_r.o +endif +ifdef NO_FNMATCH + COMPAT_CFLAGS += -Icompat/fnmatch + COMPAT_CFLAGS += -DNO_FNMATCH + COMPAT_OBJS += compat/fnmatch/fnmatch.o +else +ifdef NO_FNMATCH_CASEFOLD + COMPAT_CFLAGS += -Icompat/fnmatch + COMPAT_CFLAGS += -DNO_FNMATCH_CASEFOLD + COMPAT_OBJS += compat/fnmatch/fnmatch.o +endif +endif ifdef NO_SETENV COMPAT_CFLAGS += -DNO_SETENV COMPAT_OBJS += compat/setenv.o @@@ -1402,15 -1339,6 +1404,15 @@@ endi ifdef NO_SYS_SELECT_H BASIC_CFLAGS += -DNO_SYS_SELECT_H endif +ifdef NO_SYS_POLL_H + BASIC_CFLAGS += -DNO_SYS_POLL_H +endif +ifdef NO_INTTYPES_H + BASIC_CFLAGS += -DNO_INTTYPES_H +endif +ifdef NO_INITGROUPS + BASIC_CFLAGS += -DNO_INITGROUPS +endif ifdef NO_MMAP COMPAT_CFLAGS += -DNO_MMAP COMPAT_OBJS += compat/mmap.o @@@ -1448,11 -1376,9 +1450,11 @@@ endi endif ifdef NO_INET_NTOP LIB_OBJS += compat/inet_ntop.o + BASIC_CFLAGS += -DNO_INET_NTOP endif ifdef NO_INET_PTON LIB_OBJS += compat/inet_pton.o + BASIC_CFLAGS += -DNO_INET_PTON endif ifdef NO_ICONV @@@ -1467,10 -1393,6 +1469,10 @@@ ifdef NO_DEFLATE_BOUN BASIC_CFLAGS += -DNO_DEFLATE_BOUND endif +ifdef NO_POSIX_GOODIES + BASIC_CFLAGS += -DNO_POSIX_GOODIES +endif + ifdef BLK_SHA1 SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o @@@ -1528,7 -1450,7 +1530,7 @@@ ifdef NO_REGE endif ifdef USE_NED_ALLOCATOR - COMPAT_CFLAGS += -DUSE_NED_ALLOCATOR -DOVERRIDE_STRDUP -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR -Icompat/nedmalloc + COMPAT_CFLAGS += -Icompat/nedmalloc COMPAT_OBJS += compat/nedmalloc/nedmalloc.o endif @@@ -1565,7 -1487,6 +1567,7 @@@ ifndef QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; QUIET_LNCP = @echo ' ' LN/CP $@; + QUIET_GCOV = @echo ' ' GCOV $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@@ -1583,7 -1504,6 +1585,7 @@@ endi SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) +ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) @@@ -1821,11 -1741,7 +1823,11 @@@ ifndef NO_CUR endif XDIFF_OBJS = xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ xdiff/xmerge.o xdiff/xpatience.o -OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) +VCSSVN_OBJS = vcs-svn/string_pool.o vcs-svn/line_buffer.o \ + vcs-svn/repo_tree.o vcs-svn/fast_export.o vcs-svn/svndump.o +VCSSVN_TEST_OBJS = test-obj-pool.o test-string-pool.o \ + test-line-buffer.o test-treap.o +OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) @@@ -1940,20 -1856,12 +1942,20 @@@ builtin/prune.o builtin/reflog.o reacha builtin/commit.o builtin/revert.o wt-status.o: wt-status.h builtin/tar-tree.o archive-tar.o: tar.h builtin/pack-objects.o: thread-utils.h +connect.o transport.o http-backend.o: url.h http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h -http.o http-walker.o http-push.o remote-curl.o: http.h +http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h xdiff-interface.o $(XDIFF_OBJS): \ xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h + +$(VCSSVN_OBJS) $(VCSSVN_TEST_OBJS): $(LIB_H) \ + vcs-svn/obj_pool.h vcs-svn/trp.h vcs-svn/string_pool.h \ + vcs-svn/line_buffer.h vcs-svn/repo_tree.h vcs-svn/fast_export.h \ + vcs-svn/svndump.h + +test-svn-fe.o: vcs-svn/svndump.h endif exec_cmd.s exec_cmd.o: EXTRA_CPPFLAGS = \ @@@ -1966,29 -1874,18 +1968,29 @@@ builtin/init-db.s builtin/init-db.o: EX config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' -http.s http.o: EXTRA_CPPFLAGS = -DGIT_USER_AGENT='"git/$(GIT_VERSION)"' +attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' + +http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT http-walker.s http-walker.o: EXTRA_CPPFLAGS = -DNO_EXPAT endif +ifdef NO_REGEX +compat/regex/regex.o: EXTRA_CPPFLAGS = -DGAWK -DNO_MBSUPPORT +endif + +ifdef USE_NED_ALLOCATOR +compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ + -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR +endif + git-%$X: %.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ - $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) + $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO) git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ @@@ -2013,8 -1910,6 +2015,8 @@@ $(LIB_FILE): $(LIB_OBJS $(XDIFF_LIB): $(XDIFF_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS) +$(VCSSVN_LIB): $(VCSSVN_OBJS) + $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(VCSSVN_OBJS) doc: $(MAKE) -C Documentation all @@@ -2031,11 -1926,11 +2033,11 @@@ info pdf: $(MAKE) -C Documentation pdf -TAGS: - $(RM) TAGS - $(FIND) . -name '*.[hcS]' -print | xargs etags -a +$(ETAGS_TARGET): FORCE + $(RM) $(ETAGS_TARGET) + $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET) -tags: +tags: FORCE $(RM) tags $(FIND) . -name '*.[hcS]' -print | xargs ctags -a @@@ -2044,7 -1939,7 +2046,7 @@@ cscope $(FIND) . -name '*.[hcS]' -print | xargs cscope -b ### Detect prefix changes -TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\ +TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\ $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) GIT-CFLAGS: FORCE @@@ -2113,18 -2008,12 +2115,18 @@@ test-date$X: date.o ctype. test-delta$X: diff-delta.o patch-delta.o +test-line-buffer$X: vcs-svn/lib.a + test-parse-options$X: parse-options.o +test-string-pool$X: vcs-svn/lib.a + +test-svn-fe$X: vcs-svn/lib.a + .PRECIOUS: $(TEST_OBJS) test-%$X: test-%.o $(GITLIBS) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) check-sha1:: test-sha1$X ./test-sha1.sh @@@ -2188,19 -2077,10 +2190,19 @@@ endi bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { test "$$bindir/" = "$$execdir/" || \ - { $(RM) "$$execdir/git$X" && \ + for p in git$X $(filter $(install_bindir_programs),$(ALL_PROGRAMS)); do \ + $(RM) "$$execdir/$$p" && \ test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ - ln "$$bindir/git$X" "$$execdir/git$X" 2>/dev/null || \ - cp "$$bindir/git$X" "$$execdir/git$X"; } ; } && \ + ln "$$bindir/$$p" "$$execdir/$$p" 2>/dev/null || \ + cp "$$bindir/$$p" "$$execdir/$$p" || exit; \ + done; \ + } && \ + for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \ + $(RM) "$$bindir/$$p" && \ + ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ + cp "$$bindir/git$X" "$$bindir/$$p" || exit; \ + done && \ for p in $(BUILT_INS); do \ $(RM) "$$execdir/$$p" && \ ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ @@@ -2299,13 -2179,13 +2301,13 @@@ distclean: clea $(RM) configure clean: - $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ - builtin/*.o $(LIB_FILE) $(XDIFF_LIB) + $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ + builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers $(RM) -r $(dep_dirs) - $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* + $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache $(RM) -r $(GIT_TARNAME) .doc-tmp-dir @@@ -2329,7 -2209,7 +2331,7 @@@ endi .PHONY: all install clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell -.PHONY: FORCE TAGS tags cscope +.PHONY: FORCE cscope ### Check documentation # @@@ -2403,18 -2283,11 +2405,18 @@@ coverage $(MAKE) coverage-build $(MAKE) coverage-report +object_dirs := $(sort $(dir $(OBJECTS))) coverage-clean: - rm -f *.gcda *.gcno + $(RM) $(addsuffix *.gcov,$(object_dirs)) + $(RM) $(addsuffix *.gcda,$(object_dirs)) + $(RM) $(addsuffix *.gcno,$(object_dirs)) + $(RM) coverage-untested-functions + $(RM) -r cover_db/ + $(RM) -r cover_db_html/ COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov +GCOVFLAGS = --preserve-paths --branch-probabilities --all-blocks coverage-build: coverage-clean $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all @@@ -2422,17 -2295,7 +2424,17 @@@ -j1 test coverage-report: - gcov -b *.c + $(QUIET_GCOV)for dir in $(object_dirs); do \ + $(GCOV) $(GCOVFLAGS) --object-directory=$$dir $$dir*.c || exit; \ + done + +coverage-untested-functions: coverage-report grep '^function.*called 0 ' *.c.gcov \ | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \ - | tee coverage-untested-functions + > coverage-untested-functions + +cover_db: coverage-report + gcov2perl -db cover_db *.gcov + +cover_db_html: cover_db + cover -report html -outputdir cover_db_html cover_db diff --combined builtin.h index 8dd4569b3c,908d85064d..c3e5db2685 --- a/builtin.h +++ b/builtin.h @@@ -7,16 -7,17 +7,16 @@@ #include "commit.h" #include "notes.h" +#define DEFAULT_MERGE_LOG_LEN 20 + extern const char git_version_string[]; extern const char git_usage_string[]; extern const char git_more_info_string[]; -extern void list_common_cmds_help(void); -extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); -extern int fmt_merge_msg(int merge_summary, struct strbuf *in, - struct strbuf *out); -extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out); +extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len); - extern int commit_notes(struct notes_tree *t, const char *msg); + extern void commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { struct notes_tree **trees; @@@ -36,7 -37,7 +36,7 @@@ void finish_copy_notes_for_rewrite(stru extern int check_pager_config(const char *cmd); -extern int textconv_object(const char *path, const unsigned char *sha1, char **buf, unsigned long *buf_size); +extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, char **buf, unsigned long *buf_size); extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); diff --combined builtin/merge.c index c24a7be020,0539f7c912..3921cd3040 --- a/builtin/merge.c +++ b/builtin/merge.c @@@ -42,7 -42,7 +42,7 @@@ static const char * const builtin_merge NULL }; -static int show_diffstat = 1, option_log, squash; +static int show_diffstat = 1, shortlog_len, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only; static int allow_trivial = 1, have_message; @@@ -54,9 -54,9 +54,10 @@@ static size_t use_strategies_nr, use_st static const char **xopts; static size_t xopts_nr, xopts_alloc; static const char *branch; +static int option_renormalize; static int verbosity; static int allow_rerere_auto; + static int abort_current_merge; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@@ -132,7 -132,6 +133,7 @@@ static struct strategy *get_strategy(co ret = xcalloc(1, sizeof(struct strategy)); ret->name = xstrdup(name); + ret->attr = NO_TRIVIAL; return ret; } @@@ -177,9 -176,8 +178,9 @@@ static struct option builtin_merge_opti OPT_BOOLEAN(0, "stat", &show_diffstat, "show a diffstat at the end of the merge"), OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), + { OPTION_INTEGER, 0, "log", &shortlog_len, "n", + "add (at most ) entries from shortlog to merge commit message", + PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, OPT_BOOLEAN(0, "squash", &squash, "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, @@@ -197,6 -195,8 +198,8 @@@ "message to be used for the merge commit (if any)", option_parse_message), OPT__VERBOSITY(&verbosity), + OPT_BOOLEAN(0, "abort", &abort_current_merge, + "abort the current in-progress merge"), OPT_END() }; @@@ -234,24 -234,6 +237,24 @@@ static void save_state(void die("not a valid object: %s", buffer.buf); } +static void read_empty(unsigned const char *sha1, int verbose) +{ + int i = 0; + const char *args[7]; + + args[i++] = "read-tree"; + if (verbose) + args[i++] = "-v"; + args[i++] = "-m"; + args[i++] = "-u"; + args[i++] = EMPTY_TREE_SHA1_HEX; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + if (run_command_v_opt(args, RUN_GIT_CMD)) + die("read-tree failed"); +} + static void reset_hard(unsigned const char *sha1, int verbose) { int i = 0; @@@ -421,7 -403,7 +424,7 @@@ static void merge_name(const char *remo goto cleanup; } if (!prefixcmp(found_ref, "refs/remotes/")) { - strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", + strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); goto cleanup; } @@@ -458,7 -440,7 +461,7 @@@ strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 0, NULL)) { + if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", sha1_to_hex(remote_head->sha1), @@@ -507,8 -489,7 +510,8 @@@ static int git_merge_config(const char buf = xstrdup(v); argc = split_cmdline(buf, &argv); if (argc < 0) - die("Bad branch.%s.mergeoptions string", branch); + die("Bad branch.%s.mergeoptions string: %s", branch, + split_cmdline_strerror(argc)); argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); argc++; @@@ -523,17 -504,8 +526,17 @@@ return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); + else if (!strcmp(k, "merge.renormalize")) + option_renormalize = git_config_bool(k, v); + else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(k, v, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", k, v); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; + return 0; + } return git_diff_ui_config(k, v, cb); } @@@ -655,11 -627,18 +658,11 @@@ static int try_merge_strategy(const cha if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; - for (x = 0; x < xopts_nr; x++) { - if (!strcmp(xopts[x], "ours")) - o.recursive_variant = MERGE_RECURSIVE_OURS; - else if (!strcmp(xopts[x], "theirs")) - o.recursive_variant = MERGE_RECURSIVE_THEIRS; - else if (!strcmp(xopts[x], "subtree")) - o.subtree_shift = ""; - else if (!prefixcmp(xopts[x], "subtree=")) - o.subtree_shift = xopts[x]+8; - else + o.renormalize = option_renormalize; + + for (x = 0; x < xopts_nr; x++) + if (parse_merge_opt(&o, xopts[x])) die("Unknown option for merge-recursive: -X%s", xopts[x]); - } o.branch1 = head_arg; o.branch2 = remoteheads->item->util; @@@ -728,7 -707,7 +731,7 @@@ int checkout_fast_forward(const unsigne opts.verbose_update = 1; opts.merge = 1; opts.fn = twoway_merge; - opts.msgs = get_porcelain_error_msgs(); + setup_unpack_trees_porcelain(&opts, "merge"); trees[nr_trees] = parse_tree_indirect(head); if (!trees[nr_trees++]) @@@ -840,7 -819,7 +843,7 @@@ static int finish_automerge(struct comm return 0; } -static int suggest_conflicts(void) +static int suggest_conflicts(int renormalizing) { FILE *fp; int pos; @@@ -919,22 -898,6 +922,6 @@@ int cmd_merge(int argc, const char **ar const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; - if (read_cache_unmerged()) { - die_resolve_conflict("merge"); - } - if (file_exists(git_path("MERGE_HEAD"))) { - /* - * There is no unmerged entry, don't advise 'git - * add/rm ', just 'git commit'. - */ - if (advice_resolve_conflict) - die("You have not concluded your merge (MERGE_HEAD exists).\n" - "Please, commit your changes before you can merge."); - else - die("You have not concluded your merge (MERGE_HEAD exists)."); - } - - resolve_undo_clear(); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@@ -953,6 -916,34 +940,34 @@@ argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + + if (abort_current_merge) { + int nargc = 2; + const char *nargv[] = {"reset", "--merge", NULL}; + + if (!file_exists(git_path("MERGE_HEAD"))) + die("There is no merge to abort (MERGE_HEAD missing)."); + + /* Invoke 'git reset --merge' */ + return cmd_reset(nargc, nargv, prefix); + } + + if (read_cache_unmerged()) + die_resolve_conflict("merge"); + + if (file_exists(git_path("MERGE_HEAD"))) { + /* + * There is no unmerged entry, don't advise 'git + * add/rm ', just 'git commit'. + */ + if (advice_resolve_conflict) + die("You have not concluded your merge (MERGE_HEAD exists).\n" + "Please, commit your changes before you can merge."); + else + die("You have not concluded your merge (MERGE_HEAD exists)."); + } + resolve_undo_clear(); + if (verbosity < 0) show_diffstat = 0; @@@ -1003,7 -994,7 +1018,7 @@@ die("%s - not something we can merge", argv[0]); update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, DIE_ON_ERR); - reset_hard(remote_head->sha1, 0); + read_empty(remote_head->sha1, 0); return 0; } else { struct strbuf merge_names = STRBUF_INIT; @@@ -1022,12 -1013,14 +1037,12 @@@ for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); - if (have_message && option_log) - fmt_merge_msg_shortlog(&merge_names, &merge_msg); - else if (!have_message) - fmt_merge_msg(option_log, &merge_names, &merge_msg); - - - if (!(have_message && !option_log) && merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); + if (!have_message || shortlog_len) { + fmt_merge_msg(&merge_names, &merge_msg, !have_message, + shortlog_len); + if (merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len - 1); + } } if (head_invalid || !argc) @@@ -1323,5 -1316,5 +1338,5 @@@ "stopped before committing as requested\n"); return 0; } else - return suggest_conflicts(); + return suggest_conflicts(option_renormalize); } diff --combined builtin/notes.c index c85cbf5a47,f5abf7aa6e..4d5556e2cb --- a/builtin/notes.c +++ b/builtin/notes.c @@@ -17,6 -17,7 +17,7 @@@ #include "run-command.h" #include "parse-options.h" #include "string-list.h" + #include "notes-merge.h" static const char * const git_notes_usage[] = { "git notes [--ref ] [list []]", @@@ -25,8 -26,12 +26,12 @@@ "git notes [--ref ] append [-m | -F | (-c | -C) ] []", "git notes [--ref ] edit []", "git notes [--ref ] show []", + "git notes [--ref ] merge [-v | -q] [-s ] ", + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", "git notes [--ref ] remove []", "git notes [--ref ] prune [-n | -v]", + "git notes [--ref ] get-ref", NULL }; @@@ -61,6 -66,13 +66,13 @@@ static const char * const git_notes_sho NULL }; + static const char * const git_notes_merge_usage[] = { + "git notes merge [] ", + "git notes merge --commit []", + "git notes merge --abort []", + NULL + }; + static const char * const git_notes_remove_usage[] = { "git notes remove []", NULL @@@ -71,6 -83,11 +83,11 @@@ static const char * const git_notes_pru NULL }; + static const char * const git_notes_get_ref_usage[] = { + "git notes get-ref", + NULL + }; + static const char note_template[] = "\n" "#\n" @@@ -83,6 -100,16 +100,16 @@@ struct msg_arg struct strbuf buf; }; + static void expand_notes_ref(struct strbuf *sb) + { + if (!prefixcmp(sb->buf, "refs/notes/")) + return; /* we're happy */ + else if (!prefixcmp(sb->buf, "notes/")) + strbuf_insert(sb, 0, "refs/", 5); + else + strbuf_insert(sb, 0, "refs/notes/", 11); + } + static int list_each_note(const unsigned char *object_sha1, const unsigned char *note_sha1, char *note_path, void *cb_data) @@@ -271,18 -298,17 +298,17 @@@ static int parse_reedit_arg(const struc return parse_reuse_arg(opt, arg, unset); } - int commit_notes(struct notes_tree *t, const char *msg) + void commit_notes(struct notes_tree *t, const char *msg) { - struct commit_list *parent; - unsigned char tree_sha1[20], prev_commit[20], new_commit[20]; struct strbuf buf = STRBUF_INIT; + unsigned char commit_sha1[20]; if (!t) t = &default_notes_tree; if (!t->initialized || !t->ref || !*t->ref) die("Cannot commit uninitialized/unreferenced notes tree"); if (!t->dirty) - return 0; /* don't have to commit an unchanged tree */ + return; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ @@@ -290,27 -316,10 +316,10 @@@ if (buf.buf[buf.len - 1] != '\n') strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - /* Convert notes tree to tree object */ - if (write_notes_tree(t, tree_sha1)) - die("Failed to write current notes tree to database"); - - /* Create new commit for the tree object */ - if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */ - parent = xmalloc(sizeof(*parent)); - parent->item = lookup_commit(prev_commit); - parent->next = NULL; - } else { - hashclr(prev_commit); - parent = NULL; - } - if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL)) - die("Failed to commit notes tree to database"); - - /* Update notes ref with new commit */ - update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR); + create_notes_commit(t, NULL, buf.buf + 7, commit_sha1); + update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); strbuf_release(&buf); - return 0; } combine_notes_fn parse_combine_notes_fn(const char *v) @@@ -321,6 -330,8 +330,8 @@@ return combine_notes_ignore; else if (!strcasecmp(v, "concatenate")) return combine_notes_concatenate; + else if (!strcasecmp(v, "cat_sort_uniq")) + return combine_notes_cat_sort_uniq; else return NULL; } @@@ -538,7 -549,7 +549,7 @@@ static int add(int argc, const char **a { OPTION_CALLBACK, 'C', "reuse-message", &msg, "OBJECT", "reuse specified note object", PARSE_OPT_NONEG, parse_reuse_arg}, - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_END() }; @@@ -573,8 -584,8 +584,8 @@@ if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", "add"); @@@ -594,7 -605,7 +605,7 @@@ static int copy(int argc, const char ** struct notes_tree *t; const char *rewrite_cmd = NULL; struct option options[] = { - OPT_BOOLEAN('f', "force", &force, "replace existing notes"), + OPT__FORCE(&force, "replace existing notes"), OPT_BOOLEAN(0, "stdin", &from_stdin, "read objects from stdin"), OPT_STRING(0, "for-rewrite", &rewrite_cmd, "command", "load rewriting config for (implies " @@@ -653,7 -664,8 +664,8 @@@ goto out; } - add_note(t, object, from_note, combine_notes_overwrite); + if (add_note(t, object, from_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); commit_notes(t, "Notes added by 'git notes copy'"); out: free_notes(t); @@@ -712,8 -724,8 +724,8 @@@ static int append_edit(int argc, const if (is_null_sha1(new_note)) remove_note(t, object); - else - add_note(t, object, new_note, combine_notes_overwrite); + else if (add_note(t, object, new_note, combine_notes_overwrite)) + die("BUG: combine_notes_overwrite failed"); snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'", is_null_sha1(new_note) ? "removed" : "added", argv[0]); @@@ -761,6 -773,180 +773,180 @@@ static int show(int argc, const char ** return retval; } + static int merge_abort(struct notes_merge_options *o) + { + int ret = 0; + + /* + * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call + * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE. + */ + + if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0)) + ret += error("Failed to delete ref NOTES_MERGE_PARTIAL"); + if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF)) + ret += error("Failed to delete ref NOTES_MERGE_REF"); + if (notes_merge_abort(o)) + ret += error("Failed to remove 'git notes merge' worktree"); + return ret; + } + + static int merge_commit(struct notes_merge_options *o) + { + struct strbuf msg = STRBUF_INIT; + unsigned char sha1[20], parent_sha1[20]; + struct notes_tree *t; + struct commit *partial; + struct pretty_print_context pretty_ctx; + + /* + * Read partial merge result from .git/NOTES_MERGE_PARTIAL, + * and target notes ref from .git/NOTES_MERGE_REF. + */ + + if (get_sha1("NOTES_MERGE_PARTIAL", sha1)) + die("Failed to read ref NOTES_MERGE_PARTIAL"); + else if (!(partial = lookup_commit_reference(sha1))) + die("Could not find commit from NOTES_MERGE_PARTIAL."); + else if (parse_commit(partial)) + die("Could not parse commit from NOTES_MERGE_PARTIAL."); + + if (partial->parents) + hashcpy(parent_sha1, partial->parents->item->object.sha1); + else + hashclr(parent_sha1); + + t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); + + o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0); + if (!o->local_ref) + die("Failed to resolve NOTES_MERGE_REF"); + + if (notes_merge_commit(o, t, partial, sha1)) + die("Failed to finalize notes merge"); + + /* Reuse existing commit message in reflog message */ + memset(&pretty_ctx, 0, sizeof(pretty_ctx)); + format_commit_message(partial, "%s", &msg, &pretty_ctx); + strbuf_trim(&msg); + strbuf_insert(&msg, 0, "notes: ", 7); + update_ref(msg.buf, o->local_ref, sha1, + is_null_sha1(parent_sha1) ? NULL : parent_sha1, + 0, DIE_ON_ERR); + + free_notes(t); + strbuf_release(&msg); + return merge_abort(o); + } + + static int merge(int argc, const char **argv, const char *prefix) + { + struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT; + unsigned char result_sha1[20]; + struct notes_tree *t; + struct notes_merge_options o; + int do_merge = 0, do_commit = 0, do_abort = 0; + int verbosity = 0, result; + const char *strategy = NULL; + struct option options[] = { + OPT_GROUP("General options"), + OPT__VERBOSITY(&verbosity), + OPT_GROUP("Merge options"), + OPT_STRING('s', "strategy", &strategy, "strategy", + "resolve notes conflicts using the given strategy " + "(manual/ours/theirs/union/cat_sort_uniq)"), + OPT_GROUP("Committing unmerged notes"), + { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL, + "finalize notes merge by committing unmerged notes", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_GROUP("Aborting notes merge resolution"), + { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL, + "abort notes merge", + PARSE_OPT_NOARG | PARSE_OPT_NONEG }, + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_notes_merge_usage, 0); + + if (strategy || do_commit + do_abort == 0) + do_merge = 1; + if (do_merge + do_commit + do_abort != 1) { + error("cannot mix --commit, --abort or -s/--strategy"); + usage_with_options(git_notes_merge_usage, options); + } + + if (do_merge && argc != 1) { + error("Must specify a notes ref to merge"); + usage_with_options(git_notes_merge_usage, options); + } else if (!do_merge && argc) { + error("too many parameters"); + usage_with_options(git_notes_merge_usage, options); + } + + init_notes_merge_options(&o); + o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT; + + if (do_abort) + return merge_abort(&o); + if (do_commit) + return merge_commit(&o); + + o.local_ref = default_notes_ref(); + strbuf_addstr(&remote_ref, argv[0]); + expand_notes_ref(&remote_ref); + o.remote_ref = remote_ref.buf; + + if (strategy) { + if (!strcmp(strategy, "manual")) + o.strategy = NOTES_MERGE_RESOLVE_MANUAL; + else if (!strcmp(strategy, "ours")) + o.strategy = NOTES_MERGE_RESOLVE_OURS; + else if (!strcmp(strategy, "theirs")) + o.strategy = NOTES_MERGE_RESOLVE_THEIRS; + else if (!strcmp(strategy, "union")) + o.strategy = NOTES_MERGE_RESOLVE_UNION; + else if (!strcmp(strategy, "cat_sort_uniq")) + o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ; + else { + error("Unknown -s/--strategy: %s", strategy); + usage_with_options(git_notes_merge_usage, options); + } + } + + t = init_notes_check("merge"); + + strbuf_addf(&msg, "notes: Merged notes from %s into %s", + remote_ref.buf, default_notes_ref()); + strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */ + + result = notes_merge(&o, t, result_sha1); + + if (result >= 0) /* Merge resulted (trivially) in result_sha1 */ + /* Update default notes ref with new commit */ + update_ref(msg.buf, default_notes_ref(), result_sha1, NULL, + 0, DIE_ON_ERR); + else { /* Merge has unresolved conflicts */ + /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ + update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL, + 0, DIE_ON_ERR); + /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ + if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) + die("Failed to store link to current notes ref (%s)", + default_notes_ref()); + printf("Automatic notes merge failed. Fix conflicts in %s and " + "commit the result with 'git notes merge --commit', or " + "abort the merge with 'git notes merge --abort'.\n", + git_path(NOTES_MERGE_WORKTREE)); + } + + free_notes(t); + strbuf_release(&remote_ref); + strbuf_release(&msg); + return result < 0; /* return non-zero on conflicts */ + } + static int remove_cmd(int argc, const char **argv, const char *prefix) { struct option options[] = { @@@ -769,7 -955,6 +955,7 @@@ const char *object_ref; struct notes_tree *t; unsigned char object[20]; + int retval; argc = parse_options(argc, argv, prefix, options, git_notes_remove_usage, 0); @@@ -786,17 -971,12 +972,17 @@@ t = init_notes_check("remove"); - fprintf(stderr, "Removing note for object %s\n", sha1_to_hex(object)); - remove_note(t, object); + retval = remove_note(t, object); + if (retval) + fprintf(stderr, "Object %s has no note\n", sha1_to_hex(object)); + else { + fprintf(stderr, "Removing note for object %s\n", + sha1_to_hex(object)); - commit_notes(t, "Notes removed by 'git notes remove'"); + commit_notes(t, "Notes removed by 'git notes remove'"); + } free_notes(t); - return 0; + return retval; } static int prune(int argc, const char **argv, const char *prefix) @@@ -804,8 -984,9 +990,8 @@@ struct notes_tree *t; int show_only = 0, verbose = 0; struct option options[] = { - OPT_BOOLEAN('n', "dry-run", &show_only, - "do not remove, show only"), - OPT_BOOLEAN('v', "verbose", &verbose, "report pruned notes"), + OPT__DRY_RUN(&show_only, "do not remove, show only"), + OPT__VERBOSE(&verbose, "report pruned notes"), OPT_END() }; @@@ -827,6 -1008,21 +1013,21 @@@ return 0; } + static int get_ref(int argc, const char **argv, const char *prefix) + { + struct option options[] = { OPT_END() }; + argc = parse_options(argc, argv, prefix, options, + git_notes_get_ref_usage, 0); + + if (argc) { + error("too many parameters"); + usage_with_options(git_notes_get_ref_usage, options); + } + + puts(default_notes_ref()); + return 0; + } + int cmd_notes(int argc, const char **argv, const char *prefix) { int result; @@@ -843,13 -1039,8 +1044,8 @@@ if (override_notes_ref) { struct strbuf sb = STRBUF_INIT; - if (!prefixcmp(override_notes_ref, "refs/notes/")) - /* we're happy */; - else if (!prefixcmp(override_notes_ref, "notes/")) - strbuf_addstr(&sb, "refs/"); - else - strbuf_addstr(&sb, "refs/notes/"); strbuf_addstr(&sb, override_notes_ref); + expand_notes_ref(&sb); setenv("GIT_NOTES_REF", sb.buf, 1); strbuf_release(&sb); } @@@ -864,10 -1055,14 +1060,14 @@@ result = append_edit(argc, argv, prefix); else if (!strcmp(argv[0], "show")) result = show(argc, argv, prefix); + else if (!strcmp(argv[0], "merge")) + result = merge(argc, argv, prefix); else if (!strcmp(argv[0], "remove")) result = remove_cmd(argc, argv, prefix); else if (!strcmp(argv[0], "prune")) result = prune(argc, argv, prefix); + else if (!strcmp(argv[0], "get-ref")) + result = get_ref(argc, argv, prefix); else { result = error("Unknown subcommand: %s", argv[0]); usage_with_options(git_notes_usage, options); diff --combined notes.c index 70d00135eb,96cde42134..a013c1bc63 --- a/notes.c +++ b/notes.c @@@ -149,86 -149,6 +149,6 @@@ static struct leaf_node *note_tree_find return NULL; } - /* - * To insert a leaf_node: - * Search to the tree location appropriate for the given leaf_node's key: - * - If location is unused (NULL), store the tweaked pointer directly there - * - If location holds a note entry that matches the note-to-be-inserted, then - * combine the two notes (by calling the given combine_notes function). - * - If location holds a note entry that matches the subtree-to-be-inserted, - * then unpack the subtree-to-be-inserted into the location. - * - If location holds a matching subtree entry, unpack the subtree at that - * location, and restart the insert operation from that level. - * - Else, create a new int_node, holding both the node-at-location and the - * node-to-be-inserted, and store the new int_node into the location. - */ - static void note_tree_insert(struct notes_tree *t, struct int_node *tree, - unsigned char n, struct leaf_node *entry, unsigned char type, - combine_notes_fn combine_notes) - { - struct int_node *new_node; - struct leaf_node *l; - void **p = note_tree_search(t, &tree, &n, entry->key_sha1); - - assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ - l = (struct leaf_node *) CLR_PTR_TYPE(*p); - switch (GET_PTR_TYPE(*p)) { - case PTR_TYPE_NULL: - assert(!*p); - *p = SET_PTR_TYPE(entry, type); - return; - case PTR_TYPE_NOTE: - switch (type) { - case PTR_TYPE_NOTE: - if (!hashcmp(l->key_sha1, entry->key_sha1)) { - /* skip concatenation if l == entry */ - if (!hashcmp(l->val_sha1, entry->val_sha1)) - return; - - if (combine_notes(l->val_sha1, entry->val_sha1)) - die("failed to combine notes %s and %s" - " for object %s", - sha1_to_hex(l->val_sha1), - sha1_to_hex(entry->val_sha1), - sha1_to_hex(l->key_sha1)); - free(entry); - return; - } - break; - case PTR_TYPE_SUBTREE: - if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, - entry->key_sha1)) { - /* unpack 'entry' */ - load_subtree(t, entry, tree, n); - free(entry); - return; - } - break; - } - break; - case PTR_TYPE_SUBTREE: - if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { - /* unpack 'l' and restart insert */ - *p = NULL; - load_subtree(t, l, tree, n); - free(l); - note_tree_insert(t, tree, n, entry, type, - combine_notes); - return; - } - break; - } - - /* non-matching leaf_node */ - assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || - GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); - new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); - note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), - combine_notes); - *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); - note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); - } - /* * How to consolidate an int_node: * If there are > 1 non-NULL entries, give up and return non-zero. @@@ -263,13 -183,11 +183,13 @@@ static int note_tree_consolidate(struc * To remove a leaf_node: * Search to the tree location appropriate for the given leaf_node's key: * - If location does not hold a matching entry, abort and do nothing. + * - Copy the matching entry's value into the given entry. * - Replace the matching leaf_node with a NULL entry (and free the leaf_node). * - Consolidate int_nodes repeatedly, while walking up the tree towards root. */ -static void note_tree_remove(struct notes_tree *t, struct int_node *tree, - unsigned char n, struct leaf_node *entry) +static void note_tree_remove(struct notes_tree *t, + struct int_node *tree, unsigned char n, + struct leaf_node *entry) { struct leaf_node *l; struct int_node *parent_stack[20]; @@@ -284,7 -202,6 +204,7 @@@ return; /* key mismatch, nothing to remove */ /* we have found a matching entry */ + hashcpy(entry->val_sha1, l->val_sha1); free(l); *p = SET_PTR_TYPE(NULL, PTR_TYPE_NULL); @@@ -305,6 -222,93 +225,93 @@@ i--; } + /* + * To insert a leaf_node: + * Search to the tree location appropriate for the given leaf_node's key: + * - If location is unused (NULL), store the tweaked pointer directly there + * - If location holds a note entry that matches the note-to-be-inserted, then + * combine the two notes (by calling the given combine_notes function). + * - If location holds a note entry that matches the subtree-to-be-inserted, + * then unpack the subtree-to-be-inserted into the location. + * - If location holds a matching subtree entry, unpack the subtree at that + * location, and restart the insert operation from that level. + * - Else, create a new int_node, holding both the node-at-location and the + * node-to-be-inserted, and store the new int_node into the location. + */ + static int note_tree_insert(struct notes_tree *t, struct int_node *tree, + unsigned char n, struct leaf_node *entry, unsigned char type, + combine_notes_fn combine_notes) + { + struct int_node *new_node; + struct leaf_node *l; + void **p = note_tree_search(t, &tree, &n, entry->key_sha1); + int ret = 0; + + assert(GET_PTR_TYPE(entry) == 0); /* no type bits set */ + l = (struct leaf_node *) CLR_PTR_TYPE(*p); + switch (GET_PTR_TYPE(*p)) { + case PTR_TYPE_NULL: + assert(!*p); + if (is_null_sha1(entry->val_sha1)) + free(entry); + else + *p = SET_PTR_TYPE(entry, type); + return 0; + case PTR_TYPE_NOTE: + switch (type) { + case PTR_TYPE_NOTE: + if (!hashcmp(l->key_sha1, entry->key_sha1)) { + /* skip concatenation if l == entry */ + if (!hashcmp(l->val_sha1, entry->val_sha1)) + return 0; + + ret = combine_notes(l->val_sha1, + entry->val_sha1); + if (!ret && is_null_sha1(l->val_sha1)) + note_tree_remove(t, tree, n, entry); + free(entry); + return ret; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(l->key_sha1, + entry->key_sha1)) { + /* unpack 'entry' */ + load_subtree(t, entry, tree, n); + free(entry); + return 0; + } + break; + } + break; + case PTR_TYPE_SUBTREE: + if (!SUBTREE_SHA1_PREFIXCMP(entry->key_sha1, l->key_sha1)) { + /* unpack 'l' and restart insert */ + *p = NULL; + load_subtree(t, l, tree, n); + free(l); + return note_tree_insert(t, tree, n, entry, type, + combine_notes); + } + break; + } + + /* non-matching leaf_node */ + assert(GET_PTR_TYPE(*p) == PTR_TYPE_NOTE || + GET_PTR_TYPE(*p) == PTR_TYPE_SUBTREE); + if (is_null_sha1(entry->val_sha1)) { /* skip insertion of empty note */ + free(entry); + return 0; + } + new_node = (struct int_node *) xcalloc(sizeof(struct int_node), 1); + ret = note_tree_insert(t, new_node, n + 1, l, GET_PTR_TYPE(*p), + combine_notes); + if (ret) + return ret; + *p = SET_PTR_TYPE(new_node, PTR_TYPE_INTERNAL); + return note_tree_insert(t, new_node, n + 1, entry, type, combine_notes); + } + /* Free the entire notes data contained in the given tree */ static void note_tree_free(struct int_node *tree) { @@@ -445,8 -449,12 +452,12 @@@ static void load_subtree(struct notes_t l->key_sha1[19] = (unsigned char) len; type = PTR_TYPE_SUBTREE; } - note_tree_insert(t, node, n, l, type, - combine_notes_concatenate); + if (note_tree_insert(t, node, n, l, type, + combine_notes_concatenate)) + die("Failed to load %s %s into notes tree " + "from %s", + type == PTR_TYPE_NOTE ? "note" : "subtree", + sha1_to_hex(l->key_sha1), t->ref); } continue; @@@ -804,16 -812,17 +815,17 @@@ int combine_notes_concatenate(unsigned return 0; } - /* we will separate the notes by a newline anyway */ + /* we will separate the notes by two newlines anyway */ if (cur_msg[cur_len - 1] == '\n') cur_len--; /* concatenate cur_msg and new_msg into buf */ - buf_len = cur_len + 1 + new_len; + buf_len = cur_len + 2 + new_len; buf = (char *) xmalloc(buf_len); memcpy(buf, cur_msg, cur_len); buf[cur_len] = '\n'; - memcpy(buf + cur_len + 1, new_msg, new_len); + buf[cur_len + 1] = '\n'; + memcpy(buf + cur_len + 2, new_msg, new_len); free(cur_msg); free(new_msg); @@@ -836,6 -845,82 +848,82 @@@ int combine_notes_ignore(unsigned char return 0; } + static int string_list_add_note_lines(struct string_list *sort_uniq_list, + const unsigned char *sha1) + { + char *data; + unsigned long len; + enum object_type t; + struct strbuf buf = STRBUF_INIT; + struct strbuf **lines = NULL; + int i, list_index; + + if (is_null_sha1(sha1)) + return 0; + + /* read_sha1_file NUL-terminates */ + data = read_sha1_file(sha1, &t, &len); + if (t != OBJ_BLOB || !data || !len) { + free(data); + return t != OBJ_BLOB || !data; + } + + strbuf_attach(&buf, data, len, len + 1); + lines = strbuf_split(&buf, '\n'); + + for (i = 0; lines[i]; i++) { + if (lines[i]->buf[lines[i]->len - 1] == '\n') + strbuf_setlen(lines[i], lines[i]->len - 1); + if (!lines[i]->len) + continue; /* skip empty lines */ + list_index = string_list_find_insert_index(sort_uniq_list, + lines[i]->buf, 0); + if (list_index < 0) + continue; /* skip duplicate lines */ + string_list_insert_at_index(sort_uniq_list, list_index, + lines[i]->buf); + } + + strbuf_list_free(lines); + strbuf_release(&buf); + return 0; + } + + static int string_list_join_lines_helper(struct string_list_item *item, + void *cb_data) + { + struct strbuf *buf = cb_data; + strbuf_addstr(buf, item->string); + strbuf_addch(buf, '\n'); + return 0; + } + + int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, + const unsigned char *new_sha1) + { + struct string_list sort_uniq_list = { NULL, 0, 0, 1 }; + struct strbuf buf = STRBUF_INIT; + int ret = 1; + + /* read both note blob objects into unique_lines */ + if (string_list_add_note_lines(&sort_uniq_list, cur_sha1)) + goto out; + if (string_list_add_note_lines(&sort_uniq_list, new_sha1)) + goto out; + + /* create a new blob object from sort_uniq_list */ + if (for_each_string_list(&sort_uniq_list, + string_list_join_lines_helper, &buf)) + goto out; + + ret = write_sha1_file(buf.buf, buf.len, blob_type, cur_sha1); + + out: + strbuf_release(&buf); + string_list_clear(&sort_uniq_list, 0); + return ret; + } + static int string_list_add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb) { @@@ -880,6 -965,14 +968,6 @@@ void string_list_add_refs_from_colon_se strbuf_release(&globbuf); } -static int string_list_add_refs_from_list(struct string_list_item *item, - void *cb) -{ - struct string_list *list = cb; - string_list_add_refs_by_glob(list, item->string); - return 0; -} - static int notes_display_config(const char *k, const char *v, void *cb) { int *load_refs = cb; @@@ -893,7 -986,7 +981,7 @@@ return 0; } - static const char *default_notes_ref(void) + const char *default_notes_ref(void) { const char *notes_ref = NULL; if (!notes_ref) @@@ -935,25 -1028,37 +1023,25 @@@ void init_notes(struct notes_tree *t, c return; if (get_tree_entry(object_sha1, "", sha1, &mode)) die("Failed to read notes tree referenced by %s (%s)", - notes_ref, object_sha1); + notes_ref, sha1_to_hex(object_sha1)); hashclr(root_tree.key_sha1); hashcpy(root_tree.val_sha1, sha1); load_subtree(t, &root_tree, t->root, 0); } -struct load_notes_cb_data { - int counter; - struct notes_tree **trees; -}; - -static int load_one_display_note_ref(struct string_list_item *item, - void *cb_data) -{ - struct load_notes_cb_data *c = cb_data; - struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); - init_notes(t, item->string, combine_notes_ignore, 0); - c->trees[c->counter++] = t; - return 0; -} - struct notes_tree **load_notes_trees(struct string_list *refs) { + struct string_list_item *item; + int counter = 0; struct notes_tree **trees; - struct load_notes_cb_data cb_data; trees = xmalloc((refs->nr+1) * sizeof(struct notes_tree *)); - cb_data.counter = 0; - cb_data.trees = trees; - for_each_string_list(refs, load_one_display_note_ref, &cb_data); - trees[cb_data.counter] = NULL; + for_each_string_list_item(item, refs) { + struct notes_tree *t = xcalloc(1, sizeof(struct notes_tree)); + init_notes(t, item->string, combine_notes_ignore, 0); + trees[counter++] = t; + } + trees[counter] = NULL; return trees; } @@@ -978,18 -1083,16 +1066,18 @@@ void init_display_notes(struct display_ git_config(notes_display_config, &load_config_refs); - if (opt && opt->extra_notes_refs) - for_each_string_list(opt->extra_notes_refs, - string_list_add_refs_from_list, - &display_notes_refs); + if (opt && opt->extra_notes_refs) { + struct string_list_item *item; + for_each_string_list_item(item, opt->extra_notes_refs) + string_list_add_refs_by_glob(&display_notes_refs, + item->string); + } display_notes_trees = load_notes_trees(&display_notes_refs); string_list_clear(&display_notes_refs, 0); } - void add_note(struct notes_tree *t, const unsigned char *object_sha1, + int add_note(struct notes_tree *t, const unsigned char *object_sha1, const unsigned char *note_sha1, combine_notes_fn combine_notes) { struct leaf_node *l; @@@ -1003,23 -1106,20 +1091,23 @@@ l = (struct leaf_node *) xmalloc(sizeof(struct leaf_node)); hashcpy(l->key_sha1, object_sha1); hashcpy(l->val_sha1, note_sha1); - note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes); + return note_tree_insert(t, t->root, 0, l, PTR_TYPE_NOTE, combine_notes); } -void remove_note(struct notes_tree *t, const unsigned char *object_sha1) +int remove_note(struct notes_tree *t, const unsigned char *object_sha1) { struct leaf_node l; if (!t) t = &default_notes_tree; assert(t->initialized); - t->dirty = 1; hashcpy(l.key_sha1, object_sha1); hashclr(l.val_sha1); note_tree_remove(t, t->root, 0, &l); + if (is_null_sha1(l.val_sha1)) // no note was removed + return 1; + t->dirty = 1; + return 0; } const unsigned char *get_note(struct notes_tree *t, @@@ -1182,7 -1282,7 +1270,7 @@@ void format_display_notes(const unsigne int copy_note(struct notes_tree *t, const unsigned char *from_obj, const unsigned char *to_obj, - int force, combine_notes_fn combine_fn) + int force, combine_notes_fn combine_notes) { const unsigned char *note = get_note(t, from_obj); const unsigned char *existing_note = get_note(t, to_obj); @@@ -1191,9 -1291,9 +1279,9 @@@ return 1; if (note) - add_note(t, to_obj, note, combine_fn); + return add_note(t, to_obj, note, combine_notes); else if (existing_note) - add_note(t, to_obj, null_sha1, combine_fn); + return add_note(t, to_obj, null_sha1, combine_notes); return 0; } diff --combined notes.h index 5106761534,93a73657e2..83bd6e0ec0 --- a/notes.h +++ b/notes.h @@@ -12,7 -12,10 +12,10 @@@ * resulting SHA1 into the first SHA1 argument (cur_sha1). A non-zero return * value indicates failure. * - * The two given SHA1s must both be non-NULL and different from each other. + * The two given SHA1s shall both be non-NULL and different from each other. + * Either of them (but not both) may be == null_sha1, which indicates an + * empty/non-existent note. If the resulting SHA1 (cur_sha1) is == null_sha1, + * the note will be removed from the notes tree. * * The default combine_notes function (you get this when passing NULL) is * combine_notes_concatenate(), which appends the contents of the new note to @@@ -24,6 -27,7 +27,7 @@@ typedef int (*combine_notes_fn)(unsigne int combine_notes_concatenate(unsigned char *cur_sha1, const unsigned char *new_sha1); int combine_notes_overwrite(unsigned char *cur_sha1, const unsigned char *new_sha1); int combine_notes_ignore(unsigned char *cur_sha1, const unsigned char *new_sha1); + int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1); /* * Notes tree object @@@ -43,6 -47,20 +47,20 @@@ extern struct notes_tree int dirty; } default_notes_tree; + /* + * Return the default notes ref. + * + * The default notes ref is the notes ref that is used when notes_ref == NULL + * is passed to init_notes(). + * + * This the first of the following to be defined: + * 1. The '--ref' option to 'git notes', if given + * 2. The $GIT_NOTES_REF environment variable, if set + * 3. The value of the core.notesRef config variable, if set + * 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/notes/commits") + */ + const char *default_notes_ref(void); + /* * Flags controlling behaviour of notes tree initialization * @@@ -76,11 -94,24 +94,24 @@@ void init_notes(struct notes_tree *t, c /* * Add the given note object to the given notes_tree structure * + * If there already exists a note for the given object_sha1, the given + * combine_notes function is invoked to break the tie. If not given (i.e. + * combine_notes == NULL), the default combine_notes function for the given + * notes_tree is used. + * + * Passing note_sha1 == null_sha1 indicates the addition of an + * empty/non-existent note. This is a (potentially expensive) no-op unless + * there already exists a note for the given object_sha1, AND combining that + * note with the empty note (using the given combine_notes function) results + * in a new/changed note. + * + * Returns zero on success; non-zero means combine_notes failed. + * * IMPORTANT: The changes made by add_note() to the given notes_tree structure * are not persistent until a subsequent call to write_notes_tree() returns * zero. */ - void add_note(struct notes_tree *t, const unsigned char *object_sha1, + int add_note(struct notes_tree *t, const unsigned char *object_sha1, const unsigned char *note_sha1, combine_notes_fn combine_notes); /* @@@ -89,10 -120,8 +120,10 @@@ * IMPORTANT: The changes made by remove_note() to the given notes_tree * structure are not persistent until a subsequent call to write_notes_tree() * returns zero. + * + * Return 0 if a note was removed; 1 if there was no note to remove. */ -void remove_note(struct notes_tree *t, const unsigned char *object_sha1); +int remove_note(struct notes_tree *t, const unsigned char *object_sha1); /* * Get the note object SHA1 containing the note data for the given object @@@ -105,11 -134,18 +136,18 @@@ const unsigned char *get_note(struct no /* * Copy a note from one object to another in the given notes_tree. * - * Fails if the to_obj already has a note unless 'force' is true. + * Returns 1 if the to_obj already has a note and 'force' is false. Otherwise, + * returns non-zero if 'force' is true, but the given combine_notes function + * failed to combine from_obj's note with to_obj's existing note. + * Returns zero on success. + * + * IMPORTANT: The changes made by copy_note() to the given notes_tree structure + * are not persistent until a subsequent call to write_notes_tree() returns + * zero. */ int copy_note(struct notes_tree *t, const unsigned char *from_obj, const unsigned char *to_obj, - int force, combine_notes_fn combine_fn); + int force, combine_notes_fn combine_notes); /* * Flags controlling behaviour of for_each_note() @@@ -151,6 -187,7 +189,7 @@@ * notes tree) from within the callback: * - add_note() * - remove_note() + * - copy_note() * - free_notes() */ typedef int each_note_fn(const unsigned char *object_sha1, diff --combined t/t3301-notes.sh index 7e84ab9790,f5b72c7b00..dc2e04a016 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@@ -52,7 -52,7 +52,7 @@@ test_expect_success 'refusing to edit n # 1 indicates caught gracefully by die, 128 means git-show barked test_expect_success 'handle empty notes gracefully' ' - git notes show ; test 1 = $? + test_expect_code 1 git notes show ' test_expect_success 'show non-existent notes entry with %N' ' @@@ -299,7 -299,7 +299,7 @@@ cat expect-F >> expect-rm- test_expect_success 'verify note removal with -F /dev/null' ' git log -4 > output && test_cmp expect-rm-F output && - ! git notes show + test_must_fail git notes show ' test_expect_success 'do not create empty note with -m "" (setup)' ' @@@ -309,7 -309,7 +309,7 @@@ test_expect_success 'verify non-creation of note with -m ""' ' git log -4 > output && test_cmp expect-rm-F output && - ! git notes show + test_must_fail git notes show ' cat > expect-combine_m_and_F << EOF @@@ -357,7 -357,7 +357,7 @@@ cat expect-multiline >> expect-rm-remov test_expect_success 'verify note removal with "git notes remove"' ' git log -4 > output && test_cmp expect-rm-remove output && - ! git notes show HEAD^ + test_must_fail git notes show HEAD^ ' cat > expect << EOF @@@ -365,13 -365,6 +365,13 @@@ c18dc024e14f08d18d14eea0d747ff692d66d6a c9c6af7f78bc47490dbf3e822cf2f3c24d4b9061 268048bfb8a1fb38e703baceb8ab235421bf80c5 EOF +test_expect_success 'removing non-existing note should not create new commit' ' + git rev-parse --verify refs/notes/commits > before_commit && + test_must_fail git notes remove HEAD^ && + git rev-parse --verify refs/notes/commits > after_commit && + test_cmp before_commit after_commit +' + test_expect_success 'list notes with "git notes list"' ' git notes list > output && test_cmp expect output @@@ -627,16 -620,16 +627,16 @@@ test_expect_success '--show-notes=ref a test_expect_success 'Allow notes on non-commits (trees, blobs, tags)' ' git config core.notesRef refs/notes/other && - echo "Note on a tree" > expect + echo "Note on a tree" > expect && git notes add -m "Note on a tree" HEAD: && git notes show HEAD: > actual && test_cmp expect actual && - echo "Note on a blob" > expect + echo "Note on a blob" > expect && filename=$(git ls-tree --name-only HEAD | head -n1) && git notes add -m "Note on a blob" HEAD:$filename && git notes show HEAD:$filename > actual && test_cmp expect actual && - echo "Note on a tag" > expect + echo "Note on a tag" > expect && git tag -a -m "This is an annotated tag" foobar HEAD^ && git notes add -m "Note on a tag" foobar && git notes show foobar > actual && @@@ -962,6 -955,7 +962,7 @@@ Date: Thu Apr 7 15:27:13 2005 -070 Notes (other): a fresh note + $whitespace another fresh note EOF @@@ -983,8 -977,11 +984,11 @@@ Date: Thu Apr 7 15:27:13 2005 -070 Notes (other): a fresh note + $whitespace another fresh note + $whitespace append 1 + $whitespace append 2 EOF @@@ -1061,4 -1058,23 +1065,23 @@@ test_expect_success 'git notes copy dia test_must_fail git notes copy one two three ' + test_expect_success 'git notes get-ref (no overrides)' ' + git config --unset core.notesRef && + unset GIT_NOTES_REF && + test "$(git notes get-ref)" = "refs/notes/commits" + ' + + test_expect_success 'git notes get-ref (core.notesRef)' ' + git config core.notesRef refs/notes/foo && + test "$(git notes get-ref)" = "refs/notes/foo" + ' + + test_expect_success 'git notes get-ref (GIT_NOTES_REF)' ' + test "$(GIT_NOTES_REF=refs/notes/bar git notes get-ref)" = "refs/notes/bar" + ' + + test_expect_success 'git notes get-ref (--ref)' ' + test "$(GIT_NOTES_REF=refs/notes/bar git notes --ref=baz get-ref)" = "refs/notes/baz" + ' + test_done diff --combined t/t3404-rebase-interactive.sh index 5cb7e70d54,9ed70dc7eb..d3a3bd2679 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@@ -7,39 -7,34 +7,39 @@@ test_description='git rebase interactiv This test runs git rebase "interactively", by faking an edit, and verifies that the result still makes sense. + +Initial setup: + + one - two - three - four (conflict-branch) + / + A - B - C - D - E (master) + | \ + | F - G - H (branch1) + | \ + |\ I (branch2) + | \ + | J - K - L - M (no-conflict-branch) + \ + N - O - P (no-ff-branch) + + where A, B, D and G all touch file1, and one, two, three, four all + touch file "conflict". ' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + set_fake_editor -# Set up the repository like this: -# -# one - two - three - four (conflict-branch) -# / -# A - B - C - D - E (master) -# | \ -# | F - G - H (branch1) -# | \ -# |\ I (branch2) -# | \ -# | J - K - L - M (no-conflict-branch) -# \ -# N - O - P (no-ff-branch) -# -# where A, B, D and G all touch file1, and one, two, three, four all -# touch file "conflict". -# # WARNING: Modifications to the initial repository can change the SHA ID used # in the expect2 file for the 'stop on conflicting pick' test. - test_expect_success 'setup' ' test_commit A file1 && test_commit B file1 && @@@ -51,70 -46,22 +51,70 @@@ test_commit G file1 && test_commit H file5 && git checkout -b branch2 F && - test_commit I file6 + test_commit I file6 && git checkout -b conflict-branch A && - for n in one two three four - do - test_commit $n conflict - done && + test_commit one conflict && + test_commit two conflict && + test_commit three conflict && + test_commit four conflict && git checkout -b no-conflict-branch A && - for n in J K L M - do - test_commit $n file$n - done && + test_commit J fileJ && + test_commit K fileK && + test_commit L fileL && + test_commit M fileM && git checkout -b no-ff-branch A && - for n in N O P - do - test_commit $n file$n - done + test_commit N fileN && + test_commit O fileO && + test_commit P fileP +' + +# "exec" commands are ran with the user shell by default, but this may +# be non-POSIX. For example, if SHELL=zsh then ">file" doesn't work +# to create a file. Unseting SHELL avoids such non-portable behavior +# in tests. +SHELL= + +test_expect_success 'rebase -i with the exec command' ' + git checkout master && + ( + FAKE_LINES="1 exec_>touch-one + 2 exec_>touch-two exec_false exec_>touch-three + 3 4 exec_>\"touch-file__name_with_spaces\";_>touch-after-semicolon 5" && + export FAKE_LINES && + test_must_fail git rebase -i A + ) && + test_path_is_file touch-one && + test_path_is_file touch-two && + test_path_is_missing touch-three " (should have stopped before)" && + test_cmp_rev C HEAD && + git rebase --continue && + test_path_is_file touch-three && + test_path_is_file "touch-file name with spaces" && + test_path_is_file touch-after-semicolon && + test_cmp_rev master HEAD && + rm -f touch-* +' + +test_expect_success 'rebase -i with the exec command runs from tree root' ' + git checkout master && + mkdir subdir && (cd subdir && + FAKE_LINES="1 exec_>touch-subdir" \ + git rebase -i HEAD^ + ) && + test_path_is_file touch-subdir && + rm -fr subdir +' + +test_expect_success 'rebase -i with the exec command checks tree cleanness' ' + git checkout master && + ( + FAKE_LINES="exec_echo_foo_>file1 1" && + export FAKE_LINES && + test_must_fail git rebase -i HEAD^ + ) && + test_cmp_rev master^ HEAD && + git reset --hard && + git rebase --continue ' test_expect_success 'no changes are a nop' ' @@@ -196,17 -143,16 +196,17 @@@ test_expect_success 'abort' git rebase --abort && test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch1" && - ! test -d .git/rebase-merge + test_path_is_missing .git/rebase-merge ' test_expect_success 'abort with error when new base cannot be checked out' ' git rm --cached file1 && git commit -m "remove file in base" && test_must_fail git rebase -i master > output 2>&1 && - grep "Untracked working tree file .file1. would be overwritten" \ + grep "The following untracked working tree files would be overwritten by checkout:" \ output && - ! test -d .git/rebase-merge && + grep "file1" output && + test_path_is_missing .git/rebase-merge && git reset --hard HEAD^ ' @@@ -576,7 -522,7 +576,7 @@@ test_expect_success 'do "noop" when the git checkout -b branch4 HEAD && GIT_EDITOR=: git commit --amend \ - --author="Somebody else " + --author="Somebody else " && test $(git rev-parse branch3) != $(git rev-parse branch4) && git rebase -i branch3 && test $(git rev-parse branch3) = $(git rev-parse branch4) @@@ -591,7 -537,7 +591,7 @@@ test_expect_success 'submodule rebase s git add elif && git commit -m "submodule initial" ) && echo 1 >file1 && - git add file1 sub + git add file1 sub && test_tick && git commit -m "One" && echo 2 >file1 && @@@ -647,6 -593,7 +647,7 @@@ test_expect_success 'rebase -i can cop cat >expect <