diff options
61 files changed, 7378 insertions, 6127 deletions
diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..c3bf9c6d4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,91 @@ +language: c + +os: + - linux + - osx + +compiler: + - clang + - gcc + +addons: + apt: + packages: + - language-pack-is + +env: + global: + - P4_VERSION="15.2" + - GIT_LFS_VERSION="1.1.0" + - DEFAULT_TEST_TARGET=prove + - GIT_PROVE_OPTS="--timer --jobs 3" + - GIT_TEST_OPTS="--verbose --tee" + - CFLAGS="-g -O2 -Wall -Werror" + - GIT_TEST_CLONE_2GB=YesPlease + # t9810 occasionally fails on Travis CI OS X + # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X + - GIT_SKIP_TESTS="t9810 t9816" + +before_install: + - > + case "${TRAVIS_OS_NAME:-linux}" in + linux) + mkdir --parents custom/p4 + pushd custom/p4 + wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4d + wget --quiet http://filehost.perforce.com/perforce/r$P4_VERSION/bin.linux26x86_64/p4 + chmod u+x p4d + chmod u+x p4 + export PATH="$(pwd):$PATH" + popd + mkdir --parents custom/git-lfs + pushd custom/git-lfs + wget --quiet https://github.com/github/git-lfs/releases/download/v$GIT_LFS_VERSION/git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz + tar --extract --gunzip --file "git-lfs-linux-amd64-$GIT_LFS_VERSION.tar.gz" + cp git-lfs-$GIT_LFS_VERSION/git-lfs . + export PATH="$(pwd):$PATH" + popd + ;; + osx) + brew_force_set_latest_binary_hash () { + FORMULA=$1 + SHA=$(brew fetch --force $FORMULA 2>&1 | grep ^SHA256: | cut -d ' ' -f 2) + sed -E -i.bak "s/sha256 \"[0-9a-f]{64}\"/sha256 \"$SHA\"/g" \ + /usr/local/Library/Taps/homebrew/homebrew-binary/$FORMULA.rb + } + brew update --quiet + brew tap homebrew/binary --quiet + brew_force_set_latest_binary_hash perforce + brew_force_set_latest_binary_hash perforce-server + brew install git-lfs perforce-server perforce gettext + brew link --force gettext + ;; + esac; + echo "$(tput setaf 6)Perforce Server Version$(tput sgr0)"; + p4d -V | grep Rev.; + echo "$(tput setaf 6)Perforce Client Version$(tput sgr0)"; + p4 -V | grep Rev.; + echo "$(tput setaf 6)Git-LFS Version$(tput sgr0)"; + git-lfs version; + +before_script: make --jobs=2 + +script: make --quiet test + +after_failure: + - > + : '<-- Click here to see detailed test output! '; + for TEST_EXIT in t/test-results/*.exit; + do + if [ "$(cat "$TEST_EXIT")" != "0" ]; + then + TEST_OUT="${TEST_EXIT%exit}out"; + echo "------------------------------------------------------------------------"; + echo "$(tput setaf 1)${TEST_OUT}...$(tput sgr0)"; + echo "------------------------------------------------------------------------"; + cat "${TEST_OUT}"; + fi; + done; + +notifications: + email: false diff --git a/Documentation/RelNotes/2.6.4.txt b/Documentation/RelNotes/2.6.4.txt new file mode 100644 index 000000000..b0256a2dc --- /dev/null +++ b/Documentation/RelNotes/2.6.4.txt @@ -0,0 +1,63 @@ +Git v2.6.4 Release Notes +======================== + +Fixes since v2.6.3 +------------------ + + * The "configure" script did not test for -lpthread correctly, which + upset some linkers. + + * Add support for talking http/https over socks proxy. + + * Portability fix for Windows, which may rewrite $SHELL variable using + non-POSIX paths. + + * We now consistently allow all hooks to ignore their standard input, + rather than having git complain of SIGPIPE. + + * Fix shell quoting in contrib script. + + * Test portability fix for a topic in v2.6.1. + + * Allow tilde-expansion in some http config variables. + + * Give a useful special case "diff/show --word-diff-regex=." as an + example in the documentation. + + * Fix for a corner case in filter-branch. + + * Make git-p4 work on a detached head. + + * Documentation clarification for "check-ignore" without "--verbose". + + * Just like the working tree is cleaned up when the user cancelled + submission in P4Submit.applyCommit(), clean up the mess if "p4 + submit" fails. + + * Having a leftover .idx file without corresponding .pack file in + the repository hurts performance; "git gc" learned to prune them. + + * The code to prepare the working tree side of temporary directory + for the "dir-diff" feature forgot that symbolic links need not be + copied (or symlinked) to the temporary area, as the code already + special cases and overwrites them. Besides, it was wrong to try + computing the object name of the target of symbolic link, which may + not even exist or may be a directory. + + * There was no way to defeat a configured rebase.autostash variable + from the command line, as "git rebase --no-autostash" was missing. + + * Allow "git interpret-trailers" to run outside of a Git repository. + + * Produce correct "dirty" marker for shell prompts, even when we + are on an orphan or an unborn branch. + + * Some corner cases have been fixed in string-matching done in "git + status". + + * Apple's common crypto implementation of SHA1_Update() does not take + more than 4GB at a time, and we now have a compile-time workaround + for it. + +Also contains typofixes, documentation updates and trivial code +clean-ups. diff --git a/Documentation/RelNotes/2.7.0.txt b/Documentation/RelNotes/2.7.0.txt index ca2c24bea..aa8224cb1 100644 --- a/Documentation/RelNotes/2.7.0.txt +++ b/Documentation/RelNotes/2.7.0.txt @@ -66,6 +66,20 @@ UI, Workflows & Features a superset of "--no-progress". Extend the command to support the usual "--[no-]progress". + * The semantics of tranfer.hideRefs configuration variable have been + extended to work better with the ref "namespace" feature that lets + you throw unrelated bunches of repositories in a single physical + repository and virtually serve them as separate ones. + + * send-email config variables whose values are pathnames now go + through the ~username/ expansion. + + * bash completion learnt to TAB-complete recipient addresses given + to send-email. + + * The credential-cache daemon can be told to ignore SIGHUP to work + around issue when running Git from inside emacs. + Performance, Internal Implementation, Development Support etc. @@ -118,6 +132,30 @@ Performance, Internal Implementation, Development Support etc. * With a "debug" helper, debugging of a single "git" invocation in our test scripts has become a lot easier. + * The "configure" script did not test for -lpthread correctly, which + upset some linkers. + + * Cross completed task off of subtree project's todo list. + + * Test cleanups for the subtree project. + + * Clean up style in an ancient test t9300. + + * Work around some test flakiness with p4d. + + * Fsck did not correctly detect a NUL-truncated header in a tag. + + * Use a safer behavior when we hit errors verifying remote certificates. + + * Speed up filter-branch for cases where we only care about rewriting + commits, not tree data. + + * The parse-options API has been updated to make "-h" command line + option work more consistently in all commands. + + * "git svn rebase/mkdirs" got optimized by keeping track of empty + directories better. + Also contains various documentation updates and code clean-ups. @@ -341,6 +379,19 @@ notes for details). requested range. However, we were hand-crafting a range request and it did not kick in. + * Having a leftover .idx file without corresponding .pack file in + the repository hurts performance; "git gc" learned to prune them. + (merge 478f34d dk/gc-idx-wo-pack later to maint). + + * Apple's common crypto implementation of SHA1_Update() does not take + more than 4GB at a time, and we now have a compile-time workaround + for it. + (merge 001fd7a ad/sha1-update-chunked later to maint). + + * Produce correct "dirty" marker for shell prompts, even when we + are on an orphan or an unborn branch. + (merge c26f70c sg/bash-prompt-dirty-orphan later to maint). + * Code clean-up, minor fixes etc. (merge 15ed07d jc/rerere later to maint). (merge e7a7401 pt/pull-builtin later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index b4b01948d..2d06b11f2 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1122,6 +1122,9 @@ credential.<url>.*:: example.com. See linkgit:gitcredentials[7] for details on how URLs are matched. +credentialCache.ignoreSIGHUP:: + Tell git-credential-cache--daemon to ignore SIGHUP, instead of quitting. + include::diff-config.txt[] difftool.<tool>.path:: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d56ca9099..306b7e360 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -267,6 +267,9 @@ expression to make sure that it matches all non-whitespace characters. A match that contains a newline is silently truncated(!) at the newline. + +For example, `--word-diff-regex=.` will treat each character as a word +and, correspondingly, show differences character by character. ++ The regex can also be set via a diff driver or configuration option, see linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly overrides any diff driver or configuration setting. Diff drivers diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index 59531abba..e94367a5e 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -16,10 +16,9 @@ DESCRIPTION ----------- For each pathname given via the command-line or from a file via -`--stdin`, show the pattern from .gitignore (or other input files to -the exclude mechanism) that decides if the pathname is excluded or -included. Later patterns within a file take precedence over earlier -ones. +`--stdin`, check whether the file is excluded by .gitignore (or other +input files to the exclude mechanism) and output the path if it is +excluded. By default, tracked files are not shown at all since they are not subject to exclude rules; but see `--no-index'. @@ -32,7 +31,8 @@ OPTIONS -v, --verbose:: Also output details about the matching pattern (if any) - for each given pathname. + for each given pathname. For precedence rules within and + between exclude sources, see linkgit:gitignore[5]. --stdin:: Read pathnames from the standard input, one per line, diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index b9134d234..771a7b5b0 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git send-email' [options] <file|directory|rev-list options>... +'git send-email' --dump-aliases DESCRIPTION @@ -387,6 +388,16 @@ default to '--validate'. Send emails even if safety checks would prevent it. +Information +~~~~~~~~~~~ + +--dump-aliases:: + Instead of the normal operation, dump the shorthand alias names from + the configured alias file(s), one per line in alphabetical order. Note, + this only includes the alias name and not its expanded email addresses. + See 'sendemail.aliasesfile' for more information about aliases. + + CONFIGURATION ------------- diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt index 1a296bc29..3df9c26f4 100644 --- a/Documentation/git-update-index.txt +++ b/Documentation/git-update-index.txt @@ -17,6 +17,7 @@ SYNOPSIS [--[no-]assume-unchanged] [--[no-]skip-worktree] [--ignore-submodules] + [--[no-|force-]untracked-cache] [--really-refresh] [--unresolve] [--again | -g] [--info-only] [--index-info] [-z] [--stdin] [--index-version <n>] diff --git a/Documentation/git.txt b/Documentation/git.txt index c2e2a94e7..cbf157be4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.6.3/git.html[documentation for release 2.6.3] +* link:v2.6.4/git.html[documentation for release 2.6.4] * release notes for + link:RelNotes/2.6.4.txt[2.6.4], link:RelNotes/2.6.3.txt[2.6.3], link:RelNotes/2.6.2.txt[2.6.2], link:RelNotes/2.6.1.txt[2.6.1], @@ -1056,7 +1057,7 @@ of clones and fetches. cloning of shallow repositories. See 'GIT_TRACE' for available trace output options. -GIT_LITERAL_PATHSPECS:: +'GIT_LITERAL_PATHSPECS':: Setting this variable to `1` will cause Git to treat all pathspecs literally, rather than as glob patterns. For example, running `GIT_LITERAL_PATHSPECS=1 git log -- '*.c'` will search @@ -1065,15 +1066,15 @@ GIT_LITERAL_PATHSPECS:: literal paths to Git (e.g., paths previously given to you by `git ls-tree`, `--raw` diff output, etc). -GIT_GLOB_PATHSPECS:: +'GIT_GLOB_PATHSPECS':: Setting this variable to `1` will cause Git to treat all pathspecs as glob patterns (aka "glob" magic). -GIT_NOGLOB_PATHSPECS:: +'GIT_NOGLOB_PATHSPECS':: Setting this variable to `1` will cause Git to treat all pathspecs as literal (aka "literal" magic). -GIT_ICASE_PATHSPECS:: +'GIT_ICASE_PATHSPECS':: Setting this variable to `1` will cause Git to treat all pathspecs as case-insensitive. @@ -1087,7 +1088,7 @@ GIT_ICASE_PATHSPECS:: variable when it is invoked as the top level command by the end user, to be recorded in the body of the reflog. -`GIT_REF_PARANOIA`:: +'GIT_REF_PARANOIA':: If set to `1`, include broken or badly named refs when iterating over lists of refs. In a normal, non-corrupted repository, this does nothing. However, enabling it may help git to detect and @@ -1098,7 +1099,7 @@ GIT_ICASE_PATHSPECS:: an operation has touched every ref (e.g., because you are cloning a repository to make a backup). -`GIT_ALLOW_PROTOCOL`:: +'GIT_ALLOW_PROTOCOL':: If set, provide a colon-separated list of protocols which are allowed to be used with fetch/push/clone. This is useful to restrict recursive submodule initialization from an untrusted @@ -142,6 +142,10 @@ all:: # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # +# Define SHA1_MAX_BLOCK_SIZE to limit the amount of data that will be hashed +# in one call to the platform's SHA1_Update(). e.g. APPLE_COMMON_CRYPTO +# wants 'SHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' defined. +# # Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin). # # Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin). @@ -768,6 +772,7 @@ LIB_OBJS += reachable.o LIB_OBJS += read-cache.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o +LIB_OBJS += refs/files-backend.o LIB_OBJS += ref-filter.o LIB_OBJS += remote.o LIB_OBJS += replace_object.o @@ -1338,6 +1343,11 @@ ifdef NO_POSIX_GOODIES BASIC_CFLAGS += -DNO_POSIX_GOODIES endif +ifdef APPLE_COMMON_CRYPTO + # Apple CommonCrypto requires chunking + SHA1_MAX_BLOCK_SIZE = 1024L*1024L*1024L +endif + ifdef BLK_SHA1 SHA1_HEADER = "block-sha1/sha1.h" LIB_OBJS += block-sha1/sha1.o @@ -1356,6 +1366,10 @@ endif endif endif +ifdef SHA1_MAX_BLOCK_SIZE + LIB_OBJS += compat/sha1-chunked.o + BASIC_CFLAGS += -DSHA1_MAX_BLOCK_SIZE="$(SHA1_MAX_BLOCK_SIZE)" +endif ifdef NO_PERL_MAKEMAKER export NO_PERL_MAKEMAKER endif @@ -2419,7 +2433,7 @@ profile-clean: $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) clean: profile-clean coverage-clean - $(RM) *.o *.res block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o + $(RM) *.o *.res refs/*.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o $(RM) xdiff/*.o vcs-svn/*.o ewah/*.o builtin/*.o $(RM) $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X diff --git a/block-sha1/sha1.h b/block-sha1/sha1.h index b864df623..4df674775 100644 --- a/block-sha1/sha1.h +++ b/block-sha1/sha1.h @@ -16,7 +16,7 @@ void blk_SHA1_Init(blk_SHA_CTX *ctx); void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); -#define git_SHA_CTX blk_SHA_CTX -#define git_SHA1_Init blk_SHA1_Init -#define git_SHA1_Update blk_SHA1_Update -#define git_SHA1_Final blk_SHA1_Final +#define platform_SHA_CTX blk_SHA_CTX +#define platform_SHA1_Init blk_SHA1_Init +#define platform_SHA1_Update blk_SHA1_Update +#define platform_SHA1_Final blk_SHA1_Final diff --git a/builtin/blame.c b/builtin/blame.c index ffb990fad..1df13cf7f 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2402,10 +2402,12 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, return commit; } -static struct object_array_entry *find_single_final(struct rev_info *revs) +static struct commit *find_single_final(struct rev_info *revs, + const char **name_p) { int i; - struct object_array_entry *found = NULL; + struct commit *found = NULL; + const char *name = NULL; for (i = 0; i < revs->pending.nr; i++) { struct object *obj = revs->pending.objects[i].item; @@ -2417,22 +2419,20 @@ static struct object_array_entry *find_single_final(struct rev_info *revs) die("Non commit %s?", revs->pending.objects[i].name); if (found) die("More than one commit to dig from %s and %s?", - revs->pending.objects[i].name, - found->name); - found = &(revs->pending.objects[i]); + revs->pending.objects[i].name, name); + found = (struct commit *)obj; + name = revs->pending.objects[i].name; } + if (name_p) + *name_p = name; return found; } static char *prepare_final(struct scoreboard *sb) { - struct object_array_entry *found = find_single_final(sb->revs); - if (found) { - sb->final = (struct commit *) found->item; - return xstrdup(found->name); - } else { - return NULL; - } + const char *name; + sb->final = find_single_final(sb->revs, &name); + return xstrdup_or_null(name); } static char *prepare_initial(struct scoreboard *sb) @@ -2720,11 +2720,9 @@ parse_done: die("Cannot use --contents with final commit object name"); if (reverse && revs.first_parent_only) { - struct object_array_entry *entry = find_single_final(sb.revs); - if (!entry) + final_commit = find_single_final(sb.revs, NULL); + if (!final_commit) die("--reverse and --first-parent together require specified latest commit"); - else - final_commit = (struct commit*) entry->item; } /* diff --git a/builtin/commit.c b/builtin/commit.c index 6aac5e284..d054f8496 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -32,6 +32,7 @@ #include "sequencer.h" #include "notes-utils.h" #include "mailmap.h" +#include "sigchain.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -1537,8 +1538,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1, return code; n = snprintf(buf, sizeof(buf), "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1)); + sigchain_push(SIGPIPE, SIG_IGN); write_in_full(proc.in, buf, n); close(proc.in); + sigchain_pop(SIGPIPE); return finish_command(&proc); } diff --git a/builtin/grep.c b/builtin/grep.c index 2825453d1..4229cae39 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -612,11 +612,6 @@ static int pattern_callback(const struct option *opt, const char *arg, return 0; } -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; @@ -738,18 +733,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager }, OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)")), - { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; - /* - * 'git grep -h', unlike 'git grep -h <pattern>', is a request - * to show usage information and exit. - */ - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(grep_usage, options); - init_grep_defaults(); git_config(grep_cmd_config, NULL); grep_init(&opt, prefix); @@ -766,8 +752,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) */ argc = parse_options(argc, argv, prefix, options, grep_usage, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_STOP_AT_NON_OPTION | - PARSE_OPT_NO_INTERNAL_HELP); + PARSE_OPT_STOP_AT_NON_OPTION); grep_commit_pattern_type(pattern_type_arg, &opt); if (use_index && !startup_info->have_repository) diff --git a/builtin/init-db.c b/builtin/init-db.c index f59f40768..07229d60f 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -24,18 +24,6 @@ static int init_shared_repository = -1; static const char *init_db_template_dir; static const char *git_link; -static void safe_create_dir(const char *dir, int share) -{ - if (mkdir(dir, 0777) < 0) { - if (errno != EEXIST) { - perror(dir); - exit(1); - } - } - else if (share && adjust_shared_perm(dir)) - die(_("Could not make %s writable by group"), dir); -} - static void copy_templates_1(struct strbuf *path, struct strbuf *template, DIR *dir) { diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 264c39200..6d4e66900 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -161,11 +161,6 @@ static int exclude_existing_callback(const struct option *opt, const char *arg, return 0; } -static int help_callback(const struct option *opt, const char *arg, int unset) -{ - return -1; -} - static const struct option show_ref_options[] = { OPT_BOOL(0, "tags", &tags_only, N_("only show tags (can be combined with heads)")), OPT_BOOL(0, "heads", &heads_only, N_("only show heads (can be combined with tags)")), @@ -186,18 +181,13 @@ static const struct option show_ref_options[] = { { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, N_("pattern"), N_("show refs from stdin that aren't in local repository"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, - { OPTION_CALLBACK, 0, "help-all", NULL, NULL, N_("show usage"), - PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, OPT_END() }; int cmd_show_ref(int argc, const char **argv, const char *prefix) { - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(show_ref_usage, show_ref_options); - argc = parse_options(argc, argv, prefix, show_ref_options, - show_ref_usage, PARSE_OPT_NO_INTERNAL_HELP); + show_ref_usage, 0); if (exclude_arg) return exclude_existing(exclude_existing_arg); @@ -11,11 +11,29 @@ #include "string-list.h" #include SHA1_HEADER -#ifndef git_SHA_CTX -#define git_SHA_CTX SHA_CTX -#define git_SHA1_Init SHA1_Init -#define git_SHA1_Update SHA1_Update -#define git_SHA1_Final SHA1_Final +#ifndef platform_SHA_CTX +/* + * platform's underlying implementation of SHA-1; could be OpenSSL, + * blk_SHA, Apple CommonCrypto, etc... Note that including + * SHA1_HEADER may have already defined platform_SHA_CTX for our + * own implementations like block-sha1 and ppc-sha1, so we list + * the default for OpenSSL compatible SHA-1 implementations here. + */ +#define platform_SHA_CTX SHA_CTX +#define platform_SHA1_Init SHA1_Init +#define platform_SHA1_Update SHA1_Update +#define platform_SHA1_Final SHA1_Final +#endif + +#define git_SHA_CTX platform_SHA_CTX +#define git_SHA1_Init platform_SHA1_Init +#define git_SHA1_Update platform_SHA1_Update +#define git_SHA1_Final platform_SHA1_Final + +#ifdef SHA1_MAX_BLOCK_SIZE +#include "compat/sha1-chunked.h" +#undef git_SHA1_Update +#define git_SHA1_Update git_SHA1_Update_Chunked #endif #include <zlib.h> @@ -1753,4 +1771,12 @@ void stat_validity_update(struct stat_validity *sv, int fd); int versioncmp(const char *s1, const char *s2); void sleep_millisec(int millisec); +/* + * Create a directory and (if share is nonzero) adjust its permissions + * according to the shared_repository setting. Only use this for + * directories under $GIT_DIR. Don't use it for working tree + * directories. + */ +void safe_create_dir(const char *dir, int share); + #endif /* CACHE_H */ diff --git a/compat/apple-common-crypto.h b/compat/apple-common-crypto.h index c8b9b0e1a..d3fb26418 100644 --- a/compat/apple-common-crypto.h +++ b/compat/apple-common-crypto.h @@ -16,6 +16,10 @@ #undef TYPE_BOOL #endif +#ifndef SHA1_MAX_BLOCK_SIZE +#error Using Apple Common Crypto library requires setting SHA1_MAX_BLOCK_SIZE +#endif + #ifdef APPLE_LION_OR_NEWER #define git_CC_error_check(pattern, err) \ do { \ diff --git a/compat/sha1-chunked.c b/compat/sha1-chunked.c new file mode 100644 index 000000000..6adfcfd54 --- /dev/null +++ b/compat/sha1-chunked.c @@ -0,0 +1,19 @@ +#include "cache.h" + +int git_SHA1_Update_Chunked(platform_SHA_CTX *c, const void *data, size_t len) +{ + size_t nr; + size_t total = 0; + const char *cdata = (const char*)data; + + while (len) { + nr = len; + if (nr > SHA1_MAX_BLOCK_SIZE) + nr = SHA1_MAX_BLOCK_SIZE; + platform_SHA1_Update(c, cdata, nr); + total += nr; + cdata += nr; + len -= nr; + } + return total; +} diff --git a/compat/sha1-chunked.h b/compat/sha1-chunked.h new file mode 100644 index 000000000..7b2df28ee --- /dev/null +++ b/compat/sha1-chunked.h @@ -0,0 +1,2 @@ + +int git_SHA1_Update_Chunked(platform_SHA_CTX *c, const void *data, size_t len); diff --git a/configure.ac b/configure.ac index 76170ad06..89e2590bd 100644 --- a/configure.ac +++ b/configure.ac @@ -1142,7 +1142,12 @@ elif test -z "$PTHREAD_CFLAGS"; then # would then trigger compiler warnings on every single file we compile. for opt in "" -mt -pthread -lpthread; do old_CFLAGS="$CFLAGS" - CFLAGS="$opt $CFLAGS" + old_LIBS="$LIBS" + case "$opt" in + -l*) LIBS="$opt $LIBS" ;; + *) CFLAGS="$opt $CFLAGS" ;; + esac + AC_MSG_CHECKING([for POSIX Threads with '$opt']) AC_LINK_IFELSE([PTHREADTEST_SRC], [AC_MSG_RESULT([yes]) @@ -1154,6 +1159,7 @@ elif test -z "$PTHREAD_CFLAGS"; then ], [AC_MSG_RESULT([no])]) CFLAGS="$old_CFLAGS" + LIBS="$old_LIBS" done if test $threads_found != yes; then AC_CHECK_LIB([pthread], [pthread_create], diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 482ca84b4..111b05302 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -10,6 +10,7 @@ # *) local and remote tag names # *) .git/remotes file names # *) git 'subcommands' +# *) git email aliases for git-send-email # *) tree paths within 'ref:path/to/file' expressions # *) file paths within current working directory and index # *) common --long-options @@ -1711,6 +1712,15 @@ __git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all" _git_send_email () { + case "$prev" in + --to|--cc|--bcc|--from) + __gitcomp " + $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null) + " "" "" + return + ;; + esac + case "$cur" in --confirm=*) __gitcomp " @@ -1735,6 +1745,12 @@ _git_send_email () " "" "${cur##--thread=}" return ;; + --to=*|--cc=*|--bcc=*|--from=*) + __gitcomp " + $(git --git-dir="$(__gitdir)" send-email --dump-aliases 2>/dev/null) + " "" "${cur#--*=}" + return + ;; --*) __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to --compose --confirm= --dry-run --envelope-sender diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 07b52bedf..64219e631 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -476,10 +476,9 @@ __git_ps1 () if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ] && [ "$(git config --bool bash.showDirtyState)" != "false" ] then - git diff --no-ext-diff --quiet --exit-code || w="*" - if [ -n "$short_sha" ]; then - git diff-index --cached --quiet HEAD -- || i="+" - else + git diff --no-ext-diff --quiet || w="*" + git diff --no-ext-diff --cached --quiet || i="+" + if [ -z "$short_sha" ] && [ -z "$i" ]; then i="#" fi fi diff --git a/contrib/rerere-train.sh b/contrib/rerere-train.sh index 36b6feebe..52ad9e41f 100755 --- a/contrib/rerere-train.sh +++ b/contrib/rerere-train.sh @@ -7,7 +7,7 @@ USAGE="$me rev-list-args" SUBDIRECTORY_OK=Yes OPTIONS_SPEC= -. $(git --exec-path)/git-sh-setup +. "$(git --exec-path)/git-sh-setup" require_work_tree cd_to_toplevel diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 308b777b0..edf36f8c3 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -90,7 +90,7 @@ while [ $# -gt 0 ]; do --annotate) annotate="$1"; shift ;; --no-annotate) annotate= ;; -b) branch="$1"; shift ;; - -P) prefix="$1"; shift ;; + -P) prefix="${1%/}"; shift ;; -m) message="$1"; shift ;; --no-prefix) prefix= ;; --onto) onto="$1"; shift ;; diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile index c86481038..276898eb6 100644 --- a/contrib/subtree/t/Makefile +++ b/contrib/subtree/t/Makefile @@ -13,11 +13,23 @@ TAR ?= $(TAR) RM ?= rm -f PROVE ?= prove DEFAULT_TEST_TARGET ?= test +TEST_LINT ?= test-lint + +ifdef TEST_OUTPUT_DIRECTORY +TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results +else +TEST_RESULTS_DIRECTORY = ../../../t/test-results +endif # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) +TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) -T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) +T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) +TSVN = $(sort $(wildcard t91[0-9][0-9]-*.sh)) +TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) +THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) all: $(DEFAULT_TEST_TARGET) @@ -26,20 +38,22 @@ test: pre-clean $(TEST_LINT) prove: pre-clean $(TEST_LINT) @echo "*** prove ***"; GIT_CONFIG=.git/config $(PROVE) --exec '$(SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) - $(MAKE) clean + $(MAKE) clean-except-prove-cache $(T): @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) pre-clean: - $(RM) -r test-results + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' -clean: - $(RM) -r 'trash directory'.* test-results +clean-except-prove-cache: + $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) -r valgrind/bin + +clean: clean-except-prove-cache $(RM) .prove -test-lint: test-lint-duplicates test-lint-executable +test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax test-lint-duplicates: @dups=`echo $(T) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ @@ -51,12 +65,15 @@ test-lint-executable: test -z "$$bad" || { \ echo >&2 "non-executable tests:" $$bad; exit 1; } +test-lint-shell-syntax: + @'$(PERL_PATH_SQ)' ../../../t/check-non-portable-shell.pl $(T) $(THELPERS) + aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean aggregate-results: - for f in ../../../t/test-results/t*-*.counts; do \ + for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ echo "$$f"; \ done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index dfbe443de..751aee3a0 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -5,7 +5,7 @@ # test_description='Basic porcelain support for subtrees -This test verifies the basic operation of the merge, pull, add +This test verifies the basic operation of the add, pull, merge and split subcommands of git subtree. ' @@ -14,13 +14,21 @@ export TEST_DIRECTORY . ../../../t/test-lib.sh +subtree_test_create_repo() +{ + test_create_repo "$1" + ( + cd $1 + git config log.date relative + ) +} + create() { echo "$1" >"$1" git add "$1" } - check_equal() { test_debug 'echo' @@ -38,484 +46,972 @@ undo() git reset --hard HEAD~ } -last_commit_message() +# Make sure no patch changes more than one file. +# The original set of commits changed only one file each. +# A multi-file change would imply that we pruned commits +# too aggressively. +join_commits() { - git log --pretty=format:%s -1 + commit= + all= + while read x y; do + if [ -z "$x" ]; then + continue + elif [ "$x" = "commit:" ]; then + if [ -n "$commit" ]; then + echo "$commit $all" + all= + fi + commit="$y" + else + all="$all $y" + fi + done + echo "$commit $all" } -test_expect_success 'init subproj' ' - test_create_repo "sub proj" -' - -# To the subproject! -cd ./"sub proj" - -test_expect_success 'add sub1' ' - create sub1 && - git commit -m "sub1" && - git branch sub1 && - git branch -m master subproj -' - -# Save this hash for testing later. - -subdir_hash=$(git rev-parse HEAD) - -test_expect_success 'add sub2' ' - create sub2 && - git commit -m "sub2" && - git branch sub2 -' - -test_expect_success 'add sub3' ' - create sub3 && - git commit -m "sub3" && - git branch sub3 -' - -# Back to mainline -cd .. - -test_expect_success 'enable log.date=relative to catch errors' ' - git config log.date relative -' - -test_expect_success 'add main4' ' - create main4 && - git commit -m "main4" && - git branch -m master mainline && - git branch subdir -' - -test_expect_success 'fetch subproj history' ' - git fetch ./"sub proj" sub1 && - git branch sub1 FETCH_HEAD -' - -test_expect_success 'no subtree exists in main tree' ' - test_must_fail git subtree merge --prefix="sub dir" sub1 -' - -test_expect_success 'no pull from non-existant subtree' ' - test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" sub1 -' - -test_expect_success 'check if --message works for add' ' - git subtree add --prefix="sub dir" --message="Added subproject" sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject" && - undo -' - -test_expect_success 'check if --message works as -m and --prefix as -P' ' - git subtree add -P "sub dir" -m "Added subproject using git subtree" sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject using git subtree" && - undo -' - -test_expect_success 'check if --message works with squash too' ' - git subtree add -P "sub dir" -m "Added subproject with squash" --squash sub1 && - check_equal ''"$(last_commit_message)"'' "Added subproject with squash" && - undo -' - -test_expect_success 'add subproj to mainline' ' - git subtree add --prefix="sub dir"/ FETCH_HEAD && - check_equal ''"$(last_commit_message)"'' "Add '"'sub dir/'"' from commit '"'"'''"$(git rev-parse sub1)"'''"'"'" -' - -# this shouldn't actually do anything, since FETCH_HEAD is already a parent -test_expect_success 'merge fetched subproj' ' - git merge -m "merge -s -ours" -s ours FETCH_HEAD -' - -test_expect_success 'add main-sub5' ' - create "sub dir/main-sub5" && - git commit -m "main-sub5" -' - -test_expect_success 'add main6' ' - create main6 && - git commit -m "main6 boring" -' - -test_expect_success 'add main-sub7' ' - create "sub dir/main-sub7" && - git commit -m "main-sub7" -' - -test_expect_success 'fetch new subproj history' ' - git fetch ./"sub proj" sub2 && - git branch sub2 FETCH_HEAD -' +test_create_commit() ( + repo=$1 + commit=$2 + cd "$repo" + mkdir -p $(dirname "$commit") \ + || error "Could not create directory for commit" + echo "$commit" >"$commit" + git add "$commit" || error "Could not add commit" + git commit -m "$commit" || error "Could not commit" +) -test_expect_success 'check if --message works for merge' ' - git subtree merge --prefix="sub dir" -m "Merged changes from subproject" sub2 && - check_equal ''"$(last_commit_message)"'' "Merged changes from subproject" && - undo -' +last_commit_message() +{ + git log --pretty=format:%s -1 +} -test_expect_success 'check if --message for merge works with squash too' ' - git subtree merge --prefix "sub dir" -m "Merged changes from subproject using squash" --squash sub2 && - check_equal ''"$(last_commit_message)"'' "Merged changes from subproject using squash" && - undo -' +subtree_test_count=0 +next_test() { + subtree_test_count=$(($subtree_test_count+1)) +} -test_expect_success 'merge new subproj history into subdir' ' - git subtree merge --prefix="sub dir" FETCH_HEAD && - git branch pre-split && - check_equal ''"$(last_commit_message)"'' "Merge commit '"'"'"$(git rev-parse sub2)"'"'"' into mainline" && - undo -' +# +# Tests for 'git subtree add' +# -test_expect_success 'Check that prefix argument is required for split' ' - echo "You must provide the --prefix option." > expected && - test_must_fail git subtree split > actual 2>&1 && - test_debug "printf '"'"'expected: '"'"'" && - test_debug "cat expected" && - test_debug "printf '"'"'actual: '"'"'" && - test_debug "cat actual" && - test_cmp expected actual && - rm -f expected actual +next_test +test_expect_success 'no merge from non-existent subtree' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + test_must_fail git subtree merge --prefix="sub dir" FETCH_HEAD + ) +' + +next_test +test_expect_success 'no pull from non-existent subtree' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + test_must_fail git subtree pull --prefix="sub dir" ./"sub proj" master + )' + +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + check_equal "$(last_commit_message)" "Add '\''sub dir/'\'' from commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) +' + +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" --message="Added subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject" + ) +' + +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --prefix as -P and --message as -m' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add -P "sub dir" -m "Added subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject" + ) +' + +next_test +test_expect_success 'add subproj as subtree into sub dir/ with --squash and --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" --message="Added subproject with squash" --squash FETCH_HEAD && + check_equal "$(last_commit_message)" "Added subproject with squash" + ) ' -test_expect_success 'Check that the <prefix> exists for a split' ' - echo "'"'"'non-existent-directory'"'"'" does not exist\; use "'"'"'git subtree add'"'"'" > expected && - test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 && - test_debug "printf '"'"'expected: '"'"'" && - test_debug "cat expected" && - test_debug "printf '"'"'actual: '"'"'" && - test_debug "cat actual" && - test_cmp expected actual -# rm -f expected actual -' +# +# Tests for 'git subtree merge' +# -test_expect_success 'check if --message works for split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - git branch spl1 "$spl1" && - check_equal ''"$(last_commit_message)"'' "Split & rejoin" && - undo +next_test +test_expect_success 'merge new subproj history into sub dir/ with --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) +' + +next_test +test_expect_success 'merge new subproj history into sub dir/ with --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" --message="Merged changes from subproject" FETCH_HEAD && + check_equal "$(last_commit_message)" "Merged changes from subproject" + ) +' + +next_test +test_expect_success 'merge new subproj history into sub dir/ with --squash and --prefix and --message' ' + subtree_test_create_repo "$subtree_test_count/sub proj" && + subtree_test_create_repo "$subtree_test_count" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" --message="Merged changes from subproject using squash" --squash FETCH_HEAD && + check_equal "$(last_commit_message)" "Merged changes from subproject using squash" + ) +' + +next_test +test_expect_success 'merge the added subproj again, should do nothing' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + # this shouldn not actually do anything, since FETCH_HEAD + # is already a parent + result=$(git merge -s ours -m "merge -s -ours" FETCH_HEAD) && + check_equal "${result}" "Already up-to-date." + ) +' + +next_test +test_expect_success 'merge new subproj history into subdir/ with a slash appended to the argument of --prefix' ' + test_create_repo "$test_count" && + test_create_repo "$test_count/subproj" && + test_create_commit "$test_count" main1 && + test_create_commit "$test_count/subproj" sub1 && + ( + cd "$test_count" && + git fetch ./subproj master && + git subtree add --prefix=subdir/ FETCH_HEAD + ) && + test_create_commit "$test_count/subproj" sub2 && + ( + cd "$test_count" && + git fetch ./subproj master && + git subtree merge --prefix=subdir/ FETCH_HEAD && + check_equal "$(last_commit_message)" "Merge commit '\''$(git rev-parse FETCH_HEAD)'\''" + ) ' -test_expect_success 'check split with --branch' ' - spl1=$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin) && - undo && - git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr1 && - check_equal ''"$(git rev-parse splitbr1)"'' "$spl1" -' +# +# Tests for 'git subtree split' +# +next_test +test_expect_success 'split requires option --prefix' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + echo "You must provide the --prefix option." > expected && + test_must_fail git subtree split > actual 2>&1 && + test_debug "printf '"expected: "'" && + test_debug "cat expected" && + test_debug "printf '"actual: "'" && + test_debug "cat actual" && + test_cmp expected actual + ) +' + +next_test +test_expect_success 'split requires path given by option --prefix must exist' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD && + echo "'\''non-existent-directory'\'' does not exist; use '\''git subtree add'\''" > expected && + test_must_fail git subtree split --prefix=non-existent-directory > actual 2>&1 && + test_debug "printf '"expected: "'" && + test_debug "cat expected" && + test_debug "printf '"actual: "'" && + test_debug "cat actual" && + test_cmp expected actual + ) +' + +next_test +test_expect_success 'split sub dir/ with --rejoin' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --rejoin && + check_equal "$(last_commit_message)" "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" + ) + ' + +next_test +test_expect_success 'split sub dir/ with --rejoin and --message' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --message="Split & rejoin" --annotate="*" --rejoin && + check_equal "$(last_commit_message)" "Split & rejoin" + ) +' + +next_test +test_expect_success 'split "sub dir"/ with --branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" + ) +' + +next_test test_expect_success 'check hash of split' ' - spl1=$(git subtree split --prefix "sub dir") && - git subtree split --prefix "sub dir" --branch splitbr1test && - check_equal ''"$(git rev-parse splitbr1test)"'' "$spl1" && - new_hash=$(git rev-parse splitbr1test~2) && - check_equal ''"$new_hash"'' "$subdir_hash" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" && + # Check hash of split + new_hash=$(git rev-parse subproj-br^2) && + ( + cd ./"sub proj" && + subdir_hash=$(git rev-parse HEAD) && + check_equal ''"$new_hash"'' "$subdir_hash" + ) + ) +' + +next_test +test_expect_success 'split "sub dir"/ with --branch for an existing branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch subproj-br FETCH_HEAD && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br && + check_equal "$(git rev-parse subproj-br)" "$split_hash" + ) +' + +next_test +test_expect_success 'split "sub dir"/ with --branch for an incompatible branch' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git branch init HEAD && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + test_must_fail git subtree split --prefix="sub dir" --branch init + ) ' -test_expect_success 'check split with --branch for an existing branch' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - undo && - git branch splitbr2 sub1 && - git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --branch splitbr2 && - check_equal ''"$(git rev-parse splitbr2)"'' "$spl1" -' - -test_expect_success 'check split with --branch for an incompatible branch' ' - test_must_fail git subtree split --prefix "sub dir" --onto FETCH_HEAD --branch subdir -' - -test_expect_success 'check split+rejoin' ' - spl1=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --message "Split & rejoin" --rejoin)"'' && - undo && - git subtree split --annotate='"'*'"' --prefix "sub dir" --onto FETCH_HEAD --rejoin && - check_equal ''"$(last_commit_message)"'' "Split '"'"'sub dir/'"'"' into commit '"'"'"$spl1"'"'"'" -' - -test_expect_success 'add main-sub8' ' - create "sub dir/main-sub8" && - git commit -m "main-sub8" -' - -# To the subproject! -cd ./"sub proj" - -test_expect_success 'merge split into subproj' ' - git fetch .. spl1 && - git branch spl1 FETCH_HEAD && - git merge FETCH_HEAD -' - -test_expect_success 'add sub9' ' - create sub9 && - git commit -m "sub9" -' - -# Back to mainline -cd .. - -test_expect_success 'split for sub8' ' - split2=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir/" --rejoin)"'' && - git branch split2 "$split2" -' - -test_expect_success 'add main-sub10' ' - create "sub dir/main-sub10" && - git commit -m "main-sub10" -' - -test_expect_success 'split for sub10' ' - spl3=''"$(git subtree split --annotate='"'*'"' --prefix "sub dir" --rejoin)"'' && - git branch spl3 "$spl3" -' - -# To the subproject! -cd ./"sub proj" - -test_expect_success 'merge split into subproj' ' - git fetch .. spl3 && - git branch spl3 FETCH_HEAD && - git merge FETCH_HEAD && - git branch subproj-merge-spl3 -' +# +# Validity checking +# -chkm="main4 -main6" -chkms="main-sub10 -main-sub5 -main-sub7 -main-sub8" -chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,' -$chkms -TXT -) -chks="sub1 +next_test +test_expect_success 'make sure exactly the right set of files ends up in the subproj' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD && + + chks="sub1 sub2 sub3 -sub9" -chks_sub=$(cat <<TXT | sed 's,^,sub dir/,' +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' $chks TXT -) +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && -test_expect_success 'make sure exactly the right set of files ends up in the subproj' ' - subfiles="$(git ls-files)" && - check_equal "$subfiles" "$chkms + subfiles=$(git ls-files) && + check_equal "$subfiles" "$chkms $chks" -' -test_expect_success 'make sure the subproj history *only* contains commits that affect the subdir' ' - allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' && - check_equal "$allchanges" "$chkms + ) +' + +next_test +test_expect_success 'make sure the subproj *only* contains commits that affect the "sub dir"' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD && + + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") && + check_equal "$allchanges" "$chkms $chks" + ) ' -# Back to mainline -cd .. - -test_expect_success 'pull from subproj' ' - git fetch ./"sub proj" subproj-merge-spl3 && - git branch subproj-merge-spl3 FETCH_HEAD && - git subtree pull --prefix="sub dir" ./"sub proj" subproj-merge-spl3 -' - +next_test test_expect_success 'make sure exactly the right set of files ends up in the mainline' ' - mainfiles=$(git ls-files) && - check_equal "$mainfiles" "$chkm + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + chkm="main1 +main2" && + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + mainfiles=$(git ls-files) && + check_equal "$mainfiles" "$chkm $chkms_sub $chks_sub" +) ' +next_test test_expect_success 'make sure each filename changed exactly once in the entire history' ' - # main-sub?? and /subdir/main-sub?? both change, because those are the - # changes that were split into their own history. And subdir/sub?? never - # change, since they were *only* changed in the subtree branch. - allchanges=''"$(git log --name-only --pretty=format:'"''"' | sort | sed "/^$/d")"'' && - check_equal "$allchanges" ''"$(cat <<TXT | sort + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git config log.date relative + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + chkm="main1 +main2" && + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed '\''s,^,sub dir/,'\'' +$chkms +TXT +) && + + # main-sub?? and /"sub dir"/main-sub?? both change, because those are the + # changes that were split into their own history. And "sub dir"/sub?? never + # change, since they were *only* changed in the subtree branch. + allchanges=$(git log --name-only --pretty=format:"" | sort | sed "/^$/d") && + expected=''"$(cat <<TXT | sort $chkms $chkm $chks $chkms_sub TXT -)"'' +)"'' && + check_equal "$allchanges" "$expected" + ) ' +next_test test_expect_success 'make sure the --rejoin commits never make it into subproj' ' - check_equal ''"$(git log --pretty=format:'"'%s'"' HEAD^2 | grep -i split)"'' "" -' - + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + check_equal "$(git log --pretty=format:"%s" HEAD^2 | grep -i split)" "" + ) +' + +next_test test_expect_success 'make sure no "git subtree" tagged commits make it into subproj' ' - # They are meaningless to subproj since one side of the merge refers to the mainline - check_equal ''"$(git log --pretty=format:'"'%s%n%b'"' HEAD^2 | grep "git-subtree.*:")"'' "" -' - -# prepare second pair of repositories -mkdir test2 -cd test2 - -test_expect_success 'init main' ' - test_create_repo main -' - -cd main - -test_expect_success 'add main1' ' - create main1 && - git commit -m "main1" -' - -cd .. - -test_expect_success 'init sub' ' - test_create_repo sub -' - -cd sub - -test_expect_success 'add sub2' ' - create sub2 && - git commit -m "sub2" -' - -cd ../main - -# check if split can find proper base without --onto - -test_expect_success 'add sub as subdir in main' ' - git fetch ../sub master && - git branch sub2 FETCH_HEAD && - git subtree add --prefix "sub dir" sub2 -' - -cd ../sub - -test_expect_success 'add sub3' ' - create sub3 && - git commit -m "sub3" -' - -cd ../main - -test_expect_success 'merge from sub' ' - git fetch ../sub master && - git branch sub3 FETCH_HEAD && - git subtree merge --prefix "sub dir" sub3 -' - -test_expect_success 'add main-sub4' ' - create "sub dir/main-sub4" && - git commit -m "main-sub4" -' - -test_expect_success 'split for main-sub4 without --onto' ' - git subtree split --prefix "sub dir" --branch mainsub4 -' - -# at this point, the new commit parent should be sub3 if it is not, -# something went wrong (the "newparent" of "master~" commit should -# have been sub3, but it was not, because its cache was not set to -# itself) - -test_expect_success 'check that the commit parent is sub3' ' - check_equal ''"$(git log --pretty=format:%P -1 mainsub4)"'' ''"$(git rev-parse sub3)"'' -' - -test_expect_success 'add main-sub5' ' - mkdir subdir2 && - create subdir2/main-sub5 && - git commit -m "main-sub5" -' - -test_expect_success 'split for main-sub5 without --onto' ' - # also test that we still can split out an entirely new subtree - # if the parent of the first commit in the tree is not empty, - # then the new subtree has accidentally been attached to something - git subtree split --prefix subdir2 --branch mainsub5 && - check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' "" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count/sub proj" sub3 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub4 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --annotate="*" --branch subproj-br --rejoin + ) && + ( + cd "$subtree_test_count/sub proj" && + git fetch .. subproj-br && + git merge FETCH_HEAD + ) && + ( + cd "$subtree_test_count" && + git subtree pull --prefix="sub dir" ./"sub proj" master && + + # They are meaningless to subproj since one side of the merge refers to the mainline + check_equal "$(git log --pretty=format:"%s%n%b" HEAD^2 | grep "git-subtree.*:")" "" + ) ' -# make sure no patch changes more than one file. The original set of commits -# changed only one file each. A multi-file change would imply that we pruned -# commits too aggressively. -joincommits() -{ - commit= - all= - while read x y; do - #echo "{$x}" >&2 - if [ -z "$x" ]; then - continue - elif [ "$x" = "commit:" ]; then - if [ -n "$commit" ]; then - echo "$commit $all" - all= - fi - commit="$y" - else - all="$all $y" - fi - done - echo "$commit $all" -} +# +# A new set of tests +# +next_test +test_expect_success 'make sure "git subtree split" find the correct parent' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch subproj-ref FETCH_HEAD && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br && + + # at this point, the new commit parent should be subproj-ref, if it is + # not, something went wrong (the "newparent" of "master~" commit should + # have been sub2, but it was not, because its cache was not set to + # itself) + check_equal "$(git log --pretty=format:%P -1 subproj-br)" "$(git rev-parse subproj-ref)" + ) +' + +next_test +test_expect_success 'split a new subtree without --onto option' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br + ) && + mkdir "$subtree_test_count"/"sub dir2" && + test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 && + ( + cd "$subtree_test_count" && + + # also test that we still can split out an entirely new subtree + # if the parent of the first commit in the tree is not empty, + # then the new subtree has accidently been attached to something + git subtree split --prefix="sub dir2" --branch subproj2-br && + check_equal "$(git log --pretty=format:%P -1 subproj2-br)" "" + ) +' + +next_test test_expect_success 'verify one file change per commit' ' - x= && - list=''"$(git log --pretty=format:'"'commit: %H'"' | joincommits)"'' && -# test_debug "echo HERE" && -# test_debug "echo ''"$list"''" && - (git log --pretty=format:'"'commit: %H'"' | joincommits | - ( while read commit a b; do - test_debug "echo Verifying commit "''"$commit"'' - test_debug "echo a: "''"$a"'' - test_debug "echo b: "''"$b"'' - check_equal "$b" "" - x=1 - done - check_equal "$x" 1 - )) -' - -# test push - -cd ../.. - -mkdir test-push - -cd test-push - -test_expect_success 'init main' ' - test_create_repo main -' - -test_expect_success 'init sub' ' - test_create_repo "sub project" -' - -cd ./"sub project" - -test_expect_success 'add subproject' ' - create "sub project" && - git commit -m "Sub project: 1" && - git branch sub-branch-1 -' - -cd ../main - -test_expect_success 'make first commit and add subproject' ' - create "main-1" && - git commit -m "main: 1" && - git subtree add "../sub project" --prefix "sub dir" --message "Added subproject" sub-branch-1 && - check_equal "$(last_commit_message)" "Added subproject" -' - -test_expect_success 'make second commit to a subproject file and push it into a sub project' ' - create "sub dir/sub1" && - git commit -m "Sub project: 2" && - git subtree push "../sub project" --prefix "sub dir" sub-branch-1 -' - -cd ../"sub project" - -test_expect_success 'Test second commit is pushed' ' - git checkout sub-branch-1 && - check_equal "$(last_commit_message)" "Sub project: 2" + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git branch sub1 FETCH_HEAD && + git subtree add --prefix="sub dir" sub1 + ) && + test_create_commit "$subtree_test_count/sub proj" sub2 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir" --branch subproj-br + ) && + mkdir "$subtree_test_count"/"sub dir2" && + test_create_commit "$subtree_test_count" "sub dir2"/main-sub2 && + ( + cd "$subtree_test_count" && + git subtree split --prefix="sub dir2" --branch subproj2-br && + + x= && + git log --pretty=format:"commit: %H" | join_commits | + ( + while read commit a b; do + test_debug "echo Verifying commit $commit" + test_debug "echo a: $a" + test_debug "echo b: $b" + check_equal "$b" "" + x=1 + done + check_equal "$x" 1 + ) + ) +' + +next_test +test_expect_success 'push split to subproj' ' + subtree_test_create_repo "$subtree_test_count" && + subtree_test_create_repo "$subtree_test_count/sub proj" && + test_create_commit "$subtree_test_count" main1 && + test_create_commit "$subtree_test_count/sub proj" sub1 && + ( + cd "$subtree_test_count" && + git fetch ./"sub proj" master && + git subtree add --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub1 && + test_create_commit "$subtree_test_count" main2 && + test_create_commit "$subtree_test_count/sub proj" sub2 && + test_create_commit "$subtree_test_count" "sub dir"/main-sub2 && + ( + cd $subtree_test_count/"sub proj" && + git branch sub-branch-1 && + cd .. && + git fetch ./"sub proj" master && + git subtree merge --prefix="sub dir" FETCH_HEAD + ) && + test_create_commit "$subtree_test_count" "sub dir"/main-sub3 && + ( + cd "$subtree_test_count" && + git subtree push ./"sub proj" --prefix "sub dir" sub-branch-1 && + cd ./"sub proj" && + git checkout sub-branch-1 && + check_equal "$(last_commit_message)" "sub dir/main-sub3" + ) ' test_done diff --git a/contrib/subtree/todo b/contrib/subtree/todo index 7e44b0024..0d0e77765 100644 --- a/contrib/subtree/todo +++ b/contrib/subtree/todo @@ -12,8 +12,6 @@ exactly the right subtree structure, rather than using subtree merge...) - add a 'push' subcommand to parallel 'pull' - add a 'log' subcommand to see what's new in a subtree? add to-submodule and from-submodule commands diff --git a/credential-cache--daemon.c b/credential-cache--daemon.c index 82715aa8b..9365f2ce5 100644 --- a/credential-cache--daemon.c +++ b/credential-cache--daemon.c @@ -244,6 +244,7 @@ static void check_socket_directory(const char *path) int main(int argc, const char **argv) { const char *socket_path; + int ignore_sighup = 0; static const char *usage[] = { "git-credential-cache--daemon [opts] <socket_path>", NULL @@ -255,6 +256,8 @@ int main(int argc, const char **argv) OPT_END() }; + git_config_get_bool("credentialcache.ignoresighup", &ignore_sighup); + argc = parse_options(argc, argv, NULL, options, usage, 0); socket_path = argv[0]; @@ -263,6 +266,10 @@ int main(int argc, const char **argv) check_socket_directory(socket_path); register_tempfile(&socket_file, socket_path); + + if (ignore_sighup) + signal(SIGHUP, SIG_IGN); + serve_cache(socket_path, debug); delete_tempfile(&socket_file); @@ -711,7 +711,8 @@ static int fsck_tag_buffer(struct tag *tag, const char *data, } } - if (verify_headers(buffer, size, &tag->object, options)) + ret = verify_headers(buffer, size, &tag->object, options); + if (ret) goto done; if (!skip_prefix(buffer, "object ", &buffer)) { diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 27c9c54fb..98f1779cf 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -306,6 +306,15 @@ then start_timestamp=$(date '+%s') fi +if test -n "$filter_index" || + test -n "$filter_tree" || + test -n "$filter_subdir" +then + need_index=t +else + need_index= +fi + while read commit parents; do git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1)) @@ -313,7 +322,10 @@ while read commit parents; do case "$filter_subdir" in "") - GIT_ALLOW_NULL_SHA1=1 git read-tree -i -m $commit + if test -n "$need_index" + then + GIT_ALLOW_NULL_SHA1=1 git read-tree -i -m $commit + fi ;; *) # The commit may not have the subdirectory at all @@ -349,7 +361,7 @@ while read commit parents; do die "tree filter failed: $filter_tree" ( - git diff-index -r --name-only --ignore-submodules $commit && + git diff-index -r --name-only --ignore-submodules $commit -- && git ls-files --others ) > "$tempdir"/tree-state || exit git update-index --add --replace --remove --stdin \ @@ -387,8 +399,15 @@ while read commit parents; do } <../commit | eval "$filter_msg" > ../message || die "msg filter failed: $filter_msg" + + if test -n "$need_index" + then + tree=$(git write-tree) + else + tree="$commit^{tree}" + fi workdir=$workdir @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \ - $(git write-tree) $parentstr < ../message > ../map/$commit || + "$tree" $parentstr < ../message > ../map/$commit || die "could not write rewritten commit" done <../revs @@ -203,14 +203,16 @@ def p4_has_move_command(): # assume it failed because @... was invalid changelist return True -def system(cmd): +def system(cmd, ignore_error=False): expand = isinstance(cmd,basestring) if verbose: sys.stderr.write("executing %s\n" % str(cmd)) retcode = subprocess.call(cmd, shell=expand) - if retcode: + if retcode and not ignore_error: raise CalledProcessError(retcode, cmd) + return retcode + def p4_system(cmd): """Specifically invoke p4 as the system command. """ real_cmd = p4_build_cmd(cmd) @@ -553,7 +555,12 @@ def p4Where(depotPath): return clientPath def currentGitBranch(): - return read_pipe("git name-rev HEAD").split(" ")[1].strip() + retcode = system(["git", "symbolic-ref", "-q", "HEAD"], ignore_error=True) + if retcode != 0: + # on a detached head + return None + else: + return read_pipe(["git", "name-rev", "HEAD"]).split(" ")[1].strip() def isValidGitDir(path): if (os.path.exists(path + "/HEAD") @@ -1741,44 +1748,47 @@ class P4Submit(Command, P4UserMap): # # Let the user edit the change description, then submit it. # - if self.edit_template(fileName): - # read the edited message and submit - ret = True - tmpFile = open(fileName, "rb") - message = tmpFile.read() - tmpFile.close() - if self.isWindows: - message = message.replace("\r\n", "\n") - submitTemplate = message[:message.index(separatorLine)] - p4_write_pipe(['submit', '-i'], submitTemplate) - - if self.preserveUser: - if p4User: - # Get last changelist number. Cannot easily get it from - # the submit command output as the output is - # unmarshalled. - changelist = self.lastP4Changelist() - self.modifyChangelistUser(changelist, p4User) - - # The rename/copy happened by applying a patch that created a - # new file. This leaves it writable, which confuses p4. - for f in pureRenameCopy: - p4_sync(f, "-f") + submitted = False - else: + try: + if self.edit_template(fileName): + # read the edited message and submit + tmpFile = open(fileName, "rb") + message = tmpFile.read() + tmpFile.close() + if self.isWindows: + message = message.replace("\r\n", "\n") + submitTemplate = message[:message.index(separatorLine)] + p4_write_pipe(['submit', '-i'], submitTemplate) + + if self.preserveUser: + if p4User: + # Get last changelist number. Cannot easily get it from + # the submit command output as the output is + # unmarshalled. + changelist = self.lastP4Changelist() + self.modifyChangelistUser(changelist, p4User) + + # The rename/copy happened by applying a patch that created a + # new file. This leaves it writable, which confuses p4. + for f in pureRenameCopy: + p4_sync(f, "-f") + submitted = True + + finally: # skip this patch - ret = False - print "Submission cancelled, undoing p4 changes." - for f in editedFiles: - p4_revert(f) - for f in filesToAdd: - p4_revert(f) - os.remove(f) - for f in filesToDelete: - p4_revert(f) + if not submitted: + print "Submission cancelled, undoing p4 changes." + for f in editedFiles: + p4_revert(f) + for f in filesToAdd: + p4_revert(f) + os.remove(f) + for f in filesToDelete: + p4_revert(f) os.remove(fileName) - return ret + return submitted # Export git tags as p4 labels. Create a p4 label and then tag # with that. @@ -1854,8 +1864,6 @@ class P4Submit(Command, P4UserMap): def run(self, args): if len(args) == 0: self.master = currentGitBranch() - if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master): - die("Detecting current git branch failed!") elif len(args) == 1: self.master = args[0] if not branchExists(self.master): @@ -1863,9 +1871,10 @@ class P4Submit(Command, P4UserMap): else: return False - allowSubmit = gitConfig("git-p4.allowSubmit") - if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","): - die("%s is not in git-p4.allowSubmit" % self.master) + if self.master: + allowSubmit = gitConfig("git-p4.allowSubmit") + if len(allowSubmit) > 0 and not self.master in allowSubmit.split(","): + die("%s is not in git-p4.allowSubmit" % self.master) [upstream, settings] = findUpstreamBranchPoint() self.depotPath = settings['depot-paths'][0] @@ -1933,7 +1942,12 @@ class P4Submit(Command, P4UserMap): self.check() commits = [] - for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, self.master)]): + if self.master: + commitish = self.master + else: + commitish = 'HEAD' + + for line in read_pipe_lines(["git", "rev-list", "--no-merges", "%s..%s" % (self.origin, commitish)]): commits.append(line.strip()) commits.reverse() diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 30edb1792..b938a6d4a 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -610,7 +610,7 @@ do_next () { read -r command rest < "$todo" mark_action_done printf 'Executing: %s\n' "$rest" - ${SHELL:-@SHELL_PATH@} -c "$rest" # Actual execution + "${SHELL:-@SHELL_PATH@}" -c "$rest" # Actual execution status=$? # Run in subshell because require_clean_work_tree can die. dirty=f diff --git a/git-send-email.perl b/git-send-email.perl index e907e0eac..2a3873b74 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -46,6 +46,7 @@ package main; sub usage { print <<EOT; git send-email [options] <file | directory | rev-list options > +git send-email --dump-aliases Composing: --from <str> * Email From: @@ -101,6 +102,9 @@ git send-email [options] <file | directory | rev-list options > `git format-patch` ones. --force * Send even if safety checks would prevent it. + Information: + --dump-aliases * Dump configured aliases and exit. + EOT exit(1); } @@ -180,6 +184,7 @@ my ($quiet, $dry_run) = (0, 0); my $format_patch; my $compose_filename; my $force = 0; +my $dump_aliases = 0; # Handle interactive edition of files. my $multiedit; @@ -239,7 +244,6 @@ my %config_settings = ( "smtpserveroption" => \@smtp_server_options, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, - "smtpsslcertpath" => \$smtp_ssl_cert_path, "smtpdomain" => \$smtp_domain, "smtpauth" => \$smtp_auth, "to" => \@initial_to, @@ -259,6 +263,7 @@ my %config_settings = ( my %config_path_settings = ( "aliasesfile" => \@alias_files, + "smtpsslcertpath" => \$smtp_ssl_cert_path, ); # Handle Uncouth Termination @@ -291,6 +296,11 @@ $SIG{INT} = \&signal_handler; my $help; my $rc = GetOptions("h" => \$help, + "dump-aliases" => \$dump_aliases); +usage() unless $rc; +die "--dump-aliases incompatible with other options\n" + if !$help and $dump_aliases and @ARGV; +$rc = GetOptions( "sender|from=s" => \$sender, "in-reply-to=s" => \$initial_reply_to, "subject=s" => \$initial_subject, @@ -551,6 +561,11 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { } } +if ($dump_aliases) { + print "$_\n" for (sort keys %aliases); + exit(0); +} + # is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if # $f is a revision list specification to be passed to format-patch. sub is_format_patch_arg { @@ -1196,8 +1211,7 @@ sub ssl_verify_params { return (SSL_verify_mode => SSL_VERIFY_PEER(), SSL_ca_file => $smtp_ssl_cert_path); } else { - print STDERR "Not using SSL_VERIFY_PEER because the CA path does not exist.\n"; - return (SSL_verify_mode => SSL_VERIFY_NONE()); + die "CA path \"$smtp_ssl_cert_path\" does not exist"; } } @@ -214,10 +214,10 @@ static int http_options(const char *var, const char *value, void *cb) #endif #if LIBCURL_VERSION_NUM >= 0x070908 if (!strcmp("http.sslcapath", var)) - return git_config_string(&ssl_capath, var, value); + return git_config_pathname(&ssl_capath, var, value); #endif if (!strcmp("http.sslcainfo", var)) - return git_config_string(&ssl_cainfo, var, value); + return git_config_pathname(&ssl_cainfo, var, value); if (!strcmp("http.sslcertpasswordprotected", var)) { ssl_cert_password_required = git_config_bool(var, value); return 0; @@ -464,6 +464,17 @@ static CURL *get_curl_handle(void) if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); +#if LIBCURL_VERSION_NUM >= 0x071800 + if (starts_with(curl_http_proxy, "socks5")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + else if (starts_with(curl_http_proxy, "socks4a")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4A); + else if (starts_with(curl_http_proxy, "socks")) + curl_easy_setopt(result, + CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4); +#endif } #if LIBCURL_VERSION_NUM >= 0x070a07 curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); @@ -1617,8 +1628,8 @@ struct http_pack_request *new_http_pack_request( if (prev_posn>0) { if (http_is_verbose) fprintf(stderr, - "Resuming fetch of pack %s at byte %ld\n", - sha1_to_hex(target->sha1), prev_posn); + "Resuming fetch of pack %s at byte %"PRIuMAX"\n", + sha1_to_hex(target->sha1), (uintmax_t)prev_posn); http_opt_request_remainder(preq->slot->curl, prev_posn); } @@ -1772,8 +1783,8 @@ struct http_object_request *new_http_object_request(const char *base_url, if (prev_posn>0) { if (http_is_verbose) fprintf(stderr, - "Resuming fetch of object %s at byte %ld\n", - hex, prev_posn); + "Resuming fetch of object %s at byte %"PRIuMAX"\n", + hex, (uintmax_t)prev_posn); http_opt_request_remainder(freq->slot->curl, prev_posn); } @@ -70,10 +70,35 @@ static int add_mailname_host(struct strbuf *buf) return 0; } +static int canonical_name(const char *host, struct strbuf *out) +{ + int status = -1; + +#ifndef NO_IPV6 + struct addrinfo hints, *ai; + memset (&hints, '\0', sizeof (hints)); + hints.ai_flags = AI_CANONNAME; + if (!getaddrinfo(host, NULL, &hints, &ai)) { + if (ai && strchr(ai->ai_canonname, '.')) { + strbuf_addstr(out, ai->ai_canonname); + status = 0; + } + freeaddrinfo(ai); + } +#else + struct hostent *he = gethostbyname(buf); + if (he && strchr(he->h_name, '.')) { + strbuf_addstr(out, he->h_name); + status = 0; + } +#endif /* NO_IPV6 */ + + return status; +} + static void add_domainname(struct strbuf *out) { char buf[1024]; - struct hostent *he; if (gethostname(buf, sizeof(buf))) { warning("cannot get host name: %s", strerror(errno)); @@ -82,9 +107,7 @@ static void add_domainname(struct strbuf *out) } if (strchr(buf, '.')) strbuf_addstr(out, buf); - else if ((he = gethostbyname(buf)) && strchr(he->h_name, '.')) - strbuf_addstr(out, he->h_name); - else + else if (canonical_name(buf, out) < 0) strbuf_addf(out, "%s.(none)", buf); } diff --git a/pack-check.c b/pack-check.c index 63a595c45..433bd86cc 100644 --- a/pack-check.c +++ b/pack-check.c @@ -126,7 +126,7 @@ static int verify_packfile(struct packed_git *p, sha1_to_hex(entries[i].sha1), p->pack_name); else if (fn) { int eaten = 0; - fn(entries[i].sha1, type, size, data, &eaten); + err |= fn(entries[i].sha1, type, size, data, &eaten); if (eaten) data = NULL; } diff --git a/parse-options.c b/parse-options.c index 3eceba446..47a919206 100644 --- a/parse-options.c +++ b/parse-options.c @@ -5,10 +5,6 @@ #include "color.h" #include "utf8.h" -static int parse_options_usage(struct parse_opt_ctx_t *ctx, - const char * const *usagestr, - const struct option *opts, int err); - #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -414,7 +410,7 @@ void parse_options_start(struct parse_opt_ctx_t *ctx, const struct option *options, int flags) { memset(ctx, 0, sizeof(*ctx)); - ctx->argc = argc - 1; + ctx->argc = ctx->total = argc - 1; ctx->argv = argv + 1; ctx->out = argv; ctx->prefix = prefix; @@ -435,6 +431,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, const char * const usagestr[]) { int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP); + int err = 0; /* we must reset ->opt, unknown short option leave it dangling */ ctx->opt = NULL; @@ -451,27 +448,32 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, continue; } + /* lone -h asks for help */ + if (internal_help && ctx->total == 1 && !strcmp(arg + 1, "h")) + goto show_usage; + if (arg[1] != '-') { ctx->opt = arg + 1; - if (internal_help && *ctx->opt == 'h') - return parse_options_usage(ctx, usagestr, options, 0); switch (parse_short_opt(ctx, options)) { case -1: - return parse_options_usage(ctx, usagestr, options, 1); + goto show_usage_error; case -2: if (ctx->opt) check_typos(arg + 1, options); + if (internal_help && *ctx->opt == 'h') + goto show_usage; goto unknown; } if (ctx->opt) check_typos(arg + 1, options); while (ctx->opt) { - if (internal_help && *ctx->opt == 'h') - return parse_options_usage(ctx, usagestr, options, 0); switch (parse_short_opt(ctx, options)) { case -1: - return parse_options_usage(ctx, usagestr, options, 1); + goto show_usage_error; case -2: + if (internal_help && *ctx->opt == 'h') + goto show_usage; + /* fake a short option thing to hide the fact that we may have * started to parse aggregated stuff * @@ -496,10 +498,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (internal_help && !strcmp(arg + 2, "help-all")) return usage_with_options_internal(ctx, usagestr, options, 1, 0); if (internal_help && !strcmp(arg + 2, "help")) - return parse_options_usage(ctx, usagestr, options, 0); + goto show_usage; switch (parse_long_opt(ctx, arg + 2, options)) { case -1: - return parse_options_usage(ctx, usagestr, options, 1); + goto show_usage_error; case -2: goto unknown; } @@ -511,6 +513,11 @@ unknown: ctx->opt = NULL; } return PARSE_OPT_DONE; + + show_usage_error: + err = 1; + show_usage: + return usage_with_options_internal(ctx, usagestr, options, 0, err); } int parse_options_end(struct parse_opt_ctx_t *ctx) @@ -656,13 +663,6 @@ void NORETURN usage_msg_opt(const char *msg, usage_with_options(usagestr, options); } -static int parse_options_usage(struct parse_opt_ctx_t *ctx, - const char * const *usagestr, - const struct option *opts, int err) -{ - return usage_with_options_internal(ctx, usagestr, opts, 0, err); -} - #undef opterror int opterror(const struct option *opt, const char *reason, int flags) { diff --git a/parse-options.h b/parse-options.h index e8b55ea87..ea4af92a5 100644 --- a/parse-options.h +++ b/parse-options.h @@ -199,7 +199,7 @@ enum { struct parse_opt_ctx_t { const char **argv; const char **out; - int argc, cpidx; + int argc, cpidx, total; const char *opt; int flags; const char *prefix; @@ -740,6 +740,18 @@ int adjust_shared_perm(const char *path) return 0; } +void safe_create_dir(const char *dir, int share) +{ + if (mkdir(dir, 0777) < 0) { + if (errno != EEXIST) { + perror(dir); + exit(1); + } + } + else if (share && adjust_shared_perm(dir)) + die(_("Could not make %s writable by group"), dir); +} + static int have_same_root(const char *path1, const char *path2) { int is_abs1, is_abs2; diff --git a/ppc/sha1.h b/ppc/sha1.h index c405f734c..9b24b3261 100644 --- a/ppc/sha1.h +++ b/ppc/sha1.h @@ -19,7 +19,7 @@ int ppc_SHA1_Init(ppc_SHA_CTX *c); int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *p, unsigned long n); int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c); -#define git_SHA_CTX ppc_SHA_CTX -#define git_SHA1_Init ppc_SHA1_Init -#define git_SHA1_Update ppc_SHA1_Update -#define git_SHA1_Final ppc_SHA1_Final +#define platform_SHA_CTX ppc_SHA_CTX +#define platform_SHA1_Init ppc_SHA1_Init +#define platform_SHA1_Update ppc_SHA1_Update +#define platform_SHA1_Final ppc_SHA1_Final @@ -1,17 +1,13 @@ +/* + * The backend-independent part of the reference module. + */ + #include "cache.h" #include "lockfile.h" #include "refs.h" +#include "refs/refs-internal.h" #include "object.h" #include "tag.h" -#include "dir.h" -#include "string-list.h" - -struct ref_lock { - char *ref_name; - char *orig_ref_name; - struct lock_file *lk; - struct object_id old_oid; -}; /* * How to handle various characters in refnames: @@ -35,41 +31,6 @@ static unsigned char refname_disposition[256] = { }; /* - * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken - * refs (i.e., because the reference is about to be deleted anyway). - */ -#define REF_DELETING 0x02 - -/* - * Used as a flag in ref_update::flags when a loose ref is being - * pruned. - */ -#define REF_ISPRUNING 0x04 - -/* - * Used as a flag in ref_update::flags when the reference should be - * updated to new_sha1. - */ -#define REF_HAVE_NEW 0x08 - -/* - * Used as a flag in ref_update::flags when old_sha1 should be - * checked. - */ -#define REF_HAVE_OLD 0x10 - -/* - * Used as a flag in ref_update::flags when the lockfile needs to be - * committed. - */ -#define REF_NEEDS_COMMIT 0x20 - -/* - * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a - * value to ref_update::flags - */ - -/* * Try to read one refname component from the front of refname. * Return the length of the component found, or -1 if the component is * not legal. It is legal if it is something reasonable to have under @@ -157,199 +118,7 @@ int check_refname_format(const char *refname, int flags) return 0; } -struct ref_entry; - -/* - * Information used (along with the information in ref_entry) to - * describe a single cached reference. This data structure only - * occurs embedded in a union in struct ref_entry, and only when - * (ref_entry->flag & REF_DIR) is zero. - */ -struct ref_value { - /* - * The name of the object to which this reference resolves - * (which may be a tag object). If REF_ISBROKEN, this is - * null. If REF_ISSYMREF, then this is the name of the object - * referred to by the last reference in the symlink chain. - */ - struct object_id oid; - - /* - * If REF_KNOWS_PEELED, then this field holds the peeled value - * of this reference, or null if the reference is known not to - * be peelable. See the documentation for peel_ref() for an - * exact definition of "peelable". - */ - struct object_id peeled; -}; - -struct ref_cache; - -/* - * Information used (along with the information in ref_entry) to - * describe a level in the hierarchy of references. This data - * structure only occurs embedded in a union in struct ref_entry, and - * only when (ref_entry.flag & REF_DIR) is set. In that case, - * (ref_entry.flag & REF_INCOMPLETE) determines whether the references - * in the directory have already been read: - * - * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose - * or packed references, already read. - * - * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose - * references that hasn't been read yet (nor has any of its - * subdirectories). - * - * Entries within a directory are stored within a growable array of - * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i < - * sorted are sorted by their component name in strcmp() order and the - * remaining entries are unsorted. - * - * Loose references are read lazily, one directory at a time. When a - * directory of loose references is read, then all of the references - * in that directory are stored, and REF_INCOMPLETE stubs are created - * for any subdirectories, but the subdirectories themselves are not - * read. The reading is triggered by get_ref_dir(). - */ -struct ref_dir { - int nr, alloc; - - /* - * Entries with index 0 <= i < sorted are sorted by name. New - * entries are appended to the list unsorted, and are sorted - * only when required; thus we avoid the need to sort the list - * after the addition of every reference. - */ - int sorted; - - /* A pointer to the ref_cache that contains this ref_dir. */ - struct ref_cache *ref_cache; - - struct ref_entry **entries; -}; - -/* - * Bit values for ref_entry::flag. REF_ISSYMREF=0x01, - * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are - * public values; see refs.h. - */ - -/* - * The field ref_entry->u.value.peeled of this value entry contains - * the correct peeled value for the reference, which might be - * null_sha1 if the reference is not a tag or if it is broken. - */ -#define REF_KNOWS_PEELED 0x10 - -/* ref_entry represents a directory of references */ -#define REF_DIR 0x20 - -/* - * Entry has not yet been read from disk (used only for REF_DIR - * entries representing loose references) - */ -#define REF_INCOMPLETE 0x40 - -/* - * A ref_entry represents either a reference or a "subdirectory" of - * references. - * - * Each directory in the reference namespace is represented by a - * ref_entry with (flags & REF_DIR) set and containing a subdir member - * that holds the entries in that directory that have been read so - * far. If (flags & REF_INCOMPLETE) is set, then the directory and - * its subdirectories haven't been read yet. REF_INCOMPLETE is only - * used for loose reference directories. - * - * References are represented by a ref_entry with (flags & REF_DIR) - * unset and a value member that describes the reference's value. The - * flag member is at the ref_entry level, but it is also needed to - * interpret the contents of the value field (in other words, a - * ref_value object is not very much use without the enclosing - * ref_entry). - * - * Reference names cannot end with slash and directories' names are - * always stored with a trailing slash (except for the top-level - * directory, which is always denoted by ""). This has two nice - * consequences: (1) when the entries in each subdir are sorted - * lexicographically by name (as they usually are), the references in - * a whole tree can be generated in lexicographic order by traversing - * the tree in left-to-right, depth-first order; (2) the names of - * references and subdirectories cannot conflict, and therefore the - * presence of an empty subdirectory does not block the creation of a - * similarly-named reference. (The fact that reference names with the - * same leading components can conflict *with each other* is a - * separate issue that is regulated by verify_refname_available().) - * - * Please note that the name field contains the fully-qualified - * reference (or subdirectory) name. Space could be saved by only - * storing the relative names. But that would require the full names - * to be generated on the fly when iterating in do_for_each_ref(), and - * would break callback functions, who have always been able to assume - * that the name strings that they are passed will not be freed during - * the iteration. - */ -struct ref_entry { - unsigned char flag; /* ISSYMREF? ISPACKED? */ - union { - struct ref_value value; /* if not (flags&REF_DIR) */ - struct ref_dir subdir; /* if (flags&REF_DIR) */ - } u; - /* - * The full name of the reference (e.g., "refs/heads/master") - * or the full name of the directory with a trailing slash - * (e.g., "refs/heads/"): - */ - char name[FLEX_ARRAY]; -}; - -static void read_loose_refs(const char *dirname, struct ref_dir *dir); -static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len); -static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, - const char *dirname, size_t len, - int incomplete); -static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); - -static struct ref_dir *get_ref_dir(struct ref_entry *entry) -{ - struct ref_dir *dir; - assert(entry->flag & REF_DIR); - dir = &entry->u.subdir; - if (entry->flag & REF_INCOMPLETE) { - read_loose_refs(entry->name, dir); - - /* - * Manually add refs/bisect, which, being - * per-worktree, might not appear in the directory - * listing for refs/ in the main repo. - */ - if (!strcmp(entry->name, "refs/")) { - int pos = search_ref_dir(dir, "refs/bisect/", 12); - if (pos < 0) { - struct ref_entry *child_entry; - child_entry = create_dir_entry(dir->ref_cache, - "refs/bisect/", - 12, 1); - add_entry_to_dir(dir, child_entry); - read_loose_refs("refs/bisect", - &child_entry->u.subdir); - } - } - entry->flag &= ~REF_INCOMPLETE; - } - return dir; -} - -/* - * Check if a refname is safe. - * For refs that start with "refs/" we consider it safe as long they do - * not try to resolve to outside of refs/. - * - * For all other refs we only consider them safe iff they only contain - * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like - * "config"). - */ -static int refname_is_safe(const char *refname) +int refname_is_safe(const char *refname) { if (starts_with(refname, "refs/")) { char *buf; @@ -373,1422 +142,6 @@ static int refname_is_safe(const char *refname) return 1; } -static struct ref_entry *create_ref_entry(const char *refname, - const unsigned char *sha1, int flag, - int check_name) -{ - int len; - struct ref_entry *ref; - - if (check_name && - check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) - die("Reference has invalid format: '%s'", refname); - len = strlen(refname) + 1; - ref = xmalloc(sizeof(struct ref_entry) + len); - hashcpy(ref->u.value.oid.hash, sha1); - oidclr(&ref->u.value.peeled); - memcpy(ref->name, refname, len); - ref->flag = flag; - return ref; -} - -static void clear_ref_dir(struct ref_dir *dir); - -static void free_ref_entry(struct ref_entry *entry) -{ - if (entry->flag & REF_DIR) { - /* - * Do not use get_ref_dir() here, as that might - * trigger the reading of loose refs. - */ - clear_ref_dir(&entry->u.subdir); - } - free(entry); -} - -/* - * Add a ref_entry to the end of dir (unsorted). Entry is always - * stored directly in dir; no recursion into subdirectories is - * done. - */ -static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry) -{ - ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc); - dir->entries[dir->nr++] = entry; - /* optimize for the case that entries are added in order */ - if (dir->nr == 1 || - (dir->nr == dir->sorted + 1 && - strcmp(dir->entries[dir->nr - 2]->name, - dir->entries[dir->nr - 1]->name) < 0)) - dir->sorted = dir->nr; -} - -/* - * Clear and free all entries in dir, recursively. - */ -static void clear_ref_dir(struct ref_dir *dir) -{ - int i; - for (i = 0; i < dir->nr; i++) - free_ref_entry(dir->entries[i]); - free(dir->entries); - dir->sorted = dir->nr = dir->alloc = 0; - dir->entries = NULL; -} - -/* - * Create a struct ref_entry object for the specified dirname. - * dirname is the name of the directory with a trailing slash (e.g., - * "refs/heads/") or "" for the top-level directory. - */ -static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, - const char *dirname, size_t len, - int incomplete) -{ - struct ref_entry *direntry; - direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1); - memcpy(direntry->name, dirname, len); - direntry->name[len] = '\0'; - direntry->u.subdir.ref_cache = ref_cache; - direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); - return direntry; -} - -static int ref_entry_cmp(const void *a, const void *b) -{ - struct ref_entry *one = *(struct ref_entry **)a; - struct ref_entry *two = *(struct ref_entry **)b; - return strcmp(one->name, two->name); -} - -static void sort_ref_dir(struct ref_dir *dir); - -struct string_slice { - size_t len; - const char *str; -}; - -static int ref_entry_cmp_sslice(const void *key_, const void *ent_) -{ - const struct string_slice *key = key_; - const struct ref_entry *ent = *(const struct ref_entry * const *)ent_; - int cmp = strncmp(key->str, ent->name, key->len); - if (cmp) - return cmp; - return '\0' - (unsigned char)ent->name[key->len]; -} - -/* - * Return the index of the entry with the given refname from the - * ref_dir (non-recursively), sorting dir if necessary. Return -1 if - * no such entry is found. dir must already be complete. - */ -static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) -{ - struct ref_entry **r; - struct string_slice key; - - if (refname == NULL || !dir->nr) - return -1; - - sort_ref_dir(dir); - key.len = len; - key.str = refname; - r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries), - ref_entry_cmp_sslice); - - if (r == NULL) - return -1; - - return r - dir->entries; -} - -/* - * Search for a directory entry directly within dir (without - * recursing). Sort dir if necessary. subdirname must be a directory - * name (i.e., end in '/'). If mkdir is set, then create the - * directory if it is missing; otherwise, return NULL if the desired - * directory cannot be found. dir must already be complete. - */ -static struct ref_dir *search_for_subdir(struct ref_dir *dir, - const char *subdirname, size_t len, - int mkdir) -{ - int entry_index = search_ref_dir(dir, subdirname, len); - struct ref_entry *entry; - if (entry_index == -1) { - if (!mkdir) - return NULL; - /* - * Since dir is complete, the absence of a subdir - * means that the subdir really doesn't exist; - * therefore, create an empty record for it but mark - * the record complete. - */ - entry = create_dir_entry(dir->ref_cache, subdirname, len, 0); - add_entry_to_dir(dir, entry); - } else { - entry = dir->entries[entry_index]; - } - return get_ref_dir(entry); -} - -/* - * If refname is a reference name, find the ref_dir within the dir - * tree that should hold refname. If refname is a directory name - * (i.e., ends in '/'), then return that ref_dir itself. dir must - * represent the top-level directory and must already be complete. - * Sort ref_dirs and recurse into subdirectories as necessary. If - * mkdir is set, then create any missing directories; otherwise, - * return NULL if the desired directory cannot be found. - */ -static struct ref_dir *find_containing_dir(struct ref_dir *dir, - const char *refname, int mkdir) -{ - const char *slash; - for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { - size_t dirnamelen = slash - refname + 1; - struct ref_dir *subdir; - subdir = search_for_subdir(dir, refname, dirnamelen, mkdir); - if (!subdir) { - dir = NULL; - break; - } - dir = subdir; - } - - return dir; -} - -/* - * Find the value entry with the given name in dir, sorting ref_dirs - * and recursing into subdirectories as necessary. If the name is not - * found or it corresponds to a directory entry, return NULL. - */ -static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname) -{ - int entry_index; - struct ref_entry *entry; - dir = find_containing_dir(dir, refname, 0); - if (!dir) - return NULL; - entry_index = search_ref_dir(dir, refname, strlen(refname)); - if (entry_index == -1) - return NULL; - entry = dir->entries[entry_index]; - return (entry->flag & REF_DIR) ? NULL : entry; -} - -/* - * Remove the entry with the given name from dir, recursing into - * subdirectories as necessary. If refname is the name of a directory - * (i.e., ends with '/'), then remove the directory and its contents. - * If the removal was successful, return the number of entries - * remaining in the directory entry that contained the deleted entry. - * If the name was not found, return -1. Please note that this - * function only deletes the entry from the cache; it does not delete - * it from the filesystem or ensure that other cache entries (which - * might be symbolic references to the removed entry) are updated. - * Nor does it remove any containing dir entries that might be made - * empty by the removal. dir must represent the top-level directory - * and must already be complete. - */ -static int remove_entry(struct ref_dir *dir, const char *refname) -{ - int refname_len = strlen(refname); - int entry_index; - struct ref_entry *entry; - int is_dir = refname[refname_len - 1] == '/'; - if (is_dir) { - /* - * refname represents a reference directory. Remove - * the trailing slash; otherwise we will get the - * directory *representing* refname rather than the - * one *containing* it. - */ - char *dirname = xmemdupz(refname, refname_len - 1); - dir = find_containing_dir(dir, dirname, 0); - free(dirname); - } else { - dir = find_containing_dir(dir, refname, 0); - } - if (!dir) - return -1; - entry_index = search_ref_dir(dir, refname, refname_len); - if (entry_index == -1) - return -1; - entry = dir->entries[entry_index]; - - memmove(&dir->entries[entry_index], - &dir->entries[entry_index + 1], - (dir->nr - entry_index - 1) * sizeof(*dir->entries) - ); - dir->nr--; - if (dir->sorted > entry_index) - dir->sorted--; - free_ref_entry(entry); - return dir->nr; -} - -/* - * Add a ref_entry to the ref_dir (unsorted), recursing into - * subdirectories as necessary. dir must represent the top-level - * directory. Return 0 on success. - */ -static int add_ref(struct ref_dir *dir, struct ref_entry *ref) -{ - dir = find_containing_dir(dir, ref->name, 1); - if (!dir) - return -1; - add_entry_to_dir(dir, ref); - return 0; -} - -/* - * Emit a warning and return true iff ref1 and ref2 have the same name - * and the same sha1. Die if they have the same name but different - * sha1s. - */ -static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2) -{ - if (strcmp(ref1->name, ref2->name)) - return 0; - - /* Duplicate name; make sure that they don't conflict: */ - - if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR)) - /* This is impossible by construction */ - die("Reference directory conflict: %s", ref1->name); - - if (oidcmp(&ref1->u.value.oid, &ref2->u.value.oid)) - die("Duplicated ref, and SHA1s don't match: %s", ref1->name); - - warning("Duplicated ref: %s", ref1->name); - return 1; -} - -/* - * Sort the entries in dir non-recursively (if they are not already - * sorted) and remove any duplicate entries. - */ -static void sort_ref_dir(struct ref_dir *dir) -{ - int i, j; - struct ref_entry *last = NULL; - - /* - * This check also prevents passing a zero-length array to qsort(), - * which is a problem on some platforms. - */ - if (dir->sorted == dir->nr) - return; - - qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp); - - /* Remove any duplicates: */ - for (i = 0, j = 0; j < dir->nr; j++) { - struct ref_entry *entry = dir->entries[j]; - if (last && is_dup_ref(last, entry)) - free_ref_entry(entry); - else - last = dir->entries[i++] = entry; - } - dir->sorted = dir->nr = i; -} - -/* Include broken references in a do_for_each_ref*() iteration: */ -#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 - -/* - * Return true iff the reference described by entry can be resolved to - * an object in the database. Emit a warning if the referred-to - * object does not exist. - */ -static int ref_resolves_to_object(struct ref_entry *entry) -{ - if (entry->flag & REF_ISBROKEN) - return 0; - if (!has_sha1_file(entry->u.value.oid.hash)) { - error("%s does not point to a valid object!", entry->name); - return 0; - } - return 1; -} - -/* - * current_ref is a performance hack: when iterating over references - * using the for_each_ref*() functions, current_ref is set to the - * current reference's entry before calling the callback function. If - * the callback function calls peel_ref(), then peel_ref() first - * checks whether the reference to be peeled is the current reference - * (it usually is) and if so, returns that reference's peeled version - * if it is available. This avoids a refname lookup in a common case. - */ -static struct ref_entry *current_ref; - -typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data); - -struct ref_entry_cb { - const char *base; - int trim; - int flags; - each_ref_fn *fn; - void *cb_data; -}; - -/* - * Handle one reference in a do_for_each_ref*()-style iteration, - * calling an each_ref_fn for each entry. - */ -static int do_one_ref(struct ref_entry *entry, void *cb_data) -{ - struct ref_entry_cb *data = cb_data; - struct ref_entry *old_current_ref; - int retval; - - if (!starts_with(entry->name, data->base)) - return 0; - - if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) && - !ref_resolves_to_object(entry)) - return 0; - - /* Store the old value, in case this is a recursive call: */ - old_current_ref = current_ref; - current_ref = entry; - retval = data->fn(entry->name + data->trim, &entry->u.value.oid, - entry->flag, data->cb_data); - current_ref = old_current_ref; - return retval; -} - -/* - * Call fn for each reference in dir that has index in the range - * offset <= index < dir->nr. Recurse into subdirectories that are in - * that index range, sorting them before iterating. This function - * does not sort dir itself; it should be sorted beforehand. fn is - * called for all references, including broken ones. - */ -static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset, - each_ref_entry_fn fn, void *cb_data) -{ - int i; - assert(dir->sorted == dir->nr); - for (i = offset; i < dir->nr; i++) { - struct ref_entry *entry = dir->entries[i]; - int retval; - if (entry->flag & REF_DIR) { - struct ref_dir *subdir = get_ref_dir(entry); - sort_ref_dir(subdir); - retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data); - } else { - retval = fn(entry, cb_data); - } - if (retval) - return retval; - } - return 0; -} - -/* - * Call fn for each reference in the union of dir1 and dir2, in order - * by refname. Recurse into subdirectories. If a value entry appears - * in both dir1 and dir2, then only process the version that is in - * dir2. The input dirs must already be sorted, but subdirs will be - * sorted as needed. fn is called for all references, including - * broken ones. - */ -static int do_for_each_entry_in_dirs(struct ref_dir *dir1, - struct ref_dir *dir2, - each_ref_entry_fn fn, void *cb_data) -{ - int retval; - int i1 = 0, i2 = 0; - - assert(dir1->sorted == dir1->nr); - assert(dir2->sorted == dir2->nr); - while (1) { - struct ref_entry *e1, *e2; - int cmp; - if (i1 == dir1->nr) { - return do_for_each_entry_in_dir(dir2, i2, fn, cb_data); - } - if (i2 == dir2->nr) { - return do_for_each_entry_in_dir(dir1, i1, fn, cb_data); - } - e1 = dir1->entries[i1]; - e2 = dir2->entries[i2]; - cmp = strcmp(e1->name, e2->name); - if (cmp == 0) { - if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) { - /* Both are directories; descend them in parallel. */ - struct ref_dir *subdir1 = get_ref_dir(e1); - struct ref_dir *subdir2 = get_ref_dir(e2); - sort_ref_dir(subdir1); - sort_ref_dir(subdir2); - retval = do_for_each_entry_in_dirs( - subdir1, subdir2, fn, cb_data); - i1++; - i2++; - } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) { - /* Both are references; ignore the one from dir1. */ - retval = fn(e2, cb_data); - i1++; - i2++; - } else { - die("conflict between reference and directory: %s", - e1->name); - } - } else { - struct ref_entry *e; - if (cmp < 0) { - e = e1; - i1++; - } else { - e = e2; - i2++; - } - if (e->flag & REF_DIR) { - struct ref_dir *subdir = get_ref_dir(e); - sort_ref_dir(subdir); - retval = do_for_each_entry_in_dir( - subdir, 0, fn, cb_data); - } else { - retval = fn(e, cb_data); - } - } - if (retval) - return retval; - } -} - -/* - * Load all of the refs from the dir into our in-memory cache. The hard work - * of loading loose refs is done by get_ref_dir(), so we just need to recurse - * through all of the sub-directories. We do not even need to care about - * sorting, as traversal order does not matter to us. - */ -static void prime_ref_dir(struct ref_dir *dir) -{ - int i; - for (i = 0; i < dir->nr; i++) { - struct ref_entry *entry = dir->entries[i]; - if (entry->flag & REF_DIR) - prime_ref_dir(get_ref_dir(entry)); - } -} - -struct nonmatching_ref_data { - const struct string_list *skip; - const char *conflicting_refname; -}; - -static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata) -{ - struct nonmatching_ref_data *data = vdata; - - if (data->skip && string_list_has_string(data->skip, entry->name)) - return 0; - - data->conflicting_refname = entry->name; - return 1; -} - -/* - * Return 0 if a reference named refname could be created without - * conflicting with the name of an existing reference in dir. - * Otherwise, return a negative value and write an explanation to err. - * If extras is non-NULL, it is a list of additional refnames with - * which refname is not allowed to conflict. If skip is non-NULL, - * ignore potential conflicts with refs in skip (e.g., because they - * are scheduled for deletion in the same operation). Behavior is - * undefined if the same name is listed in both extras and skip. - * - * Two reference names conflict if one of them exactly matches the - * leading components of the other; e.g., "refs/foo/bar" conflicts - * with both "refs/foo" and with "refs/foo/bar/baz" but not with - * "refs/foo/bar" or "refs/foo/barbados". - * - * extras and skip must be sorted. - */ -static int verify_refname_available(const char *refname, - const struct string_list *extras, - const struct string_list *skip, - struct ref_dir *dir, - struct strbuf *err) -{ - const char *slash; - int pos; - struct strbuf dirname = STRBUF_INIT; - int ret = -1; - - /* - * For the sake of comments in this function, suppose that - * refname is "refs/foo/bar". - */ - - assert(err); - - strbuf_grow(&dirname, strlen(refname) + 1); - for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { - /* Expand dirname to the new prefix, not including the trailing slash: */ - strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); - - /* - * We are still at a leading dir of the refname (e.g., - * "refs/foo"; if there is a reference with that name, - * it is a conflict, *unless* it is in skip. - */ - if (dir) { - pos = search_ref_dir(dir, dirname.buf, dirname.len); - if (pos >= 0 && - (!skip || !string_list_has_string(skip, dirname.buf))) { - /* - * We found a reference whose name is - * a proper prefix of refname; e.g., - * "refs/foo", and is not in skip. - */ - strbuf_addf(err, "'%s' exists; cannot create '%s'", - dirname.buf, refname); - goto cleanup; - } - } - - if (extras && string_list_has_string(extras, dirname.buf) && - (!skip || !string_list_has_string(skip, dirname.buf))) { - strbuf_addf(err, "cannot process '%s' and '%s' at the same time", - refname, dirname.buf); - goto cleanup; - } - - /* - * Otherwise, we can try to continue our search with - * the next component. So try to look up the - * directory, e.g., "refs/foo/". If we come up empty, - * we know there is nothing under this whole prefix, - * but even in that case we still have to continue the - * search for conflicts with extras. - */ - strbuf_addch(&dirname, '/'); - if (dir) { - pos = search_ref_dir(dir, dirname.buf, dirname.len); - if (pos < 0) { - /* - * There was no directory "refs/foo/", - * so there is nothing under this - * whole prefix. So there is no need - * to continue looking for conflicting - * references. But we need to continue - * looking for conflicting extras. - */ - dir = NULL; - } else { - dir = get_ref_dir(dir->entries[pos]); - } - } - } - - /* - * We are at the leaf of our refname (e.g., "refs/foo/bar"). - * There is no point in searching for a reference with that - * name, because a refname isn't considered to conflict with - * itself. But we still need to check for references whose - * names are in the "refs/foo/bar/" namespace, because they - * *do* conflict. - */ - strbuf_addstr(&dirname, refname + dirname.len); - strbuf_addch(&dirname, '/'); - - if (dir) { - pos = search_ref_dir(dir, dirname.buf, dirname.len); - - if (pos >= 0) { - /* - * We found a directory named "$refname/" - * (e.g., "refs/foo/bar/"). It is a problem - * iff it contains any ref that is not in - * "skip". - */ - struct nonmatching_ref_data data; - - data.skip = skip; - data.conflicting_refname = NULL; - dir = get_ref_dir(dir->entries[pos]); - sort_ref_dir(dir); - if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) { - strbuf_addf(err, "'%s' exists; cannot create '%s'", - data.conflicting_refname, refname); - goto cleanup; - } - } - } - - if (extras) { - /* - * Check for entries in extras that start with - * "$refname/". We do that by looking for the place - * where "$refname/" would be inserted in extras. If - * there is an entry at that position that starts with - * "$refname/" and is not in skip, then we have a - * conflict. - */ - for (pos = string_list_find_insert_index(extras, dirname.buf, 0); - pos < extras->nr; pos++) { - const char *extra_refname = extras->items[pos].string; - - if (!starts_with(extra_refname, dirname.buf)) - break; - - if (!skip || !string_list_has_string(skip, extra_refname)) { - strbuf_addf(err, "cannot process '%s' and '%s' at the same time", - refname, extra_refname); - goto cleanup; - } - } - } - - /* No conflicts were found */ - ret = 0; - -cleanup: - strbuf_release(&dirname); - return ret; -} - -struct packed_ref_cache { - struct ref_entry *root; - - /* - * Count of references to the data structure in this instance, - * including the pointer from ref_cache::packed if any. The - * data will not be freed as long as the reference count is - * nonzero. - */ - unsigned int referrers; - - /* - * Iff the packed-refs file associated with this instance is - * currently locked for writing, this points at the associated - * lock (which is owned by somebody else). The referrer count - * is also incremented when the file is locked and decremented - * when it is unlocked. - */ - struct lock_file *lock; - - /* The metadata from when this packed-refs cache was read */ - struct stat_validity validity; -}; - -/* - * Future: need to be in "struct repository" - * when doing a full libification. - */ -static struct ref_cache { - struct ref_cache *next; - struct ref_entry *loose; - struct packed_ref_cache *packed; - /* - * The submodule name, or "" for the main repo. We allocate - * length 1 rather than FLEX_ARRAY so that the main ref_cache - * is initialized correctly. - */ - char name[1]; -} ref_cache, *submodule_ref_caches; - -/* Lock used for the main packed-refs file: */ -static struct lock_file packlock; - -/* - * Increment the reference count of *packed_refs. - */ -static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs) -{ - packed_refs->referrers++; -} - -/* - * Decrease the reference count of *packed_refs. If it goes to zero, - * free *packed_refs and return true; otherwise return false. - */ -static int release_packed_ref_cache(struct packed_ref_cache *packed_refs) -{ - if (!--packed_refs->referrers) { - free_ref_entry(packed_refs->root); - stat_validity_clear(&packed_refs->validity); - free(packed_refs); - return 1; - } else { - return 0; - } -} - -static void clear_packed_ref_cache(struct ref_cache *refs) -{ - if (refs->packed) { - struct packed_ref_cache *packed_refs = refs->packed; - - if (packed_refs->lock) - die("internal error: packed-ref cache cleared while locked"); - refs->packed = NULL; - release_packed_ref_cache(packed_refs); - } -} - -static void clear_loose_ref_cache(struct ref_cache *refs) -{ - if (refs->loose) { - free_ref_entry(refs->loose); - refs->loose = NULL; - } -} - -static struct ref_cache *create_ref_cache(const char *submodule) -{ - int len; - struct ref_cache *refs; - if (!submodule) - submodule = ""; - len = strlen(submodule) + 1; - refs = xcalloc(1, sizeof(struct ref_cache) + len); - memcpy(refs->name, submodule, len); - return refs; -} - -/* - * Return a pointer to a ref_cache for the specified submodule. For - * the main repository, use submodule==NULL. The returned structure - * will be allocated and initialized but not necessarily populated; it - * should not be freed. - */ -static struct ref_cache *get_ref_cache(const char *submodule) -{ - struct ref_cache *refs; - - if (!submodule || !*submodule) - return &ref_cache; - - for (refs = submodule_ref_caches; refs; refs = refs->next) - if (!strcmp(submodule, refs->name)) - return refs; - - refs = create_ref_cache(submodule); - refs->next = submodule_ref_caches; - submodule_ref_caches = refs; - return refs; -} - -/* The length of a peeled reference line in packed-refs, including EOL: */ -#define PEELED_LINE_LENGTH 42 - -/* - * The packed-refs header line that we write out. Perhaps other - * traits will be added later. The trailing space is required. - */ -static const char PACKED_REFS_HEADER[] = - "# pack-refs with: peeled fully-peeled \n"; - -/* - * Parse one line from a packed-refs file. Write the SHA1 to sha1. - * Return a pointer to the refname within the line (null-terminated), - * or NULL if there was a problem. - */ -static const char *parse_ref_line(struct strbuf *line, unsigned char *sha1) -{ - const char *ref; - - /* - * 42: the answer to everything. - * - * In this case, it happens to be the answer to - * 40 (length of sha1 hex representation) - * +1 (space in between hex and name) - * +1 (newline at the end of the line) - */ - if (line->len <= 42) - return NULL; - - if (get_sha1_hex(line->buf, sha1) < 0) - return NULL; - if (!isspace(line->buf[40])) - return NULL; - - ref = line->buf + 41; - if (isspace(*ref)) - return NULL; - - if (line->buf[line->len - 1] != '\n') - return NULL; - line->buf[--line->len] = 0; - - return ref; -} - -/* - * Read f, which is a packed-refs file, into dir. - * - * A comment line of the form "# pack-refs with: " may contain zero or - * more traits. We interpret the traits as follows: - * - * No traits: - * - * Probably no references are peeled. But if the file contains a - * peeled value for a reference, we will use it. - * - * peeled: - * - * References under "refs/tags/", if they *can* be peeled, *are* - * peeled in this file. References outside of "refs/tags/" are - * probably not peeled even if they could have been, but if we find - * a peeled value for such a reference we will use it. - * - * fully-peeled: - * - * All references in the file that can be peeled are peeled. - * Inversely (and this is more important), any references in the - * file for which no peeled value is recorded is not peelable. This - * trait should typically be written alongside "peeled" for - * compatibility with older clients, but we do not require it - * (i.e., "peeled" is a no-op if "fully-peeled" is set). - */ -static void read_packed_refs(FILE *f, struct ref_dir *dir) -{ - struct ref_entry *last = NULL; - struct strbuf line = STRBUF_INIT; - enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE; - - while (strbuf_getwholeline(&line, f, '\n') != EOF) { - unsigned char sha1[20]; - const char *refname; - const char *traits; - - if (skip_prefix(line.buf, "# pack-refs with:", &traits)) { - if (strstr(traits, " fully-peeled ")) - peeled = PEELED_FULLY; - else if (strstr(traits, " peeled ")) - peeled = PEELED_TAGS; - /* perhaps other traits later as well */ - continue; - } - - refname = parse_ref_line(&line, sha1); - if (refname) { - int flag = REF_ISPACKED; - - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - if (!refname_is_safe(refname)) - die("packed refname is dangerous: %s", refname); - hashclr(sha1); - flag |= REF_BAD_NAME | REF_ISBROKEN; - } - last = create_ref_entry(refname, sha1, flag, 0); - if (peeled == PEELED_FULLY || - (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/"))) - last->flag |= REF_KNOWS_PEELED; - add_ref(dir, last); - continue; - } - if (last && - line.buf[0] == '^' && - line.len == PEELED_LINE_LENGTH && - line.buf[PEELED_LINE_LENGTH - 1] == '\n' && - !get_sha1_hex(line.buf + 1, sha1)) { - hashcpy(last->u.value.peeled.hash, sha1); - /* - * Regardless of what the file header said, - * we definitely know the value of *this* - * reference: - */ - last->flag |= REF_KNOWS_PEELED; - } - } - - strbuf_release(&line); -} - -/* - * Get the packed_ref_cache for the specified ref_cache, creating it - * if necessary. - */ -static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) -{ - char *packed_refs_file; - - if (*refs->name) - packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs"); - else - packed_refs_file = git_pathdup("packed-refs"); - - if (refs->packed && - !stat_validity_check(&refs->packed->validity, packed_refs_file)) - clear_packed_ref_cache(refs); - - if (!refs->packed) { - FILE *f; - - refs->packed = xcalloc(1, sizeof(*refs->packed)); - acquire_packed_ref_cache(refs->packed); - refs->packed->root = create_dir_entry(refs, "", 0, 0); - f = fopen(packed_refs_file, "r"); - if (f) { - stat_validity_update(&refs->packed->validity, fileno(f)); - read_packed_refs(f, get_ref_dir(refs->packed->root)); - fclose(f); - } - } - free(packed_refs_file); - return refs->packed; -} - -static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache) -{ - return get_ref_dir(packed_ref_cache->root); -} - -static struct ref_dir *get_packed_refs(struct ref_cache *refs) -{ - return get_packed_ref_dir(get_packed_ref_cache(refs)); -} - -/* - * Add a reference to the in-memory packed reference cache. This may - * only be called while the packed-refs file is locked (see - * lock_packed_refs()). To actually write the packed-refs file, call - * commit_packed_refs(). - */ -static void add_packed_ref(const char *refname, const unsigned char *sha1) -{ - struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); - - if (!packed_ref_cache->lock) - die("internal error: packed refs not locked"); - add_ref(get_packed_ref_dir(packed_ref_cache), - create_ref_entry(refname, sha1, REF_ISPACKED, 1)); -} - -/* - * Read the loose references from the namespace dirname into dir - * (without recursing). dirname must end with '/'. dir must be the - * directory entry corresponding to dirname. - */ -static void read_loose_refs(const char *dirname, struct ref_dir *dir) -{ - struct ref_cache *refs = dir->ref_cache; - DIR *d; - struct dirent *de; - int dirnamelen = strlen(dirname); - struct strbuf refname; - struct strbuf path = STRBUF_INIT; - size_t path_baselen; - - if (*refs->name) - strbuf_git_path_submodule(&path, refs->name, "%s", dirname); - else - strbuf_git_path(&path, "%s", dirname); - path_baselen = path.len; - - d = opendir(path.buf); - if (!d) { - strbuf_release(&path); - return; - } - - strbuf_init(&refname, dirnamelen + 257); - strbuf_add(&refname, dirname, dirnamelen); - - while ((de = readdir(d)) != NULL) { - unsigned char sha1[20]; - struct stat st; - int flag; - - if (de->d_name[0] == '.') - continue; - if (ends_with(de->d_name, ".lock")) - continue; - strbuf_addstr(&refname, de->d_name); - strbuf_addstr(&path, de->d_name); - if (stat(path.buf, &st) < 0) { - ; /* silently ignore */ - } else if (S_ISDIR(st.st_mode)) { - strbuf_addch(&refname, '/'); - add_entry_to_dir(dir, - create_dir_entry(refs, refname.buf, - refname.len, 1)); - } else { - int read_ok; - - if (*refs->name) { - hashclr(sha1); - flag = 0; - read_ok = !resolve_gitlink_ref(refs->name, - refname.buf, sha1); - } else { - read_ok = !read_ref_full(refname.buf, - RESOLVE_REF_READING, - sha1, &flag); - } - - if (!read_ok) { - hashclr(sha1); - flag |= REF_ISBROKEN; - } else if (is_null_sha1(sha1)) { - /* - * It is so astronomically unlikely - * that NULL_SHA1 is the SHA-1 of an - * actual object that we consider its - * appearance in a loose reference - * file to be repo corruption - * (probably due to a software bug). - */ - flag |= REF_ISBROKEN; - } - - if (check_refname_format(refname.buf, - REFNAME_ALLOW_ONELEVEL)) { - if (!refname_is_safe(refname.buf)) - die("loose refname is dangerous: %s", refname.buf); - hashclr(sha1); - flag |= REF_BAD_NAME | REF_ISBROKEN; - } - add_entry_to_dir(dir, - create_ref_entry(refname.buf, sha1, flag, 0)); - } - strbuf_setlen(&refname, dirnamelen); - strbuf_setlen(&path, path_baselen); - } - strbuf_release(&refname); - strbuf_release(&path); - closedir(d); -} - -static struct ref_dir *get_loose_refs(struct ref_cache *refs) -{ - if (!refs->loose) { - /* - * Mark the top-level directory complete because we - * are about to read the only subdirectory that can - * hold references: - */ - refs->loose = create_dir_entry(refs, "", 0, 0); - /* - * Create an incomplete entry for "refs/": - */ - add_entry_to_dir(get_ref_dir(refs->loose), - create_dir_entry(refs, "refs/", 5, 1)); - } - return get_ref_dir(refs->loose); -} - -/* We allow "recursive" symbolic refs. Only within reason, though */ -#define MAXDEPTH 5 -#define MAXREFLEN (1024) - -/* - * Called by resolve_gitlink_ref_recursive() after it failed to read - * from the loose refs in ref_cache refs. Find <refname> in the - * packed-refs file for the submodule. - */ -static int resolve_gitlink_packed_ref(struct ref_cache *refs, - const char *refname, unsigned char *sha1) -{ - struct ref_entry *ref; - struct ref_dir *dir = get_packed_refs(refs); - - ref = find_ref(dir, refname); - if (ref == NULL) - return -1; - - hashcpy(sha1, ref->u.value.oid.hash); - return 0; -} - -static int resolve_gitlink_ref_recursive(struct ref_cache *refs, - const char *refname, unsigned char *sha1, - int recursion) -{ - int fd, len; - char buffer[128], *p; - char *path; - - if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) - return -1; - path = *refs->name - ? git_pathdup_submodule(refs->name, "%s", refname) - : git_pathdup("%s", refname); - fd = open(path, O_RDONLY); - free(path); - if (fd < 0) - return resolve_gitlink_packed_ref(refs, refname, sha1); - - len = read(fd, buffer, sizeof(buffer)-1); - close(fd); - if (len < 0) - return -1; - while (len && isspace(buffer[len-1])) - len--; - buffer[len] = 0; - - /* Was it a detached head or an old-fashioned symlink? */ - if (!get_sha1_hex(buffer, sha1)) - return 0; - - /* Symref? */ - if (strncmp(buffer, "ref:", 4)) - return -1; - p = buffer + 4; - while (isspace(*p)) - p++; - - return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1); -} - -int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1) -{ - int len = strlen(path), retval; - char *submodule; - struct ref_cache *refs; - - while (len && path[len-1] == '/') - len--; - if (!len) - return -1; - submodule = xstrndup(path, len); - refs = get_ref_cache(submodule); - free(submodule); - - retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0); - return retval; -} - -/* - * Return the ref_entry for the given refname from the packed - * references. If it does not exist, return NULL. - */ -static struct ref_entry *get_packed_ref(const char *refname) -{ - return find_ref(get_packed_refs(&ref_cache), refname); -} - -/* - * A loose ref file doesn't exist; check for a packed ref. The - * options are forwarded from resolve_safe_unsafe(). - */ -static int resolve_missing_loose_ref(const char *refname, - int resolve_flags, - unsigned char *sha1, - int *flags) -{ - struct ref_entry *entry; - - /* - * The loose reference file does not exist; check for a packed - * reference. - */ - entry = get_packed_ref(refname); - if (entry) { - hashcpy(sha1, entry->u.value.oid.hash); - if (flags) - *flags |= REF_ISPACKED; - return 0; - } - /* The reference is not a packed reference, either. */ - if (resolve_flags & RESOLVE_REF_READING) { - errno = ENOENT; - return -1; - } else { - hashclr(sha1); - return 0; - } -} - -/* This function needs to return a meaningful errno on failure */ -static const char *resolve_ref_1(const char *refname, - int resolve_flags, - unsigned char *sha1, - int *flags, - struct strbuf *sb_refname, - struct strbuf *sb_path, - struct strbuf *sb_contents) -{ - int depth = MAXDEPTH; - int bad_name = 0; - - if (flags) - *flags = 0; - - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - if (flags) - *flags |= REF_BAD_NAME; - - if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - errno = EINVAL; - return NULL; - } - /* - * dwim_ref() uses REF_ISBROKEN to distinguish between - * missing refs and refs that were present but invalid, - * to complain about the latter to stderr. - * - * We don't know whether the ref exists, so don't set - * REF_ISBROKEN yet. - */ - bad_name = 1; - } - for (;;) { - const char *path; - struct stat st; - char *buf; - int fd; - - if (--depth < 0) { - errno = ELOOP; - return NULL; - } - - strbuf_reset(sb_path); - strbuf_git_path(sb_path, "%s", refname); - path = sb_path->buf; - - /* - * We might have to loop back here to avoid a race - * condition: first we lstat() the file, then we try - * to read it as a link or as a file. But if somebody - * changes the type of the file (file <-> directory - * <-> symlink) between the lstat() and reading, then - * we don't want to report that as an error but rather - * try again starting with the lstat(). - */ - stat_ref: - if (lstat(path, &st) < 0) { - if (errno != ENOENT) - return NULL; - if (resolve_missing_loose_ref(refname, resolve_flags, - sha1, flags)) - return NULL; - if (bad_name) { - hashclr(sha1); - if (flags) - *flags |= REF_ISBROKEN; - } - return refname; - } - - /* Follow "normalized" - ie "refs/.." symlinks by hand */ - if (S_ISLNK(st.st_mode)) { - strbuf_reset(sb_contents); - if (strbuf_readlink(sb_contents, path, 0) < 0) { - if (errno == ENOENT || errno == EINVAL) - /* inconsistent with lstat; retry */ - goto stat_ref; - else - return NULL; - } - if (starts_with(sb_contents->buf, "refs/") && - !check_refname_format(sb_contents->buf, 0)) { - strbuf_swap(sb_refname, sb_contents); - refname = sb_refname->buf; - if (flags) - *flags |= REF_ISSYMREF; - if (resolve_flags & RESOLVE_REF_NO_RECURSE) { - hashclr(sha1); - return refname; - } - continue; - } - } - - /* Is it a directory? */ - if (S_ISDIR(st.st_mode)) { - errno = EISDIR; - return NULL; - } - - /* - * Anything else, just open it and try to use it as - * a ref - */ - fd = open(path, O_RDONLY); - if (fd < 0) { - if (errno == ENOENT) - /* inconsistent with lstat; retry */ - goto stat_ref; - else - return NULL; - } - strbuf_reset(sb_contents); - if (strbuf_read(sb_contents, fd, 256) < 0) { - int save_errno = errno; - close(fd); - errno = save_errno; - return NULL; - } - close(fd); - strbuf_rtrim(sb_contents); - - /* - * Is it a symbolic ref? - */ - if (!starts_with(sb_contents->buf, "ref:")) { - /* - * Please note that FETCH_HEAD has a second - * line containing other data. - */ - if (get_sha1_hex(sb_contents->buf, sha1) || - (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) { - if (flags) - *flags |= REF_ISBROKEN; - errno = EINVAL; - return NULL; - } - if (bad_name) { - hashclr(sha1); - if (flags) - *flags |= REF_ISBROKEN; - } - return refname; - } - if (flags) - *flags |= REF_ISSYMREF; - buf = sb_contents->buf + 4; - while (isspace(*buf)) - buf++; - strbuf_reset(sb_refname); - strbuf_addstr(sb_refname, buf); - refname = sb_refname->buf; - if (resolve_flags & RESOLVE_REF_NO_RECURSE) { - hashclr(sha1); - return refname; - } - if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { - if (flags) - *flags |= REF_ISBROKEN; - - if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(buf)) { - errno = EINVAL; - return NULL; - } - bad_name = 1; - } - } -} - -const char *resolve_ref_unsafe(const char *refname, int resolve_flags, - unsigned char *sha1, int *flags) -{ - static struct strbuf sb_refname = STRBUF_INIT; - struct strbuf sb_contents = STRBUF_INIT; - struct strbuf sb_path = STRBUF_INIT; - const char *ret; - - ret = resolve_ref_1(refname, resolve_flags, sha1, flags, - &sb_refname, &sb_path, &sb_contents); - strbuf_release(&sb_path); - strbuf_release(&sb_contents); - return ret; -} - char *resolve_refdup(const char *refname, int resolve_flags, unsigned char *sha1, int *flags) { @@ -1831,39 +184,7 @@ static int filter_refs(const char *refname, const struct object_id *oid, return filter->fn(refname, oid, flags, filter->cb_data); } -enum peel_status { - /* object was peeled successfully: */ - PEEL_PEELED = 0, - - /* - * object cannot be peeled because the named object (or an - * object referred to by a tag in the peel chain), does not - * exist. - */ - PEEL_INVALID = -1, - - /* object cannot be peeled because it is not a tag: */ - PEEL_NON_TAG = -2, - - /* ref_entry contains no peeled value because it is a symref: */ - PEEL_IS_SYMREF = -3, - - /* - * ref_entry cannot be peeled because it is broken (i.e., the - * symbolic reference cannot even be resolved to an object - * name): - */ - PEEL_BROKEN = -4 -}; - -/* - * Peel the named object; i.e., if the object is a tag, resolve the - * tag recursively until a non-tag is found. If successful, store the - * result to sha1 and return PEEL_PEELED. If the object is not a tag - * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively, - * and leave sha1 unchanged. - */ -static enum peel_status peel_object(const unsigned char *name, unsigned char *sha1) +enum peel_status peel_object(const unsigned char *name, unsigned char *sha1) { struct object *o = lookup_unknown_object(name); @@ -1884,78 +205,6 @@ static enum peel_status peel_object(const unsigned char *name, unsigned char *sh return PEEL_PEELED; } -/* - * Peel the entry (if possible) and return its new peel_status. If - * repeel is true, re-peel the entry even if there is an old peeled - * value that is already stored in it. - * - * It is OK to call this function with a packed reference entry that - * might be stale and might even refer to an object that has since - * been garbage-collected. In such a case, if the entry has - * REF_KNOWS_PEELED then leave the status unchanged and return - * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID. - */ -static enum peel_status peel_entry(struct ref_entry *entry, int repeel) -{ - enum peel_status status; - - if (entry->flag & REF_KNOWS_PEELED) { - if (repeel) { - entry->flag &= ~REF_KNOWS_PEELED; - oidclr(&entry->u.value.peeled); - } else { - return is_null_oid(&entry->u.value.peeled) ? - PEEL_NON_TAG : PEEL_PEELED; - } - } - if (entry->flag & REF_ISBROKEN) - return PEEL_BROKEN; - if (entry->flag & REF_ISSYMREF) - return PEEL_IS_SYMREF; - - status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash); - if (status == PEEL_PEELED || status == PEEL_NON_TAG) - entry->flag |= REF_KNOWS_PEELED; - return status; -} - -int peel_ref(const char *refname, unsigned char *sha1) -{ - int flag; - unsigned char base[20]; - - if (current_ref && (current_ref->name == refname - || !strcmp(current_ref->name, refname))) { - if (peel_entry(current_ref, 0)) - return -1; - hashcpy(sha1, current_ref->u.value.peeled.hash); - return 0; - } - - if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag)) - return -1; - - /* - * If the reference is packed, read its ref_entry from the - * cache in the hope that we already know its peeled value. - * We only try this optimization on packed references because - * (a) forcing the filling of the loose reference cache could - * be expensive and (b) loose references anyway usually do not - * have REF_KNOWS_PEELED. - */ - if (flag & REF_ISPACKED) { - struct ref_entry *r = get_packed_ref(refname); - if (r) { - if (peel_entry(r, 0)) - return -1; - hashcpy(sha1, r->u.value.peeled.hash); - return 0; - } - } - - return peel_object(base, sha1); -} - struct warn_if_dangling_data { FILE *fp; const char *refname; @@ -2008,147 +257,6 @@ void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_li for_each_rawref(warn_if_dangling_symref, &data); } -/* - * Call fn for each reference in the specified ref_cache, omitting - * references not in the containing_dir of base. fn is called for all - * references, including broken ones. If fn ever returns a non-zero - * value, stop the iteration and return that value; otherwise, return - * 0. - */ -static int do_for_each_entry(struct ref_cache *refs, const char *base, - each_ref_entry_fn fn, void *cb_data) -{ - struct packed_ref_cache *packed_ref_cache; - struct ref_dir *loose_dir; - struct ref_dir *packed_dir; - int retval = 0; - - /* - * We must make sure that all loose refs are read before accessing the - * packed-refs file; this avoids a race condition in which loose refs - * are migrated to the packed-refs file by a simultaneous process, but - * our in-memory view is from before the migration. get_packed_ref_cache() - * takes care of making sure our view is up to date with what is on - * disk. - */ - loose_dir = get_loose_refs(refs); - if (base && *base) { - loose_dir = find_containing_dir(loose_dir, base, 0); - } - if (loose_dir) - prime_ref_dir(loose_dir); - - packed_ref_cache = get_packed_ref_cache(refs); - acquire_packed_ref_cache(packed_ref_cache); - packed_dir = get_packed_ref_dir(packed_ref_cache); - if (base && *base) { - packed_dir = find_containing_dir(packed_dir, base, 0); - } - - if (packed_dir && loose_dir) { - sort_ref_dir(packed_dir); - sort_ref_dir(loose_dir); - retval = do_for_each_entry_in_dirs( - packed_dir, loose_dir, fn, cb_data); - } else if (packed_dir) { - sort_ref_dir(packed_dir); - retval = do_for_each_entry_in_dir( - packed_dir, 0, fn, cb_data); - } else if (loose_dir) { - sort_ref_dir(loose_dir); - retval = do_for_each_entry_in_dir( - loose_dir, 0, fn, cb_data); - } - - release_packed_ref_cache(packed_ref_cache); - return retval; -} - -/* - * Call fn for each reference in the specified ref_cache for which the - * refname begins with base. If trim is non-zero, then trim that many - * characters off the beginning of each refname before passing the - * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include - * broken references in the iteration. If fn ever returns a non-zero - * value, stop the iteration and return that value; otherwise, return - * 0. - */ -static int do_for_each_ref(struct ref_cache *refs, const char *base, - each_ref_fn fn, int trim, int flags, void *cb_data) -{ - struct ref_entry_cb data; - data.base = base; - data.trim = trim; - data.flags = flags; - data.fn = fn; - data.cb_data = cb_data; - - if (ref_paranoia < 0) - ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0); - if (ref_paranoia) - data.flags |= DO_FOR_EACH_INCLUDE_BROKEN; - - return do_for_each_entry(refs, base, do_one_ref, &data); -} - -static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) -{ - struct object_id oid; - int flag; - - if (submodule) { - if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0) - return fn("HEAD", &oid, 0, cb_data); - - return 0; - } - - if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag)) - return fn("HEAD", &oid, flag, cb_data); - - return 0; -} - -int head_ref(each_ref_fn fn, void *cb_data) -{ - return do_head_ref(NULL, fn, cb_data); -} - -int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return do_head_ref(submodule, fn, cb_data); -} - -int for_each_ref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data); -} - -int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data); -} - -int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); -} - -int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) -{ - unsigned int flag = 0; - - if (broken) - flag = DO_FOR_EACH_INCLUDE_BROKEN; - return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data); -} - -int for_each_ref_in_submodule(const char *submodule, const char *prefix, - each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data); -} - int for_each_tag_ref(each_ref_fn fn, void *cb_data) { return for_each_ref_in("refs/tags/", fn, cb_data); @@ -2179,12 +287,6 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c return for_each_ref_in_submodule(submodule, "refs/remotes/", fn, cb_data); } -int for_each_replace_ref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, git_replace_ref_base, fn, - strlen(git_replace_ref_base), 0, cb_data); -} - int head_ref_namespaced(each_ref_fn fn, void *cb_data) { struct strbuf buf = STRBUF_INIT; @@ -2200,16 +302,6 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data) return ret; } -int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) -{ - struct strbuf buf = STRBUF_INIT; - int ret; - strbuf_addf(&buf, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data); - strbuf_release(&buf); - return ret; -} - int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data) { @@ -2244,12 +336,6 @@ int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data) return for_each_glob_ref_in(fn, pattern, NULL, cb_data); } -int for_each_rawref(each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(&ref_cache, "", fn, 0, - DO_FOR_EACH_INCLUDE_BROKEN, cb_data); -} - const char *prettify_refname(const char *name) { return name + ( @@ -2283,57 +369,6 @@ int refname_match(const char *abbrev_name, const char *full_name) return 0; } -static void unlock_ref(struct ref_lock *lock) -{ - /* Do not free lock->lk -- atexit() still looks at them */ - if (lock->lk) - rollback_lock_file(lock->lk); - free(lock->ref_name); - free(lock->orig_ref_name); - free(lock); -} - -/* - * Verify that the reference locked by lock has the value old_sha1. - * Fail if the reference doesn't exist and mustexist is set. Return 0 - * on success. On error, write an error message to err, set errno, and - * return a negative value. - */ -static int verify_lock(struct ref_lock *lock, - const unsigned char *old_sha1, int mustexist, - struct strbuf *err) -{ - assert(err); - - if (read_ref_full(lock->ref_name, - mustexist ? RESOLVE_REF_READING : 0, - lock->old_oid.hash, NULL)) { - int save_errno = errno; - strbuf_addf(err, "can't verify ref %s", lock->ref_name); - errno = save_errno; - return -1; - } - if (hashcmp(lock->old_oid.hash, old_sha1)) { - strbuf_addf(err, "ref %s is at %s but expected %s", - lock->ref_name, - sha1_to_hex(lock->old_oid.hash), - sha1_to_hex(old_sha1)); - errno = EBUSY; - return -1; - } - return 0; -} - -static int remove_empty_directories(struct strbuf *path) -{ - /* - * we want to create a file but there is a directory there; - * if that is an empty directory (or a directory that contains - * only empty directories), remove them. - */ - return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); -} - /* * *string and *len will only be substituted, and *string returned (for * later free()ing) if the string passed in is a magic short-hand form @@ -2420,480 +455,6 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) return logs_found; } -/* - * Locks a ref returning the lock on success and NULL on failure. - * On failure errno is set to something meaningful. - */ -static struct ref_lock *lock_ref_sha1_basic(const char *refname, - const unsigned char *old_sha1, - const struct string_list *extras, - const struct string_list *skip, - unsigned int flags, int *type_p, - struct strbuf *err) -{ - struct strbuf ref_file = STRBUF_INIT; - struct strbuf orig_ref_file = STRBUF_INIT; - const char *orig_refname = refname; - struct ref_lock *lock; - int last_errno = 0; - int type, lflags; - int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); - int resolve_flags = 0; - int attempts_remaining = 3; - - assert(err); - - lock = xcalloc(1, sizeof(struct ref_lock)); - - if (mustexist) - resolve_flags |= RESOLVE_REF_READING; - if (flags & REF_DELETING) { - resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; - if (flags & REF_NODEREF) - resolve_flags |= RESOLVE_REF_NO_RECURSE; - } - - refname = resolve_ref_unsafe(refname, resolve_flags, - lock->old_oid.hash, &type); - if (!refname && errno == EISDIR) { - /* - * we are trying to lock foo but we used to - * have foo/bar which now does not exist; - * it is normal for the empty directory 'foo' - * to remain. - */ - strbuf_git_path(&orig_ref_file, "%s", orig_refname); - if (remove_empty_directories(&orig_ref_file)) { - last_errno = errno; - if (!verify_refname_available(orig_refname, extras, skip, - get_loose_refs(&ref_cache), err)) - strbuf_addf(err, "there are still refs under '%s'", - orig_refname); - goto error_return; - } - refname = resolve_ref_unsafe(orig_refname, resolve_flags, - lock->old_oid.hash, &type); - } - if (type_p) - *type_p = type; - if (!refname) { - last_errno = errno; - if (last_errno != ENOTDIR || - !verify_refname_available(orig_refname, extras, skip, - get_loose_refs(&ref_cache), err)) - strbuf_addf(err, "unable to resolve reference %s: %s", - orig_refname, strerror(last_errno)); - - goto error_return; - } - /* - * If the ref did not exist and we are creating it, make sure - * there is no existing packed ref whose name begins with our - * refname, nor a packed ref whose name is a proper prefix of - * our refname. - */ - if (is_null_oid(&lock->old_oid) && - verify_refname_available(refname, extras, skip, - get_packed_refs(&ref_cache), err)) { - last_errno = ENOTDIR; - goto error_return; - } - - lock->lk = xcalloc(1, sizeof(struct lock_file)); - - lflags = 0; - if (flags & REF_NODEREF) { - refname = orig_refname; - lflags |= LOCK_NO_DEREF; - } - lock->ref_name = xstrdup(refname); - lock->orig_ref_name = xstrdup(orig_refname); - strbuf_git_path(&ref_file, "%s", refname); - - retry: - switch (safe_create_leading_directories_const(ref_file.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: - last_errno = errno; - strbuf_addf(err, "unable to create directory for %s", - ref_file.buf); - goto error_return; - } - - if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { - last_errno = errno; - if (errno == ENOENT && --attempts_remaining > 0) - /* - * Maybe somebody just deleted one of the - * directories leading to ref_file. Try - * again: - */ - goto retry; - else { - unable_to_lock_message(ref_file.buf, errno, err); - goto error_return; - } - } - if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) { - last_errno = errno; - goto error_return; - } - goto out; - - error_return: - unlock_ref(lock); - lock = NULL; - - out: - strbuf_release(&ref_file); - strbuf_release(&orig_ref_file); - errno = last_errno; - return lock; -} - -/* - * Write an entry to the packed-refs file for the specified refname. - * If peeled is non-NULL, write it as the entry's peeled value. - */ -static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1, - unsigned char *peeled) -{ - fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname); - if (peeled) - fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled)); -} - -/* - * An each_ref_entry_fn that writes the entry to a packed-refs file. - */ -static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) -{ - enum peel_status peel_status = peel_entry(entry, 0); - - if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG) - error("internal error: %s is not a valid packed reference!", - entry->name); - write_packed_entry(cb_data, entry->name, entry->u.value.oid.hash, - peel_status == PEEL_PEELED ? - entry->u.value.peeled.hash : NULL); - return 0; -} - -/* - * Lock the packed-refs file for writing. Flags is passed to - * hold_lock_file_for_update(). Return 0 on success. On errors, set - * errno appropriately and return a nonzero value. - */ -static int lock_packed_refs(int flags) -{ - static int timeout_configured = 0; - static int timeout_value = 1000; - - struct packed_ref_cache *packed_ref_cache; - - if (!timeout_configured) { - git_config_get_int("core.packedrefstimeout", &timeout_value); - timeout_configured = 1; - } - - if (hold_lock_file_for_update_timeout( - &packlock, git_path("packed-refs"), - flags, timeout_value) < 0) - return -1; - /* - * Get the current packed-refs while holding the lock. If the - * packed-refs file has been modified since we last read it, - * this will automatically invalidate the cache and re-read - * the packed-refs file. - */ - packed_ref_cache = get_packed_ref_cache(&ref_cache); - packed_ref_cache->lock = &packlock; - /* Increment the reference count to prevent it from being freed: */ - acquire_packed_ref_cache(packed_ref_cache); - return 0; -} - -/* - * Write the current version of the packed refs cache from memory to - * disk. The packed-refs file must already be locked for writing (see - * lock_packed_refs()). Return zero on success. On errors, set errno - * and return a nonzero value - */ -static int commit_packed_refs(void) -{ - struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); - int error = 0; - int save_errno = 0; - FILE *out; - - if (!packed_ref_cache->lock) - die("internal error: packed-refs not locked"); - - out = fdopen_lock_file(packed_ref_cache->lock, "w"); - if (!out) - die_errno("unable to fdopen packed-refs descriptor"); - - fprintf_or_die(out, "%s", PACKED_REFS_HEADER); - do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache), - 0, write_packed_entry_fn, out); - - if (commit_lock_file(packed_ref_cache->lock)) { - save_errno = errno; - error = -1; - } - packed_ref_cache->lock = NULL; - release_packed_ref_cache(packed_ref_cache); - errno = save_errno; - return error; -} - -/* - * Rollback the lockfile for the packed-refs file, and discard the - * in-memory packed reference cache. (The packed-refs file will be - * read anew if it is needed again after this function is called.) - */ -static void rollback_packed_refs(void) -{ - struct packed_ref_cache *packed_ref_cache = - get_packed_ref_cache(&ref_cache); - - if (!packed_ref_cache->lock) - die("internal error: packed-refs not locked"); - rollback_lock_file(packed_ref_cache->lock); - packed_ref_cache->lock = NULL; - release_packed_ref_cache(packed_ref_cache); - clear_packed_ref_cache(&ref_cache); -} - -struct ref_to_prune { - struct ref_to_prune *next; - unsigned char sha1[20]; - char name[FLEX_ARRAY]; -}; - -struct pack_refs_cb_data { - unsigned int flags; - struct ref_dir *packed_refs; - struct ref_to_prune *ref_to_prune; -}; - -static int is_per_worktree_ref(const char *refname); - -/* - * An each_ref_entry_fn that is run over loose references only. If - * the loose reference can be packed, add an entry in the packed ref - * cache. If the reference should be pruned, also add it to - * ref_to_prune in the pack_refs_cb_data. - */ -static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data) -{ - struct pack_refs_cb_data *cb = cb_data; - enum peel_status peel_status; - struct ref_entry *packed_entry; - int is_tag_ref = starts_with(entry->name, "refs/tags/"); - - /* Do not pack per-worktree refs: */ - if (is_per_worktree_ref(entry->name)) - return 0; - - /* ALWAYS pack tags */ - if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref) - return 0; - - /* Do not pack symbolic or broken refs: */ - if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry)) - return 0; - - /* Add a packed ref cache entry equivalent to the loose entry. */ - peel_status = peel_entry(entry, 1); - if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG) - die("internal error peeling reference %s (%s)", - entry->name, oid_to_hex(&entry->u.value.oid)); - packed_entry = find_ref(cb->packed_refs, entry->name); - if (packed_entry) { - /* Overwrite existing packed entry with info from loose entry */ - packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED; - oidcpy(&packed_entry->u.value.oid, &entry->u.value.oid); - } else { - packed_entry = create_ref_entry(entry->name, entry->u.value.oid.hash, - REF_ISPACKED | REF_KNOWS_PEELED, 0); - add_ref(cb->packed_refs, packed_entry); - } - oidcpy(&packed_entry->u.value.peeled, &entry->u.value.peeled); - - /* Schedule the loose reference for pruning if requested. */ - if ((cb->flags & PACK_REFS_PRUNE)) { - int namelen = strlen(entry->name) + 1; - struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); - hashcpy(n->sha1, entry->u.value.oid.hash); - memcpy(n->name, entry->name, namelen); /* includes NUL */ - n->next = cb->ref_to_prune; - cb->ref_to_prune = n; - } - return 0; -} - -/* - * Remove empty parents, but spare refs/ and immediate subdirs. - * Note: munges *name. - */ -static void try_remove_empty_parents(char *name) -{ - char *p, *q; - int i; - p = name; - for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ - while (*p && *p != '/') - p++; - /* tolerate duplicate slashes; see check_refname_format() */ - while (*p == '/') - p++; - } - for (q = p; *q; q++) - ; - while (1) { - while (q > p && *q != '/') - q--; - while (q > p && *(q-1) == '/') - q--; - if (q == p) - break; - *q = '\0'; - if (rmdir(git_path("%s", name))) - break; - } -} - -/* make sure nobody touched the ref, and unlink */ -static void prune_ref(struct ref_to_prune *r) -{ - struct ref_transaction *transaction; - struct strbuf err = STRBUF_INIT; - - if (check_refname_format(r->name, 0)) - return; - - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_delete(transaction, r->name, r->sha1, - REF_ISPRUNING, NULL, &err) || - ref_transaction_commit(transaction, &err)) { - ref_transaction_free(transaction); - error("%s", err.buf); - strbuf_release(&err); - return; - } - ref_transaction_free(transaction); - strbuf_release(&err); - try_remove_empty_parents(r->name); -} - -static void prune_refs(struct ref_to_prune *r) -{ - while (r) { - prune_ref(r); - r = r->next; - } -} - -int pack_refs(unsigned int flags) -{ - struct pack_refs_cb_data cbdata; - - memset(&cbdata, 0, sizeof(cbdata)); - cbdata.flags = flags; - - lock_packed_refs(LOCK_DIE_ON_ERROR); - cbdata.packed_refs = get_packed_refs(&ref_cache); - - do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0, - pack_if_possible_fn, &cbdata); - - if (commit_packed_refs()) - die_errno("unable to overwrite old ref-pack file"); - - prune_refs(cbdata.ref_to_prune); - return 0; -} - -/* - * Rewrite the packed-refs file, omitting any refs listed in - * 'refnames'. On error, leave packed-refs unchanged, write an error - * message to 'err', and return a nonzero value. - * - * The refs in 'refnames' needn't be sorted. `err` must not be NULL. - */ -static int repack_without_refs(struct string_list *refnames, struct strbuf *err) -{ - struct ref_dir *packed; - struct string_list_item *refname; - int ret, needs_repacking = 0, removed = 0; - - assert(err); - - /* Look for a packed ref */ - for_each_string_list_item(refname, refnames) { - if (get_packed_ref(refname->string)) { - needs_repacking = 1; - break; - } - } - - /* Avoid locking if we have nothing to do */ - if (!needs_repacking) - return 0; /* no refname exists in packed refs */ - - if (lock_packed_refs(0)) { - unable_to_lock_message(git_path("packed-refs"), errno, err); - return -1; - } - packed = get_packed_refs(&ref_cache); - - /* Remove refnames from the cache */ - for_each_string_list_item(refname, refnames) - if (remove_entry(packed, refname->string) != -1) - removed = 1; - if (!removed) { - /* - * All packed entries disappeared while we were - * acquiring the lock. - */ - rollback_packed_refs(); - return 0; - } - - /* Write what remains */ - ret = commit_packed_refs(); - if (ret) - strbuf_addf(err, "unable to overwrite old ref-pack file: %s", - strerror(errno)); - return ret; -} - -static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) -{ - assert(err); - - if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { - /* - * loose. The loose file name is the same as the - * lockfile name, minus ".lock": - */ - char *loose_filename = get_locked_file_path(lock->lk); - int res = unlink_or_msg(loose_filename, err); - free(loose_filename); - if (res) - return 1; - } - return 0; -} - static int is_per_worktree_ref(const char *refname) { return !strcmp(refname, "HEAD") || @@ -3021,254 +582,7 @@ int delete_ref(const char *refname, const unsigned char *old_sha1, return 0; } -int delete_refs(struct string_list *refnames) -{ - struct strbuf err = STRBUF_INIT; - int i, result = 0; - - if (!refnames->nr) - return 0; - - result = repack_without_refs(refnames, &err); - if (result) { - /* - * If we failed to rewrite the packed-refs file, then - * it is unsafe to try to remove loose refs, because - * doing so might expose an obsolete packed value for - * a reference that might even point at an object that - * has been garbage collected. - */ - if (refnames->nr == 1) - error(_("could not delete reference %s: %s"), - refnames->items[0].string, err.buf); - else - error(_("could not delete references: %s"), err.buf); - - goto out; - } - - for (i = 0; i < refnames->nr; i++) { - const char *refname = refnames->items[i].string; - - if (delete_ref(refname, NULL, 0)) - result |= error(_("could not remove reference %s"), refname); - } - -out: - strbuf_release(&err); - return result; -} - -/* - * People using contrib's git-new-workdir have .git/logs/refs -> - * /some/other/path/.git/logs/refs, and that may live on another device. - * - * IOW, to avoid cross device rename errors, the temporary renamed log must - * live into logs/refs. - */ -#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" - -static int rename_tmp_log(const char *newrefname) -{ - int attempts_remaining = 4; - struct strbuf path = STRBUF_INIT; - int ret = -1; - - retry: - strbuf_reset(&path); - strbuf_git_path(&path, "logs/%s", newrefname); - switch (safe_create_leading_directories_const(path.buf)) { - case SCLD_OK: - break; /* success */ - case SCLD_VANISHED: - if (--attempts_remaining > 0) - goto retry; - /* fall through */ - default: - error("unable to create directory for %s", newrefname); - goto out; - } - - if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { - if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { - /* - * rename(a, b) when b is an existing - * directory ought to result in ISDIR, but - * Solaris 5.8 gives ENOTDIR. Sheesh. - */ - if (remove_empty_directories(&path)) { - error("Directory not empty: logs/%s", newrefname); - goto out; - } - goto retry; - } else if (errno == ENOENT && --attempts_remaining > 0) { - /* - * Maybe another process just deleted one of - * the directories in the path to newrefname. - * Try again from the beginning. - */ - goto retry; - } else { - error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", - newrefname, strerror(errno)); - goto out; - } - } - ret = 0; -out: - strbuf_release(&path); - return ret; -} - -static int rename_ref_available(const char *oldname, const char *newname) -{ - struct string_list skip = STRING_LIST_INIT_NODUP; - struct strbuf err = STRBUF_INIT; - int ret; - - string_list_insert(&skip, oldname); - ret = !verify_refname_available(newname, NULL, &skip, - get_packed_refs(&ref_cache), &err) - && !verify_refname_available(newname, NULL, &skip, - get_loose_refs(&ref_cache), &err); - if (!ret) - error("%s", err.buf); - - string_list_clear(&skip, 0); - strbuf_release(&err); - return ret; -} - -static int write_ref_to_lockfile(struct ref_lock *lock, - const unsigned char *sha1, struct strbuf *err); -static int commit_ref_update(struct ref_lock *lock, - const unsigned char *sha1, const char *logmsg, - int flags, struct strbuf *err); - -int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) -{ - unsigned char sha1[20], orig_sha1[20]; - int flag = 0, logmoved = 0; - struct ref_lock *lock; - struct stat loginfo; - int log = !lstat(git_path("logs/%s", oldrefname), &loginfo); - const char *symref = NULL; - struct strbuf err = STRBUF_INIT; - - if (log && S_ISLNK(loginfo.st_mode)) - return error("reflog for %s is a symlink", oldrefname); - - symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, - orig_sha1, &flag); - if (flag & REF_ISSYMREF) - return error("refname %s is a symbolic ref, renaming it is not supported", - oldrefname); - if (!symref) - return error("refname %s not found", oldrefname); - - if (!rename_ref_available(oldrefname, newrefname)) - return 1; - - if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) - return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", - oldrefname, strerror(errno)); - - if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) { - error("unable to delete old %s", oldrefname); - goto rollback; - } - - if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && - delete_ref(newrefname, sha1, REF_NODEREF)) { - if (errno==EISDIR) { - struct strbuf path = STRBUF_INIT; - int result; - - strbuf_git_path(&path, "%s", newrefname); - result = remove_empty_directories(&path); - strbuf_release(&path); - - if (result) { - error("Directory not empty: %s", newrefname); - goto rollback; - } - } else { - error("unable to delete existing %s", newrefname); - goto rollback; - } - } - - if (log && rename_tmp_log(newrefname)) - goto rollback; - - logmoved = log; - - lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err); - if (!lock) { - error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); - strbuf_release(&err); - goto rollback; - } - hashcpy(lock->old_oid.hash, orig_sha1); - - if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) { - error("unable to write current sha1 into %s: %s", newrefname, err.buf); - strbuf_release(&err); - goto rollback; - } - - return 0; - - rollback: - lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err); - if (!lock) { - error("unable to lock %s for rollback: %s", oldrefname, err.buf); - strbuf_release(&err); - goto rollbacklog; - } - - flag = log_all_ref_updates; - log_all_ref_updates = 0; - if (write_ref_to_lockfile(lock, orig_sha1, &err) || - commit_ref_update(lock, orig_sha1, NULL, 0, &err)) { - error("unable to write current sha1 into %s: %s", oldrefname, err.buf); - strbuf_release(&err); - } - log_all_ref_updates = flag; - - rollbacklog: - if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname))) - error("unable to restore logfile %s from %s: %s", - oldrefname, newrefname, strerror(errno)); - if (!logmoved && log && - rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname))) - error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s", - oldrefname, strerror(errno)); - - return 1; -} - -static int close_ref(struct ref_lock *lock) -{ - if (close_lock_file(lock->lk)) - return -1; - return 0; -} - -static int commit_ref(struct ref_lock *lock) -{ - if (commit_lock_file(lock->lk)) - return -1; - return 0; -} - -/* - * copy the reflog message msg to buf, which has been allocated sufficiently - * large, while cleaning up the whitespaces. Especially, convert LF to space, - * because reflog file is one line per entry. - */ -static int copy_msg(char *buf, const char *msg) +int copy_reflog_msg(char *buf, const char *msg) { char *cp = buf; char c; @@ -3289,7 +603,7 @@ static int copy_msg(char *buf, const char *msg) return cp - buf; } -static int should_autocreate_reflog(const char *refname) +int should_autocreate_reflog(const char *refname) { if (!log_all_ref_updates) return 0; @@ -3299,305 +613,11 @@ static int should_autocreate_reflog(const char *refname) !strcmp(refname, "HEAD"); } -/* - * Create a reflog for a ref. If force_create = 0, the reflog will - * only be created for certain refs (those for which - * should_autocreate_reflog returns non-zero. Otherwise, create it - * regardless of the ref name. Fill in *err and return -1 on failure. - */ -static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) -{ - int logfd, oflags = O_APPEND | O_WRONLY; - - strbuf_git_path(logfile, "logs/%s", refname); - if (force_create || should_autocreate_reflog(refname)) { - if (safe_create_leading_directories(logfile->buf) < 0) { - strbuf_addf(err, "unable to create directory for %s: " - "%s", logfile->buf, strerror(errno)); - return -1; - } - oflags |= O_CREAT; - } - - logfd = open(logfile->buf, oflags, 0666); - if (logfd < 0) { - if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) - return 0; - - if (errno == EISDIR) { - if (remove_empty_directories(logfile)) { - strbuf_addf(err, "There are still logs under " - "'%s'", logfile->buf); - return -1; - } - logfd = open(logfile->buf, oflags, 0666); - } - - if (logfd < 0) { - strbuf_addf(err, "unable to append to %s: %s", - logfile->buf, strerror(errno)); - return -1; - } - } - - adjust_shared_perm(logfile->buf); - close(logfd); - return 0; -} - - -int safe_create_reflog(const char *refname, int force_create, struct strbuf *err) -{ - int ret; - struct strbuf sb = STRBUF_INIT; - - ret = log_ref_setup(refname, &sb, err, force_create); - strbuf_release(&sb); - return ret; -} - -static int log_ref_write_fd(int fd, const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *committer, const char *msg) -{ - int msglen, written; - unsigned maxlen, len; - char *logrec; - - msglen = msg ? strlen(msg) : 0; - maxlen = strlen(committer) + msglen + 100; - logrec = xmalloc(maxlen); - len = xsnprintf(logrec, maxlen, "%s %s %s\n", - sha1_to_hex(old_sha1), - sha1_to_hex(new_sha1), - committer); - if (msglen) - len += copy_msg(logrec + len - 1, msg) - 1; - - written = len <= maxlen ? write_in_full(fd, logrec, len) : -1; - free(logrec); - if (written != len) - return -1; - - return 0; -} - -static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - struct strbuf *logfile, int flags, - struct strbuf *err) -{ - int logfd, result, oflags = O_APPEND | O_WRONLY; - - if (log_all_ref_updates < 0) - log_all_ref_updates = !is_bare_repository(); - - result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); - - if (result) - return result; - - logfd = open(logfile->buf, oflags); - if (logfd < 0) - return 0; - result = log_ref_write_fd(logfd, old_sha1, new_sha1, - git_committer_info(0), msg); - if (result) { - strbuf_addf(err, "unable to append to %s: %s", logfile->buf, - strerror(errno)); - close(logfd); - return -1; - } - if (close(logfd)) { - strbuf_addf(err, "unable to append to %s: %s", logfile->buf, - strerror(errno)); - return -1; - } - return 0; -} - -static int log_ref_write(const char *refname, const unsigned char *old_sha1, - const unsigned char *new_sha1, const char *msg, - int flags, struct strbuf *err) -{ - struct strbuf sb = STRBUF_INIT; - int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags, - err); - strbuf_release(&sb); - return ret; -} - int is_branch(const char *refname) { return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/"); } -/* - * Write sha1 into the open lockfile, then close the lockfile. On - * errors, rollback the lockfile, fill in *err and - * return -1. - */ -static int write_ref_to_lockfile(struct ref_lock *lock, - const unsigned char *sha1, struct strbuf *err) -{ - static char term = '\n'; - struct object *o; - int fd; - - o = parse_object(sha1); - if (!o) { - strbuf_addf(err, - "Trying to write ref %s with nonexistent object %s", - lock->ref_name, sha1_to_hex(sha1)); - unlock_ref(lock); - return -1; - } - if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { - strbuf_addf(err, - "Trying to write non-commit object %s to branch %s", - sha1_to_hex(sha1), lock->ref_name); - unlock_ref(lock); - return -1; - } - fd = get_lock_file_fd(lock->lk); - if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 || - write_in_full(fd, &term, 1) != 1 || - close_ref(lock) < 0) { - strbuf_addf(err, - "Couldn't write %s", get_lock_file_path(lock->lk)); - unlock_ref(lock); - return -1; - } - return 0; -} - -/* - * Commit a change to a loose reference that has already been written - * to the loose reference lockfile. Also update the reflogs if - * necessary, using the specified lockmsg (which can be NULL). - */ -static int commit_ref_update(struct ref_lock *lock, - const unsigned char *sha1, const char *logmsg, - int flags, struct strbuf *err) -{ - clear_loose_ref_cache(&ref_cache); - if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 || - (strcmp(lock->ref_name, lock->orig_ref_name) && - log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) { - char *old_msg = strbuf_detach(err, NULL); - strbuf_addf(err, "Cannot update the ref '%s': %s", - lock->ref_name, old_msg); - free(old_msg); - unlock_ref(lock); - return -1; - } - if (strcmp(lock->orig_ref_name, "HEAD") != 0) { - /* - * Special hack: If a branch is updated directly and HEAD - * points to it (may happen on the remote side of a push - * for example) then logically the HEAD reflog should be - * updated too. - * A generic solution implies reverse symref information, - * but finding all symrefs pointing to the given branch - * would be rather costly for this rare event (the direct - * update of a branch) to be worth it. So let's cheat and - * check with HEAD only which should cover 99% of all usage - * scenarios (even 100% of the default ones). - */ - unsigned char head_sha1[20]; - int head_flag; - const char *head_ref; - head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, - head_sha1, &head_flag); - if (head_ref && (head_flag & REF_ISSYMREF) && - !strcmp(head_ref, lock->ref_name)) { - struct strbuf log_err = STRBUF_INIT; - if (log_ref_write("HEAD", lock->old_oid.hash, sha1, - logmsg, 0, &log_err)) { - error("%s", log_err.buf); - strbuf_release(&log_err); - } - } - } - if (commit_ref(lock)) { - error("Couldn't set %s", lock->ref_name); - unlock_ref(lock); - return -1; - } - - unlock_ref(lock); - return 0; -} - -int create_symref(const char *ref_target, const char *refs_heads_master, - const char *logmsg) -{ - char *lockpath = NULL; - char ref[1000]; - int fd, len, written; - char *git_HEAD = git_pathdup("%s", ref_target); - unsigned char old_sha1[20], new_sha1[20]; - struct strbuf err = STRBUF_INIT; - - if (logmsg && read_ref(ref_target, old_sha1)) - hashclr(old_sha1); - - if (safe_create_leading_directories(git_HEAD) < 0) - return error("unable to create directory for %s", git_HEAD); - -#ifndef NO_SYMLINK_HEAD - if (prefer_symlink_refs) { - unlink(git_HEAD); - if (!symlink(refs_heads_master, git_HEAD)) - goto done; - fprintf(stderr, "no symlink - falling back to symbolic ref\n"); - } -#endif - - len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); - if (sizeof(ref) <= len) { - error("refname too long: %s", refs_heads_master); - goto error_free_return; - } - lockpath = mkpathdup("%s.lock", git_HEAD); - fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); - if (fd < 0) { - error("Unable to open %s for writing", lockpath); - goto error_free_return; - } - written = write_in_full(fd, ref, len); - if (close(fd) != 0 || written != len) { - error("Unable to write to %s", lockpath); - goto error_unlink_return; - } - if (rename(lockpath, git_HEAD) < 0) { - error("Unable to create %s", git_HEAD); - goto error_unlink_return; - } - if (adjust_shared_perm(git_HEAD)) { - error("Unable to fix permissions on %s", lockpath); - error_unlink_return: - unlink_or_warn(lockpath); - error_free_return: - free(lockpath); - free(git_HEAD); - return -1; - } - free(lockpath); - -#ifndef NO_SYMLINK_HEAD - done: -#endif - if (logmsg && !read_ref(refs_heads_master, new_sha1) && - log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) { - error("%s", err.buf); - strbuf_release(&err); - } - - free(git_HEAD); - return 0; -} - struct read_ref_at_cb { const char *refname; unsigned long at_time; @@ -3716,287 +736,6 @@ int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, return 1; } -int reflog_exists(const char *refname) -{ - struct stat st; - - return !lstat(git_path("logs/%s", refname), &st) && - S_ISREG(st.st_mode); -} - -int delete_reflog(const char *refname) -{ - return remove_path(git_path("logs/%s", refname)); -} - -static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) -{ - unsigned char osha1[20], nsha1[20]; - char *email_end, *message; - unsigned long timestamp; - int tz; - - /* old SP new SP name <email> SP time TAB msg LF */ - if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || - get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || - get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || - !(email_end = strchr(sb->buf + 82, '>')) || - email_end[1] != ' ' || - !(timestamp = strtoul(email_end + 2, &message, 10)) || - !message || message[0] != ' ' || - (message[1] != '+' && message[1] != '-') || - !isdigit(message[2]) || !isdigit(message[3]) || - !isdigit(message[4]) || !isdigit(message[5])) - return 0; /* corrupt? */ - email_end[1] = '\0'; - tz = strtol(message + 1, NULL, 10); - if (message[6] != '\t') - message += 6; - else - message += 7; - return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); -} - -static char *find_beginning_of_line(char *bob, char *scan) -{ - while (bob < scan && *(--scan) != '\n') - ; /* keep scanning backwards */ - /* - * Return either beginning of the buffer, or LF at the end of - * the previous line. - */ - return scan; -} - -int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data) -{ - struct strbuf sb = STRBUF_INIT; - FILE *logfp; - long pos; - int ret = 0, at_tail = 1; - - logfp = fopen(git_path("logs/%s", refname), "r"); - if (!logfp) - return -1; - - /* Jump to the end */ - if (fseek(logfp, 0, SEEK_END) < 0) - return error("cannot seek back reflog for %s: %s", - refname, strerror(errno)); - pos = ftell(logfp); - while (!ret && 0 < pos) { - int cnt; - size_t nread; - char buf[BUFSIZ]; - char *endp, *scanp; - - /* Fill next block from the end */ - cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos; - if (fseek(logfp, pos - cnt, SEEK_SET)) - return error("cannot seek back reflog for %s: %s", - refname, strerror(errno)); - nread = fread(buf, cnt, 1, logfp); - if (nread != 1) - return error("cannot read %d bytes from reflog for %s: %s", - cnt, refname, strerror(errno)); - pos -= cnt; - - scanp = endp = buf + cnt; - if (at_tail && scanp[-1] == '\n') - /* Looking at the final LF at the end of the file */ - scanp--; - at_tail = 0; - - while (buf < scanp) { - /* - * terminating LF of the previous line, or the beginning - * of the buffer. - */ - char *bp; - - bp = find_beginning_of_line(buf, scanp); - - if (*bp == '\n') { - /* - * The newline is the end of the previous line, - * so we know we have complete line starting - * at (bp + 1). Prefix it onto any prior data - * we collected for the line and process it. - */ - strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1)); - scanp = bp; - endp = bp + 1; - ret = show_one_reflog_ent(&sb, fn, cb_data); - strbuf_reset(&sb); - if (ret) - break; - } else if (!pos) { - /* - * We are at the start of the buffer, and the - * start of the file; there is no previous - * line, and we have everything for this one. - * Process it, and we can end the loop. - */ - strbuf_splice(&sb, 0, 0, buf, endp - buf); - ret = show_one_reflog_ent(&sb, fn, cb_data); - strbuf_reset(&sb); - break; - } - - if (bp == buf) { - /* - * We are at the start of the buffer, and there - * is more file to read backwards. Which means - * we are in the middle of a line. Note that we - * may get here even if *bp was a newline; that - * just means we are at the exact end of the - * previous line, rather than some spot in the - * middle. - * - * Save away what we have to be combined with - * the data from the next read. - */ - strbuf_splice(&sb, 0, 0, buf, endp - buf); - break; - } - } - - } - if (!ret && sb.len) - die("BUG: reverse reflog parser had leftover data"); - - fclose(logfp); - strbuf_release(&sb); - return ret; -} - -int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) -{ - FILE *logfp; - struct strbuf sb = STRBUF_INIT; - int ret = 0; - - logfp = fopen(git_path("logs/%s", refname), "r"); - if (!logfp) - return -1; - - while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) - ret = show_one_reflog_ent(&sb, fn, cb_data); - fclose(logfp); - strbuf_release(&sb); - return ret; -} -/* - * Call fn for each reflog in the namespace indicated by name. name - * must be empty or end with '/'. Name will be used as a scratch - * space, but its contents will be restored before return. - */ -static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data) -{ - DIR *d = opendir(git_path("logs/%s", name->buf)); - int retval = 0; - struct dirent *de; - int oldlen = name->len; - - if (!d) - return name->len ? errno : 0; - - while ((de = readdir(d)) != NULL) { - struct stat st; - - if (de->d_name[0] == '.') - continue; - if (ends_with(de->d_name, ".lock")) - continue; - strbuf_addstr(name, de->d_name); - if (stat(git_path("logs/%s", name->buf), &st) < 0) { - ; /* silently ignore */ - } else { - if (S_ISDIR(st.st_mode)) { - strbuf_addch(name, '/'); - retval = do_for_each_reflog(name, fn, cb_data); - } else { - struct object_id oid; - - if (read_ref_full(name->buf, 0, oid.hash, NULL)) - retval = error("bad ref for %s", name->buf); - else - retval = fn(name->buf, &oid, 0, cb_data); - } - if (retval) - break; - } - strbuf_setlen(name, oldlen); - } - closedir(d); - return retval; -} - -int for_each_reflog(each_ref_fn fn, void *cb_data) -{ - int retval; - struct strbuf name; - strbuf_init(&name, PATH_MAX); - retval = do_for_each_reflog(&name, fn, cb_data); - strbuf_release(&name); - return retval; -} - -/** - * Information needed for a single ref update. Set new_sha1 to the new - * value or to null_sha1 to delete the ref. To check the old value - * while the ref is locked, set (flags & REF_HAVE_OLD) and set - * old_sha1 to the old value, or to null_sha1 to ensure the ref does - * not exist before update. - */ -struct ref_update { - /* - * If (flags & REF_HAVE_NEW), set the reference to this value: - */ - unsigned char new_sha1[20]; - /* - * If (flags & REF_HAVE_OLD), check that the reference - * previously had this value: - */ - unsigned char old_sha1[20]; - /* - * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, - * REF_DELETING, and REF_ISPRUNING: - */ - unsigned int flags; - struct ref_lock *lock; - int type; - char *msg; - const char refname[FLEX_ARRAY]; -}; - -/* - * Transaction states. - * OPEN: The transaction is in a valid state and can accept new updates. - * An OPEN transaction can be committed. - * CLOSED: A closed transaction is no longer active and no other operations - * than free can be used on it in this state. - * A transaction can either become closed by successfully committing - * an active transaction or if there is a failure while building - * the transaction thus rendering it failed/inactive. - */ -enum ref_transaction_state { - REF_TRANSACTION_OPEN = 0, - REF_TRANSACTION_CLOSED = 1 -}; - -/* - * Data structure for holding a reference transaction, which can - * consist of checks and updates to multiple references, carried out - * as atomically as possible. This structure is opaque to callers. - */ -struct ref_transaction { - struct ref_update **updates; - size_t alloc; - size_t nr; - enum ref_transaction_state state; -}; - struct ref_transaction *ref_transaction_begin(struct strbuf *err) { assert(err); @@ -4147,274 +886,6 @@ int update_ref(const char *msg, const char *refname, return 0; } -static int ref_update_reject_duplicates(struct string_list *refnames, - struct strbuf *err) -{ - int i, n = refnames->nr; - - assert(err); - - for (i = 1; i < n; i++) - if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) { - strbuf_addf(err, - "Multiple updates for ref '%s' not allowed.", - refnames->items[i].string); - return 1; - } - return 0; -} - -int ref_transaction_commit(struct ref_transaction *transaction, - struct strbuf *err) -{ - int ret = 0, i; - int n = transaction->nr; - struct ref_update **updates = transaction->updates; - struct string_list refs_to_delete = STRING_LIST_INIT_NODUP; - struct string_list_item *ref_to_delete; - struct string_list affected_refnames = STRING_LIST_INIT_NODUP; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: commit called for transaction that is not open"); - - if (!n) { - transaction->state = REF_TRANSACTION_CLOSED; - return 0; - } - - /* Fail if a refname appears more than once in the transaction: */ - for (i = 0; i < n; i++) - string_list_append(&affected_refnames, updates[i]->refname); - string_list_sort(&affected_refnames); - if (ref_update_reject_duplicates(&affected_refnames, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - - /* - * Acquire all locks, verify old values if provided, check - * that new values are valid, and write new values to the - * lockfiles, ready to be activated. Only keep one lockfile - * open at a time to avoid running out of file descriptors. - */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - - if ((update->flags & REF_HAVE_NEW) && - is_null_sha1(update->new_sha1)) - update->flags |= REF_DELETING; - update->lock = lock_ref_sha1_basic( - update->refname, - ((update->flags & REF_HAVE_OLD) ? - update->old_sha1 : NULL), - &affected_refnames, NULL, - update->flags, - &update->type, - err); - if (!update->lock) { - char *reason; - - ret = (errno == ENOTDIR) - ? TRANSACTION_NAME_CONFLICT - : TRANSACTION_GENERIC_ERROR; - reason = strbuf_detach(err, NULL); - strbuf_addf(err, "cannot lock ref '%s': %s", - update->refname, reason); - free(reason); - goto cleanup; - } - if ((update->flags & REF_HAVE_NEW) && - !(update->flags & REF_DELETING)) { - int overwriting_symref = ((update->type & REF_ISSYMREF) && - (update->flags & REF_NODEREF)); - - if (!overwriting_symref && - !hashcmp(update->lock->old_oid.hash, update->new_sha1)) { - /* - * The reference already has the desired - * value, so we don't need to write it. - */ - } else if (write_ref_to_lockfile(update->lock, - update->new_sha1, - err)) { - char *write_err = strbuf_detach(err, NULL); - - /* - * The lock was freed upon failure of - * write_ref_to_lockfile(): - */ - update->lock = NULL; - strbuf_addf(err, - "cannot update the ref '%s': %s", - update->refname, write_err); - free(write_err); - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } else { - update->flags |= REF_NEEDS_COMMIT; - } - } - if (!(update->flags & REF_NEEDS_COMMIT)) { - /* - * We didn't have to write anything to the lockfile. - * Close it to free up the file descriptor: - */ - if (close_ref(update->lock)) { - strbuf_addf(err, "Couldn't close %s.lock", - update->refname); - goto cleanup; - } - } - } - - /* Perform updates first so live commits remain referenced */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - - if (update->flags & REF_NEEDS_COMMIT) { - if (commit_ref_update(update->lock, - update->new_sha1, update->msg, - update->flags, err)) { - /* freed by commit_ref_update(): */ - update->lock = NULL; - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } else { - /* freed by commit_ref_update(): */ - update->lock = NULL; - } - } - } - - /* Perform deletes now that updates are safely completed */ - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - - if (update->flags & REF_DELETING) { - if (delete_ref_loose(update->lock, update->type, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - - if (!(update->flags & REF_ISPRUNING)) - string_list_append(&refs_to_delete, - update->lock->ref_name); - } - } - - if (repack_without_refs(&refs_to_delete, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - for_each_string_list_item(ref_to_delete, &refs_to_delete) - unlink_or_warn(git_path("logs/%s", ref_to_delete->string)); - clear_loose_ref_cache(&ref_cache); - -cleanup: - transaction->state = REF_TRANSACTION_CLOSED; - - for (i = 0; i < n; i++) - if (updates[i]->lock) - unlock_ref(updates[i]->lock); - string_list_clear(&refs_to_delete, 0); - string_list_clear(&affected_refnames, 0); - return ret; -} - -static int ref_present(const char *refname, - const struct object_id *oid, int flags, void *cb_data) -{ - struct string_list *affected_refnames = cb_data; - - return string_list_has_string(affected_refnames, refname); -} - -int initial_ref_transaction_commit(struct ref_transaction *transaction, - struct strbuf *err) -{ - struct ref_dir *loose_refs = get_loose_refs(&ref_cache); - struct ref_dir *packed_refs = get_packed_refs(&ref_cache); - int ret = 0, i; - int n = transaction->nr; - struct ref_update **updates = transaction->updates; - struct string_list affected_refnames = STRING_LIST_INIT_NODUP; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: commit called for transaction that is not open"); - - /* Fail if a refname appears more than once in the transaction: */ - for (i = 0; i < n; i++) - string_list_append(&affected_refnames, updates[i]->refname); - string_list_sort(&affected_refnames); - if (ref_update_reject_duplicates(&affected_refnames, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - - /* - * It's really undefined to call this function in an active - * repository or when there are existing references: we are - * only locking and changing packed-refs, so (1) any - * simultaneous processes might try to change a reference at - * the same time we do, and (2) any existing loose versions of - * the references that we are setting would have precedence - * over our values. But some remote helpers create the remote - * "HEAD" and "master" branches before calling this function, - * so here we really only check that none of the references - * that we are creating already exists. - */ - if (for_each_rawref(ref_present, &affected_refnames)) - die("BUG: initial ref transaction called with existing refs"); - - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - - if ((update->flags & REF_HAVE_OLD) && - !is_null_sha1(update->old_sha1)) - die("BUG: initial ref transaction with old_sha1 set"); - if (verify_refname_available(update->refname, - &affected_refnames, NULL, - loose_refs, err) || - verify_refname_available(update->refname, - &affected_refnames, NULL, - packed_refs, err)) { - ret = TRANSACTION_NAME_CONFLICT; - goto cleanup; - } - } - - if (lock_packed_refs(0)) { - strbuf_addf(err, "unable to lock packed-refs file: %s", - strerror(errno)); - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - - for (i = 0; i < n; i++) { - struct ref_update *update = updates[i]; - - if ((update->flags & REF_HAVE_NEW) && - !is_null_sha1(update->new_sha1)) - add_packed_ref(update->refname, update->new_sha1); - } - - if (commit_packed_refs()) { - strbuf_addf(err, "unable to commit packed-refs file: %s", - strerror(errno)); - ret = TRANSACTION_GENERIC_ERROR; - goto cleanup; - } - -cleanup: - transaction->state = REF_TRANSACTION_CLOSED; - string_list_clear(&affected_refnames, 0); - return ret; -} - char *shorten_unambiguous_ref(const char *refname, int strict) { int i; @@ -4568,144 +1039,46 @@ int ref_is_hidden(const char *refname, const char *refname_full) return 0; } -struct expire_reflog_cb { - unsigned int flags; - reflog_expiry_should_prune_fn *should_prune_fn; - void *policy_cb; - FILE *newlog; - unsigned char last_kept_sha1[20]; -}; - -static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) +const char *find_descendant_ref(const char *dirname, + const struct string_list *extras, + const struct string_list *skip) { - struct expire_reflog_cb *cb = cb_data; - struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; - - if (cb->flags & EXPIRE_REFLOGS_REWRITE) - osha1 = cb->last_kept_sha1; - - if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, - message, policy_cb)) { - if (!cb->newlog) - printf("would prune %s", message); - else if (cb->flags & EXPIRE_REFLOGS_VERBOSE) - printf("prune %s", message); - } else { - if (cb->newlog) { - fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), - email, timestamp, tz, message); - hashcpy(cb->last_kept_sha1, nsha1); - } - if (cb->flags & EXPIRE_REFLOGS_VERBOSE) - printf("keep %s", message); - } - return 0; -} - -int reflog_expire(const char *refname, const unsigned char *sha1, - unsigned int flags, - reflog_expiry_prepare_fn prepare_fn, - reflog_expiry_should_prune_fn should_prune_fn, - reflog_expiry_cleanup_fn cleanup_fn, - void *policy_cb_data) -{ - static struct lock_file reflog_lock; - struct expire_reflog_cb cb; - struct ref_lock *lock; - char *log_file; - int status = 0; - int type; - struct strbuf err = STRBUF_INIT; + int pos; - memset(&cb, 0, sizeof(cb)); - cb.flags = flags; - cb.policy_cb = policy_cb_data; - cb.should_prune_fn = should_prune_fn; + if (!extras) + return NULL; /* - * The reflog file is locked by holding the lock on the - * reference itself, plus we might need to update the - * reference if --updateref was specified: + * Look at the place where dirname would be inserted into + * extras. If there is an entry at that position that starts + * with dirname (remember, dirname includes the trailing + * slash) and is not in skip, then we have a conflict. */ - lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err); - if (!lock) { - error("cannot lock ref '%s': %s", refname, err.buf); - strbuf_release(&err); - return -1; - } - if (!reflog_exists(refname)) { - unlock_ref(lock); - return 0; - } + for (pos = string_list_find_insert_index(extras, dirname, 0); + pos < extras->nr; pos++) { + const char *extra_refname = extras->items[pos].string; - log_file = git_pathdup("logs/%s", refname); - if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { - /* - * Even though holding $GIT_DIR/logs/$reflog.lock has - * no locking implications, we use the lock_file - * machinery here anyway because it does a lot of the - * work we need, including cleaning up if the program - * exits unexpectedly. - */ - if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) { - struct strbuf err = STRBUF_INIT; - unable_to_lock_message(log_file, errno, &err); - error("%s", err.buf); - strbuf_release(&err); - goto failure; - } - cb.newlog = fdopen_lock_file(&reflog_lock, "w"); - if (!cb.newlog) { - error("cannot fdopen %s (%s)", - get_lock_file_path(&reflog_lock), strerror(errno)); - goto failure; - } + if (!starts_with(extra_refname, dirname)) + break; + + if (!skip || !string_list_has_string(skip, extra_refname)) + return extra_refname; } + return NULL; +} - (*prepare_fn)(refname, sha1, cb.policy_cb); - for_each_reflog_ent(refname, expire_reflog_ent, &cb); - (*cleanup_fn)(cb.policy_cb); +int rename_ref_available(const char *oldname, const char *newname) +{ + struct string_list skip = STRING_LIST_INIT_NODUP; + struct strbuf err = STRBUF_INIT; + int ret; - if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { - /* - * It doesn't make sense to adjust a reference pointed - * to by a symbolic ref based on expiring entries in - * the symbolic reference's reflog. Nor can we update - * a reference if there are no remaining reflog - * entries. - */ - int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && - !(type & REF_ISSYMREF) && - !is_null_sha1(cb.last_kept_sha1); - - if (close_lock_file(&reflog_lock)) { - status |= error("couldn't write %s: %s", log_file, - strerror(errno)); - } else if (update && - (write_in_full(get_lock_file_fd(lock->lk), - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || - close_ref(lock) < 0)) { - status |= error("couldn't write %s", - get_lock_file_path(lock->lk)); - rollback_lock_file(&reflog_lock); - } else if (commit_lock_file(&reflog_lock)) { - status |= error("unable to commit reflog '%s' (%s)", - log_file, strerror(errno)); - } else if (update && commit_ref(lock)) { - status |= error("couldn't set %s", lock->ref_name); - } - } - free(log_file); - unlock_ref(lock); - return status; - - failure: - rollback_lock_file(&reflog_lock); - free(log_file); - unlock_ref(lock); - return -1; + string_list_insert(&skip, oldname); + ret = !verify_refname_available(newname, NULL, &skip, &err); + if (!ret) + error("%s", err.buf); + + string_list_clear(&skip, 0); + strbuf_release(&err); + return ret; } diff --git a/refs/files-backend.c b/refs/files-backend.c new file mode 100644 index 000000000..4db3e36f8 --- /dev/null +++ b/refs/files-backend.c @@ -0,0 +1,3512 @@ +#include "../cache.h" +#include "../refs.h" +#include "refs-internal.h" +#include "../lockfile.h" +#include "../object.h" +#include "../dir.h" + +struct ref_lock { + char *ref_name; + char *orig_ref_name; + struct lock_file *lk; + struct object_id old_oid; +}; + +struct ref_entry; + +/* + * Information used (along with the information in ref_entry) to + * describe a single cached reference. This data structure only + * occurs embedded in a union in struct ref_entry, and only when + * (ref_entry->flag & REF_DIR) is zero. + */ +struct ref_value { + /* + * The name of the object to which this reference resolves + * (which may be a tag object). If REF_ISBROKEN, this is + * null. If REF_ISSYMREF, then this is the name of the object + * referred to by the last reference in the symlink chain. + */ + struct object_id oid; + + /* + * If REF_KNOWS_PEELED, then this field holds the peeled value + * of this reference, or null if the reference is known not to + * be peelable. See the documentation for peel_ref() for an + * exact definition of "peelable". + */ + struct object_id peeled; +}; + +struct ref_cache; + +/* + * Information used (along with the information in ref_entry) to + * describe a level in the hierarchy of references. This data + * structure only occurs embedded in a union in struct ref_entry, and + * only when (ref_entry.flag & REF_DIR) is set. In that case, + * (ref_entry.flag & REF_INCOMPLETE) determines whether the references + * in the directory have already been read: + * + * (ref_entry.flag & REF_INCOMPLETE) unset -- a directory of loose + * or packed references, already read. + * + * (ref_entry.flag & REF_INCOMPLETE) set -- a directory of loose + * references that hasn't been read yet (nor has any of its + * subdirectories). + * + * Entries within a directory are stored within a growable array of + * pointers to ref_entries (entries, nr, alloc). Entries 0 <= i < + * sorted are sorted by their component name in strcmp() order and the + * remaining entries are unsorted. + * + * Loose references are read lazily, one directory at a time. When a + * directory of loose references is read, then all of the references + * in that directory are stored, and REF_INCOMPLETE stubs are created + * for any subdirectories, but the subdirectories themselves are not + * read. The reading is triggered by get_ref_dir(). + */ +struct ref_dir { + int nr, alloc; + + /* + * Entries with index 0 <= i < sorted are sorted by name. New + * entries are appended to the list unsorted, and are sorted + * only when required; thus we avoid the need to sort the list + * after the addition of every reference. + */ + int sorted; + + /* A pointer to the ref_cache that contains this ref_dir. */ + struct ref_cache *ref_cache; + + struct ref_entry **entries; +}; + +/* + * Bit values for ref_entry::flag. REF_ISSYMREF=0x01, + * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are + * public values; see refs.h. + */ + +/* + * The field ref_entry->u.value.peeled of this value entry contains + * the correct peeled value for the reference, which might be + * null_sha1 if the reference is not a tag or if it is broken. + */ +#define REF_KNOWS_PEELED 0x10 + +/* ref_entry represents a directory of references */ +#define REF_DIR 0x20 + +/* + * Entry has not yet been read from disk (used only for REF_DIR + * entries representing loose references) + */ +#define REF_INCOMPLETE 0x40 + +/* + * A ref_entry represents either a reference or a "subdirectory" of + * references. + * + * Each directory in the reference namespace is represented by a + * ref_entry with (flags & REF_DIR) set and containing a subdir member + * that holds the entries in that directory that have been read so + * far. If (flags & REF_INCOMPLETE) is set, then the directory and + * its subdirectories haven't been read yet. REF_INCOMPLETE is only + * used for loose reference directories. + * + * References are represented by a ref_entry with (flags & REF_DIR) + * unset and a value member that describes the reference's value. The + * flag member is at the ref_entry level, but it is also needed to + * interpret the contents of the value field (in other words, a + * ref_value object is not very much use without the enclosing + * ref_entry). + * + * Reference names cannot end with slash and directories' names are + * always stored with a trailing slash (except for the top-level + * directory, which is always denoted by ""). This has two nice + * consequences: (1) when the entries in each subdir are sorted + * lexicographically by name (as they usually are), the references in + * a whole tree can be generated in lexicographic order by traversing + * the tree in left-to-right, depth-first order; (2) the names of + * references and subdirectories cannot conflict, and therefore the + * presence of an empty subdirectory does not block the creation of a + * similarly-named reference. (The fact that reference names with the + * same leading components can conflict *with each other* is a + * separate issue that is regulated by verify_refname_available().) + * + * Please note that the name field contains the fully-qualified + * reference (or subdirectory) name. Space could be saved by only + * storing the relative names. But that would require the full names + * to be generated on the fly when iterating in do_for_each_ref(), and + * would break callback functions, who have always been able to assume + * that the name strings that they are passed will not be freed during + * the iteration. + */ +struct ref_entry { + unsigned char flag; /* ISSYMREF? ISPACKED? */ + union { + struct ref_value value; /* if not (flags&REF_DIR) */ + struct ref_dir subdir; /* if (flags&REF_DIR) */ + } u; + /* + * The full name of the reference (e.g., "refs/heads/master") + * or the full name of the directory with a trailing slash + * (e.g., "refs/heads/"): + */ + char name[FLEX_ARRAY]; +}; + +static void read_loose_refs(const char *dirname, struct ref_dir *dir); +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len); +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, + const char *dirname, size_t len, + int incomplete); +static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); + +static struct ref_dir *get_ref_dir(struct ref_entry *entry) +{ + struct ref_dir *dir; + assert(entry->flag & REF_DIR); + dir = &entry->u.subdir; + if (entry->flag & REF_INCOMPLETE) { + read_loose_refs(entry->name, dir); + + /* + * Manually add refs/bisect, which, being + * per-worktree, might not appear in the directory + * listing for refs/ in the main repo. + */ + if (!strcmp(entry->name, "refs/")) { + int pos = search_ref_dir(dir, "refs/bisect/", 12); + if (pos < 0) { + struct ref_entry *child_entry; + child_entry = create_dir_entry(dir->ref_cache, + "refs/bisect/", + 12, 1); + add_entry_to_dir(dir, child_entry); + read_loose_refs("refs/bisect", + &child_entry->u.subdir); + } + } + entry->flag &= ~REF_INCOMPLETE; + } + return dir; +} + +static struct ref_entry *create_ref_entry(const char *refname, + const unsigned char *sha1, int flag, + int check_name) +{ + int len; + struct ref_entry *ref; + + if (check_name && + check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) + die("Reference has invalid format: '%s'", refname); + len = strlen(refname) + 1; + ref = xmalloc(sizeof(struct ref_entry) + len); + hashcpy(ref->u.value.oid.hash, sha1); + oidclr(&ref->u.value.peeled); + memcpy(ref->name, refname, len); + ref->flag = flag; + return ref; +} + +static void clear_ref_dir(struct ref_dir *dir); + +static void free_ref_entry(struct ref_entry *entry) +{ + if (entry->flag & REF_DIR) { + /* + * Do not use get_ref_dir() here, as that might + * trigger the reading of loose refs. + */ + clear_ref_dir(&entry->u.subdir); + } + free(entry); +} + +/* + * Add a ref_entry to the end of dir (unsorted). Entry is always + * stored directly in dir; no recursion into subdirectories is + * done. + */ +static void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry) +{ + ALLOC_GROW(dir->entries, dir->nr + 1, dir->alloc); + dir->entries[dir->nr++] = entry; + /* optimize for the case that entries are added in order */ + if (dir->nr == 1 || + (dir->nr == dir->sorted + 1 && + strcmp(dir->entries[dir->nr - 2]->name, + dir->entries[dir->nr - 1]->name) < 0)) + dir->sorted = dir->nr; +} + +/* + * Clear and free all entries in dir, recursively. + */ +static void clear_ref_dir(struct ref_dir *dir) +{ + int i; + for (i = 0; i < dir->nr; i++) + free_ref_entry(dir->entries[i]); + free(dir->entries); + dir->sorted = dir->nr = dir->alloc = 0; + dir->entries = NULL; +} + +/* + * Create a struct ref_entry object for the specified dirname. + * dirname is the name of the directory with a trailing slash (e.g., + * "refs/heads/") or "" for the top-level directory. + */ +static struct ref_entry *create_dir_entry(struct ref_cache *ref_cache, + const char *dirname, size_t len, + int incomplete) +{ + struct ref_entry *direntry; + direntry = xcalloc(1, sizeof(struct ref_entry) + len + 1); + memcpy(direntry->name, dirname, len); + direntry->name[len] = '\0'; + direntry->u.subdir.ref_cache = ref_cache; + direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); + return direntry; +} + +static int ref_entry_cmp(const void *a, const void *b) +{ + struct ref_entry *one = *(struct ref_entry **)a; + struct ref_entry *two = *(struct ref_entry **)b; + return strcmp(one->name, two->name); +} + +static void sort_ref_dir(struct ref_dir *dir); + +struct string_slice { + size_t len; + const char *str; +}; + +static int ref_entry_cmp_sslice(const void *key_, const void *ent_) +{ + const struct string_slice *key = key_; + const struct ref_entry *ent = *(const struct ref_entry * const *)ent_; + int cmp = strncmp(key->str, ent->name, key->len); + if (cmp) + return cmp; + return '\0' - (unsigned char)ent->name[key->len]; +} + +/* + * Return the index of the entry with the given refname from the + * ref_dir (non-recursively), sorting dir if necessary. Return -1 if + * no such entry is found. dir must already be complete. + */ +static int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) +{ + struct ref_entry **r; + struct string_slice key; + + if (refname == NULL || !dir->nr) + return -1; + + sort_ref_dir(dir); + key.len = len; + key.str = refname; + r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries), + ref_entry_cmp_sslice); + + if (r == NULL) + return -1; + + return r - dir->entries; +} + +/* + * Search for a directory entry directly within dir (without + * recursing). Sort dir if necessary. subdirname must be a directory + * name (i.e., end in '/'). If mkdir is set, then create the + * directory if it is missing; otherwise, return NULL if the desired + * directory cannot be found. dir must already be complete. + */ +static struct ref_dir *search_for_subdir(struct ref_dir *dir, + const char *subdirname, size_t len, + int mkdir) +{ + int entry_index = search_ref_dir(dir, subdirname, len); + struct ref_entry *entry; + if (entry_index == -1) { + if (!mkdir) + return NULL; + /* + * Since dir is complete, the absence of a subdir + * means that the subdir really doesn't exist; + * therefore, create an empty record for it but mark + * the record complete. + */ + entry = create_dir_entry(dir->ref_cache, subdirname, len, 0); + add_entry_to_dir(dir, entry); + } else { + entry = dir->entries[entry_index]; + } + return get_ref_dir(entry); +} + +/* + * If refname is a reference name, find the ref_dir within the dir + * tree that should hold refname. If refname is a directory name + * (i.e., ends in '/'), then return that ref_dir itself. dir must + * represent the top-level directory and must already be complete. + * Sort ref_dirs and recurse into subdirectories as necessary. If + * mkdir is set, then create any missing directories; otherwise, + * return NULL if the desired directory cannot be found. + */ +static struct ref_dir *find_containing_dir(struct ref_dir *dir, + const char *refname, int mkdir) +{ + const char *slash; + for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { + size_t dirnamelen = slash - refname + 1; + struct ref_dir *subdir; + subdir = search_for_subdir(dir, refname, dirnamelen, mkdir); + if (!subdir) { + dir = NULL; + break; + } + dir = subdir; + } + + return dir; +} + +/* + * Find the value entry with the given name in dir, sorting ref_dirs + * and recursing into subdirectories as necessary. If the name is not + * found or it corresponds to a directory entry, return NULL. + */ +static struct ref_entry *find_ref(struct ref_dir *dir, const char *refname) +{ + int entry_index; + struct ref_entry *entry; + dir = find_containing_dir(dir, refname, 0); + if (!dir) + return NULL; + entry_index = search_ref_dir(dir, refname, strlen(refname)); + if (entry_index == -1) + return NULL; + entry = dir->entries[entry_index]; + return (entry->flag & REF_DIR) ? NULL : entry; +} + +/* + * Remove the entry with the given name from dir, recursing into + * subdirectories as necessary. If refname is the name of a directory + * (i.e., ends with '/'), then remove the directory and its contents. + * If the removal was successful, return the number of entries + * remaining in the directory entry that contained the deleted entry. + * If the name was not found, return -1. Please note that this + * function only deletes the entry from the cache; it does not delete + * it from the filesystem or ensure that other cache entries (which + * might be symbolic references to the removed entry) are updated. + * Nor does it remove any containing dir entries that might be made + * empty by the removal. dir must represent the top-level directory + * and must already be complete. + */ +static int remove_entry(struct ref_dir *dir, const char *refname) +{ + int refname_len = strlen(refname); + int entry_index; + struct ref_entry *entry; + int is_dir = refname[refname_len - 1] == '/'; + if (is_dir) { + /* + * refname represents a reference directory. Remove + * the trailing slash; otherwise we will get the + * directory *representing* refname rather than the + * one *containing* it. + */ + char *dirname = xmemdupz(refname, refname_len - 1); + dir = find_containing_dir(dir, dirname, 0); + free(dirname); + } else { + dir = find_containing_dir(dir, refname, 0); + } + if (!dir) + return -1; + entry_index = search_ref_dir(dir, refname, refname_len); + if (entry_index == -1) + return -1; + entry = dir->entries[entry_index]; + + memmove(&dir->entries[entry_index], + &dir->entries[entry_index + 1], + (dir->nr - entry_index - 1) * sizeof(*dir->entries) + ); + dir->nr--; + if (dir->sorted > entry_index) + dir->sorted--; + free_ref_entry(entry); + return dir->nr; +} + +/* + * Add a ref_entry to the ref_dir (unsorted), recursing into + * subdirectories as necessary. dir must represent the top-level + * directory. Return 0 on success. + */ +static int add_ref(struct ref_dir *dir, struct ref_entry *ref) +{ + dir = find_containing_dir(dir, ref->name, 1); + if (!dir) + return -1; + add_entry_to_dir(dir, ref); + return 0; +} + +/* + * Emit a warning and return true iff ref1 and ref2 have the same name + * and the same sha1. Die if they have the same name but different + * sha1s. + */ +static int is_dup_ref(const struct ref_entry *ref1, const struct ref_entry *ref2) +{ + if (strcmp(ref1->name, ref2->name)) + return 0; + + /* Duplicate name; make sure that they don't conflict: */ + + if ((ref1->flag & REF_DIR) || (ref2->flag & REF_DIR)) + /* This is impossible by construction */ + die("Reference directory conflict: %s", ref1->name); + + if (oidcmp(&ref1->u.value.oid, &ref2->u.value.oid)) + die("Duplicated ref, and SHA1s don't match: %s", ref1->name); + + warning("Duplicated ref: %s", ref1->name); + return 1; +} + +/* + * Sort the entries in dir non-recursively (if they are not already + * sorted) and remove any duplicate entries. + */ +static void sort_ref_dir(struct ref_dir *dir) +{ + int i, j; + struct ref_entry *last = NULL; + + /* + * This check also prevents passing a zero-length array to qsort(), + * which is a problem on some platforms. + */ + if (dir->sorted == dir->nr) + return; + + qsort(dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp); + + /* Remove any duplicates: */ + for (i = 0, j = 0; j < dir->nr; j++) { + struct ref_entry *entry = dir->entries[j]; + if (last && is_dup_ref(last, entry)) + free_ref_entry(entry); + else + last = dir->entries[i++] = entry; + } + dir->sorted = dir->nr = i; +} + +/* Include broken references in a do_for_each_ref*() iteration: */ +#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 + +/* + * Return true iff the reference described by entry can be resolved to + * an object in the database. Emit a warning if the referred-to + * object does not exist. + */ +static int ref_resolves_to_object(struct ref_entry *entry) +{ + if (entry->flag & REF_ISBROKEN) + return 0; + if (!has_sha1_file(entry->u.value.oid.hash)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } + return 1; +} + +/* + * current_ref is a performance hack: when iterating over references + * using the for_each_ref*() functions, current_ref is set to the + * current reference's entry before calling the callback function. If + * the callback function calls peel_ref(), then peel_ref() first + * checks whether the reference to be peeled is the current reference + * (it usually is) and if so, returns that reference's peeled version + * if it is available. This avoids a refname lookup in a common case. + */ +static struct ref_entry *current_ref; + +typedef int each_ref_entry_fn(struct ref_entry *entry, void *cb_data); + +struct ref_entry_cb { + const char *base; + int trim; + int flags; + each_ref_fn *fn; + void *cb_data; +}; + +/* + * Handle one reference in a do_for_each_ref*()-style iteration, + * calling an each_ref_fn for each entry. + */ +static int do_one_ref(struct ref_entry *entry, void *cb_data) +{ + struct ref_entry_cb *data = cb_data; + struct ref_entry *old_current_ref; + int retval; + + if (!starts_with(entry->name, data->base)) + return 0; + + if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(entry)) + return 0; + + /* Store the old value, in case this is a recursive call: */ + old_current_ref = current_ref; + current_ref = entry; + retval = data->fn(entry->name + data->trim, &entry->u.value.oid, + entry->flag, data->cb_data); + current_ref = old_current_ref; + return retval; +} + +/* + * Call fn for each reference in dir that has index in the range + * offset <= index < dir->nr. Recurse into subdirectories that are in + * that index range, sorting them before iterating. This function + * does not sort dir itself; it should be sorted beforehand. fn is + * called for all references, including broken ones. + */ +static int do_for_each_entry_in_dir(struct ref_dir *dir, int offset, + each_ref_entry_fn fn, void *cb_data) +{ + int i; + assert(dir->sorted == dir->nr); + for (i = offset; i < dir->nr; i++) { + struct ref_entry *entry = dir->entries[i]; + int retval; + if (entry->flag & REF_DIR) { + struct ref_dir *subdir = get_ref_dir(entry); + sort_ref_dir(subdir); + retval = do_for_each_entry_in_dir(subdir, 0, fn, cb_data); + } else { + retval = fn(entry, cb_data); + } + if (retval) + return retval; + } + return 0; +} + +/* + * Call fn for each reference in the union of dir1 and dir2, in order + * by refname. Recurse into subdirectories. If a value entry appears + * in both dir1 and dir2, then only process the version that is in + * dir2. The input dirs must already be sorted, but subdirs will be + * sorted as needed. fn is called for all references, including + * broken ones. + */ +static int do_for_each_entry_in_dirs(struct ref_dir *dir1, + struct ref_dir *dir2, + each_ref_entry_fn fn, void *cb_data) +{ + int retval; + int i1 = 0, i2 = 0; + + assert(dir1->sorted == dir1->nr); + assert(dir2->sorted == dir2->nr); + while (1) { + struct ref_entry *e1, *e2; + int cmp; + if (i1 == dir1->nr) { + return do_for_each_entry_in_dir(dir2, i2, fn, cb_data); + } + if (i2 == dir2->nr) { + return do_for_each_entry_in_dir(dir1, i1, fn, cb_data); + } + e1 = dir1->entries[i1]; + e2 = dir2->entries[i2]; + cmp = strcmp(e1->name, e2->name); + if (cmp == 0) { + if ((e1->flag & REF_DIR) && (e2->flag & REF_DIR)) { + /* Both are directories; descend them in parallel. */ + struct ref_dir *subdir1 = get_ref_dir(e1); + struct ref_dir *subdir2 = get_ref_dir(e2); + sort_ref_dir(subdir1); + sort_ref_dir(subdir2); + retval = do_for_each_entry_in_dirs( + subdir1, subdir2, fn, cb_data); + i1++; + i2++; + } else if (!(e1->flag & REF_DIR) && !(e2->flag & REF_DIR)) { + /* Both are references; ignore the one from dir1. */ + retval = fn(e2, cb_data); + i1++; + i2++; + } else { + die("conflict between reference and directory: %s", + e1->name); + } + } else { + struct ref_entry *e; + if (cmp < 0) { + e = e1; + i1++; + } else { + e = e2; + i2++; + } + if (e->flag & REF_DIR) { + struct ref_dir *subdir = get_ref_dir(e); + sort_ref_dir(subdir); + retval = do_for_each_entry_in_dir( + subdir, 0, fn, cb_data); + } else { + retval = fn(e, cb_data); + } + } + if (retval) + return retval; + } +} + +/* + * Load all of the refs from the dir into our in-memory cache. The hard work + * of loading loose refs is done by get_ref_dir(), so we just need to recurse + * through all of the sub-directories. We do not even need to care about + * sorting, as traversal order does not matter to us. + */ +static void prime_ref_dir(struct ref_dir *dir) +{ + int i; + for (i = 0; i < dir->nr; i++) { + struct ref_entry *entry = dir->entries[i]; + if (entry->flag & REF_DIR) + prime_ref_dir(get_ref_dir(entry)); + } +} + +struct nonmatching_ref_data { + const struct string_list *skip; + const char *conflicting_refname; +}; + +static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata) +{ + struct nonmatching_ref_data *data = vdata; + + if (data->skip && string_list_has_string(data->skip, entry->name)) + return 0; + + data->conflicting_refname = entry->name; + return 1; +} + +/* + * Return 0 if a reference named refname could be created without + * conflicting with the name of an existing reference in dir. + * See verify_refname_available for more information. + */ +static int verify_refname_available_dir(const char *refname, + const struct string_list *extras, + const struct string_list *skip, + struct ref_dir *dir, + struct strbuf *err) +{ + const char *slash; + const char *extra_refname; + int pos; + struct strbuf dirname = STRBUF_INIT; + int ret = -1; + + /* + * For the sake of comments in this function, suppose that + * refname is "refs/foo/bar". + */ + + assert(err); + + strbuf_grow(&dirname, strlen(refname) + 1); + for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { + /* Expand dirname to the new prefix, not including the trailing slash: */ + strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); + + /* + * We are still at a leading dir of the refname (e.g., + * "refs/foo"; if there is a reference with that name, + * it is a conflict, *unless* it is in skip. + */ + if (dir) { + pos = search_ref_dir(dir, dirname.buf, dirname.len); + if (pos >= 0 && + (!skip || !string_list_has_string(skip, dirname.buf))) { + /* + * We found a reference whose name is + * a proper prefix of refname; e.g., + * "refs/foo", and is not in skip. + */ + strbuf_addf(err, "'%s' exists; cannot create '%s'", + dirname.buf, refname); + goto cleanup; + } + } + + if (extras && string_list_has_string(extras, dirname.buf) && + (!skip || !string_list_has_string(skip, dirname.buf))) { + strbuf_addf(err, "cannot process '%s' and '%s' at the same time", + refname, dirname.buf); + goto cleanup; + } + + /* + * Otherwise, we can try to continue our search with + * the next component. So try to look up the + * directory, e.g., "refs/foo/". If we come up empty, + * we know there is nothing under this whole prefix, + * but even in that case we still have to continue the + * search for conflicts with extras. + */ + strbuf_addch(&dirname, '/'); + if (dir) { + pos = search_ref_dir(dir, dirname.buf, dirname.len); + if (pos < 0) { + /* + * There was no directory "refs/foo/", + * so there is nothing under this + * whole prefix. So there is no need + * to continue looking for conflicting + * references. But we need to continue + * looking for conflicting extras. + */ + dir = NULL; + } else { + dir = get_ref_dir(dir->entries[pos]); + } + } + } + + /* + * We are at the leaf of our refname (e.g., "refs/foo/bar"). + * There is no point in searching for a reference with that + * name, because a refname isn't considered to conflict with + * itself. But we still need to check for references whose + * names are in the "refs/foo/bar/" namespace, because they + * *do* conflict. + */ + strbuf_addstr(&dirname, refname + dirname.len); + strbuf_addch(&dirname, '/'); + + if (dir) { + pos = search_ref_dir(dir, dirname.buf, dirname.len); + + if (pos >= 0) { + /* + * We found a directory named "$refname/" + * (e.g., "refs/foo/bar/"). It is a problem + * iff it contains any ref that is not in + * "skip". + */ + struct nonmatching_ref_data data; + + data.skip = skip; + data.conflicting_refname = NULL; + dir = get_ref_dir(dir->entries[pos]); + sort_ref_dir(dir); + if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) { + strbuf_addf(err, "'%s' exists; cannot create '%s'", + data.conflicting_refname, refname); + goto cleanup; + } + } + } + + extra_refname = find_descendant_ref(dirname.buf, extras, skip); + if (extra_refname) + strbuf_addf(err, "cannot process '%s' and '%s' at the same time", + refname, extra_refname); + else + ret = 0; + +cleanup: + strbuf_release(&dirname); + return ret; +} + +struct packed_ref_cache { + struct ref_entry *root; + + /* + * Count of references to the data structure in this instance, + * including the pointer from ref_cache::packed if any. The + * data will not be freed as long as the reference count is + * nonzero. + */ + unsigned int referrers; + + /* + * Iff the packed-refs file associated with this instance is + * currently locked for writing, this points at the associated + * lock (which is owned by somebody else). The referrer count + * is also incremented when the file is locked and decremented + * when it is unlocked. + */ + struct lock_file *lock; + + /* The metadata from when this packed-refs cache was read */ + struct stat_validity validity; +}; + +/* + * Future: need to be in "struct repository" + * when doing a full libification. + */ +static struct ref_cache { + struct ref_cache *next; + struct ref_entry *loose; + struct packed_ref_cache *packed; + /* + * The submodule name, or "" for the main repo. We allocate + * length 1 rather than FLEX_ARRAY so that the main ref_cache + * is initialized correctly. + */ + char name[1]; +} ref_cache, *submodule_ref_caches; + +/* Lock used for the main packed-refs file: */ +static struct lock_file packlock; + +/* + * Increment the reference count of *packed_refs. + */ +static void acquire_packed_ref_cache(struct packed_ref_cache *packed_refs) +{ + packed_refs->referrers++; +} + +/* + * Decrease the reference count of *packed_refs. If it goes to zero, + * free *packed_refs and return true; otherwise return false. + */ +static int release_packed_ref_cache(struct packed_ref_cache *packed_refs) +{ + if (!--packed_refs->referrers) { + free_ref_entry(packed_refs->root); + stat_validity_clear(&packed_refs->validity); + free(packed_refs); + return 1; + } else { + return 0; + } +} + +static void clear_packed_ref_cache(struct ref_cache *refs) +{ + if (refs->packed) { + struct packed_ref_cache *packed_refs = refs->packed; + + if (packed_refs->lock) + die("internal error: packed-ref cache cleared while locked"); + refs->packed = NULL; + release_packed_ref_cache(packed_refs); + } +} + +static void clear_loose_ref_cache(struct ref_cache *refs) +{ + if (refs->loose) { + free_ref_entry(refs->loose); + refs->loose = NULL; + } +} + +static struct ref_cache *create_ref_cache(const char *submodule) +{ + int len; + struct ref_cache *refs; + if (!submodule) + submodule = ""; + len = strlen(submodule) + 1; + refs = xcalloc(1, sizeof(struct ref_cache) + len); + memcpy(refs->name, submodule, len); + return refs; +} + +/* + * Return a pointer to a ref_cache for the specified submodule. For + * the main repository, use submodule==NULL. The returned structure + * will be allocated and initialized but not necessarily populated; it + * should not be freed. + */ +static struct ref_cache *get_ref_cache(const char *submodule) +{ + struct ref_cache *refs; + + if (!submodule || !*submodule) + return &ref_cache; + + for (refs = submodule_ref_caches; refs; refs = refs->next) + if (!strcmp(submodule, refs->name)) + return refs; + + refs = create_ref_cache(submodule); + refs->next = submodule_ref_caches; + submodule_ref_caches = refs; + return refs; +} + +/* The length of a peeled reference line in packed-refs, including EOL: */ +#define PEELED_LINE_LENGTH 42 + +/* + * The packed-refs header line that we write out. Perhaps other + * traits will be added later. The trailing space is required. + */ +static const char PACKED_REFS_HEADER[] = + "# pack-refs with: peeled fully-peeled \n"; + +/* + * Parse one line from a packed-refs file. Write the SHA1 to sha1. + * Return a pointer to the refname within the line (null-terminated), + * or NULL if there was a problem. + */ +static const char *parse_ref_line(struct strbuf *line, unsigned char *sha1) +{ + const char *ref; + + /* + * 42: the answer to everything. + * + * In this case, it happens to be the answer to + * 40 (length of sha1 hex representation) + * +1 (space in between hex and name) + * +1 (newline at the end of the line) + */ + if (line->len <= 42) + return NULL; + + if (get_sha1_hex(line->buf, sha1) < 0) + return NULL; + if (!isspace(line->buf[40])) + return NULL; + + ref = line->buf + 41; + if (isspace(*ref)) + return NULL; + + if (line->buf[line->len - 1] != '\n') + return NULL; + line->buf[--line->len] = 0; + + return ref; +} + +/* + * Read f, which is a packed-refs file, into dir. + * + * A comment line of the form "# pack-refs with: " may contain zero or + * more traits. We interpret the traits as follows: + * + * No traits: + * + * Probably no references are peeled. But if the file contains a + * peeled value for a reference, we will use it. + * + * peeled: + * + * References under "refs/tags/", if they *can* be peeled, *are* + * peeled in this file. References outside of "refs/tags/" are + * probably not peeled even if they could have been, but if we find + * a peeled value for such a reference we will use it. + * + * fully-peeled: + * + * All references in the file that can be peeled are peeled. + * Inversely (and this is more important), any references in the + * file for which no peeled value is recorded is not peelable. This + * trait should typically be written alongside "peeled" for + * compatibility with older clients, but we do not require it + * (i.e., "peeled" is a no-op if "fully-peeled" is set). + */ +static void read_packed_refs(FILE *f, struct ref_dir *dir) +{ + struct ref_entry *last = NULL; + struct strbuf line = STRBUF_INIT; + enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE; + + while (strbuf_getwholeline(&line, f, '\n') != EOF) { + unsigned char sha1[20]; + const char *refname; + const char *traits; + + if (skip_prefix(line.buf, "# pack-refs with:", &traits)) { + if (strstr(traits, " fully-peeled ")) + peeled = PEELED_FULLY; + else if (strstr(traits, " peeled ")) + peeled = PEELED_TAGS; + /* perhaps other traits later as well */ + continue; + } + + refname = parse_ref_line(&line, sha1); + if (refname) { + int flag = REF_ISPACKED; + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + if (!refname_is_safe(refname)) + die("packed refname is dangerous: %s", refname); + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } + last = create_ref_entry(refname, sha1, flag, 0); + if (peeled == PEELED_FULLY || + (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/"))) + last->flag |= REF_KNOWS_PEELED; + add_ref(dir, last); + continue; + } + if (last && + line.buf[0] == '^' && + line.len == PEELED_LINE_LENGTH && + line.buf[PEELED_LINE_LENGTH - 1] == '\n' && + !get_sha1_hex(line.buf + 1, sha1)) { + hashcpy(last->u.value.peeled.hash, sha1); + /* + * Regardless of what the file header said, + * we definitely know the value of *this* + * reference: + */ + last->flag |= REF_KNOWS_PEELED; + } + } + + strbuf_release(&line); +} + +/* + * Get the packed_ref_cache for the specified ref_cache, creating it + * if necessary. + */ +static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs) +{ + char *packed_refs_file; + + if (*refs->name) + packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs"); + else + packed_refs_file = git_pathdup("packed-refs"); + + if (refs->packed && + !stat_validity_check(&refs->packed->validity, packed_refs_file)) + clear_packed_ref_cache(refs); + + if (!refs->packed) { + FILE *f; + + refs->packed = xcalloc(1, sizeof(*refs->packed)); + acquire_packed_ref_cache(refs->packed); + refs->packed->root = create_dir_entry(refs, "", 0, 0); + f = fopen(packed_refs_file, "r"); + if (f) { + stat_validity_update(&refs->packed->validity, fileno(f)); + read_packed_refs(f, get_ref_dir(refs->packed->root)); + fclose(f); + } + } + free(packed_refs_file); + return refs->packed; +} + +static struct ref_dir *get_packed_ref_dir(struct packed_ref_cache *packed_ref_cache) +{ + return get_ref_dir(packed_ref_cache->root); +} + +static struct ref_dir *get_packed_refs(struct ref_cache *refs) +{ + return get_packed_ref_dir(get_packed_ref_cache(refs)); +} + +/* + * Add a reference to the in-memory packed reference cache. This may + * only be called while the packed-refs file is locked (see + * lock_packed_refs()). To actually write the packed-refs file, call + * commit_packed_refs(). + */ +static void add_packed_ref(const char *refname, const unsigned char *sha1) +{ + struct packed_ref_cache *packed_ref_cache = + get_packed_ref_cache(&ref_cache); + + if (!packed_ref_cache->lock) + die("internal error: packed refs not locked"); + add_ref(get_packed_ref_dir(packed_ref_cache), + create_ref_entry(refname, sha1, REF_ISPACKED, 1)); +} + +/* + * Read the loose references from the namespace dirname into dir + * (without recursing). dirname must end with '/'. dir must be the + * directory entry corresponding to dirname. + */ +static void read_loose_refs(const char *dirname, struct ref_dir *dir) +{ + struct ref_cache *refs = dir->ref_cache; + DIR *d; + struct dirent *de; + int dirnamelen = strlen(dirname); + struct strbuf refname; + struct strbuf path = STRBUF_INIT; + size_t path_baselen; + + if (*refs->name) + strbuf_git_path_submodule(&path, refs->name, "%s", dirname); + else + strbuf_git_path(&path, "%s", dirname); + path_baselen = path.len; + + d = opendir(path.buf); + if (!d) { + strbuf_release(&path); + return; + } + + strbuf_init(&refname, dirnamelen + 257); + strbuf_add(&refname, dirname, dirnamelen); + + while ((de = readdir(d)) != NULL) { + unsigned char sha1[20]; + struct stat st; + int flag; + + if (de->d_name[0] == '.') + continue; + if (ends_with(de->d_name, ".lock")) + continue; + strbuf_addstr(&refname, de->d_name); + strbuf_addstr(&path, de->d_name); + if (stat(path.buf, &st) < 0) { + ; /* silently ignore */ + } else if (S_ISDIR(st.st_mode)) { + strbuf_addch(&refname, '/'); + add_entry_to_dir(dir, + create_dir_entry(refs, refname.buf, + refname.len, 1)); + } else { + int read_ok; + + if (*refs->name) { + hashclr(sha1); + flag = 0; + read_ok = !resolve_gitlink_ref(refs->name, + refname.buf, sha1); + } else { + read_ok = !read_ref_full(refname.buf, + RESOLVE_REF_READING, + sha1, &flag); + } + + if (!read_ok) { + hashclr(sha1); + flag |= REF_ISBROKEN; + } else if (is_null_sha1(sha1)) { + /* + * It is so astronomically unlikely + * that NULL_SHA1 is the SHA-1 of an + * actual object that we consider its + * appearance in a loose reference + * file to be repo corruption + * (probably due to a software bug). + */ + flag |= REF_ISBROKEN; + } + + if (check_refname_format(refname.buf, + REFNAME_ALLOW_ONELEVEL)) { + if (!refname_is_safe(refname.buf)) + die("loose refname is dangerous: %s", refname.buf); + hashclr(sha1); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } + add_entry_to_dir(dir, + create_ref_entry(refname.buf, sha1, flag, 0)); + } + strbuf_setlen(&refname, dirnamelen); + strbuf_setlen(&path, path_baselen); + } + strbuf_release(&refname); + strbuf_release(&path); + closedir(d); +} + +static struct ref_dir *get_loose_refs(struct ref_cache *refs) +{ + if (!refs->loose) { + /* + * Mark the top-level directory complete because we + * are about to read the only subdirectory that can + * hold references: + */ + refs->loose = create_dir_entry(refs, "", 0, 0); + /* + * Create an incomplete entry for "refs/": + */ + add_entry_to_dir(get_ref_dir(refs->loose), + create_dir_entry(refs, "refs/", 5, 1)); + } + return get_ref_dir(refs->loose); +} + +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define MAXDEPTH 5 +#define MAXREFLEN (1024) + +/* + * Called by resolve_gitlink_ref_recursive() after it failed to read + * from the loose refs in ref_cache refs. Find <refname> in the + * packed-refs file for the submodule. + */ +static int resolve_gitlink_packed_ref(struct ref_cache *refs, + const char *refname, unsigned char *sha1) +{ + struct ref_entry *ref; + struct ref_dir *dir = get_packed_refs(refs); + + ref = find_ref(dir, refname); + if (ref == NULL) + return -1; + + hashcpy(sha1, ref->u.value.oid.hash); + return 0; +} + +static int resolve_gitlink_ref_recursive(struct ref_cache *refs, + const char *refname, unsigned char *sha1, + int recursion) +{ + int fd, len; + char buffer[128], *p; + char *path; + + if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN) + return -1; + path = *refs->name + ? git_pathdup_submodule(refs->name, "%s", refname) + : git_pathdup("%s", refname); + fd = open(path, O_RDONLY); + free(path); + if (fd < 0) + return resolve_gitlink_packed_ref(refs, refname, sha1); + + len = read(fd, buffer, sizeof(buffer)-1); + close(fd); + if (len < 0) + return -1; + while (len && isspace(buffer[len-1])) + len--; + buffer[len] = 0; + + /* Was it a detached head or an old-fashioned symlink? */ + if (!get_sha1_hex(buffer, sha1)) + return 0; + + /* Symref? */ + if (strncmp(buffer, "ref:", 4)) + return -1; + p = buffer + 4; + while (isspace(*p)) + p++; + + return resolve_gitlink_ref_recursive(refs, p, sha1, recursion+1); +} + +int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1) +{ + int len = strlen(path), retval; + char *submodule; + struct ref_cache *refs; + + while (len && path[len-1] == '/') + len--; + if (!len) + return -1; + submodule = xstrndup(path, len); + refs = get_ref_cache(submodule); + free(submodule); + + retval = resolve_gitlink_ref_recursive(refs, refname, sha1, 0); + return retval; +} + +/* + * Return the ref_entry for the given refname from the packed + * references. If it does not exist, return NULL. + */ +static struct ref_entry *get_packed_ref(const char *refname) +{ + return find_ref(get_packed_refs(&ref_cache), refname); +} + +/* + * A loose ref file doesn't exist; check for a packed ref. The + * options are forwarded from resolve_safe_unsafe(). + */ +static int resolve_missing_loose_ref(const char *refname, + int resolve_flags, + unsigned char *sha1, + int *flags) +{ + struct ref_entry *entry; + + /* + * The loose reference file does not exist; check for a packed + * reference. + */ + entry = get_packed_ref(refname); + if (entry) { + hashcpy(sha1, entry->u.value.oid.hash); + if (flags) + *flags |= REF_ISPACKED; + return 0; + } + /* The reference is not a packed reference, either. */ + if (resolve_flags & RESOLVE_REF_READING) { + errno = ENOENT; + return -1; + } else { + hashclr(sha1); + return 0; + } +} + +/* This function needs to return a meaningful errno on failure */ +static const char *resolve_ref_1(const char *refname, + int resolve_flags, + unsigned char *sha1, + int *flags, + struct strbuf *sb_refname, + struct strbuf *sb_path, + struct strbuf *sb_contents) +{ + int depth = MAXDEPTH; + int bad_name = 0; + + if (flags) + *flags = 0; + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + if (flags) + *flags |= REF_BAD_NAME; + + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(refname)) { + errno = EINVAL; + return NULL; + } + /* + * dwim_ref() uses REF_ISBROKEN to distinguish between + * missing refs and refs that were present but invalid, + * to complain about the latter to stderr. + * + * We don't know whether the ref exists, so don't set + * REF_ISBROKEN yet. + */ + bad_name = 1; + } + for (;;) { + const char *path; + struct stat st; + char *buf; + int fd; + + if (--depth < 0) { + errno = ELOOP; + return NULL; + } + + strbuf_reset(sb_path); + strbuf_git_path(sb_path, "%s", refname); + path = sb_path->buf; + + /* + * We might have to loop back here to avoid a race + * condition: first we lstat() the file, then we try + * to read it as a link or as a file. But if somebody + * changes the type of the file (file <-> directory + * <-> symlink) between the lstat() and reading, then + * we don't want to report that as an error but rather + * try again starting with the lstat(). + */ + stat_ref: + if (lstat(path, &st) < 0) { + if (errno != ENOENT) + return NULL; + if (resolve_missing_loose_ref(refname, resolve_flags, + sha1, flags)) + return NULL; + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } + return refname; + } + + /* Follow "normalized" - ie "refs/.." symlinks by hand */ + if (S_ISLNK(st.st_mode)) { + strbuf_reset(sb_contents); + if (strbuf_readlink(sb_contents, path, 0) < 0) { + if (errno == ENOENT || errno == EINVAL) + /* inconsistent with lstat; retry */ + goto stat_ref; + else + return NULL; + } + if (starts_with(sb_contents->buf, "refs/") && + !check_refname_format(sb_contents->buf, 0)) { + strbuf_swap(sb_refname, sb_contents); + refname = sb_refname->buf; + if (flags) + *flags |= REF_ISSYMREF; + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } + continue; + } + } + + /* Is it a directory? */ + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return NULL; + } + + /* + * Anything else, just open it and try to use it as + * a ref + */ + fd = open(path, O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + /* inconsistent with lstat; retry */ + goto stat_ref; + else + return NULL; + } + strbuf_reset(sb_contents); + if (strbuf_read(sb_contents, fd, 256) < 0) { + int save_errno = errno; + close(fd); + errno = save_errno; + return NULL; + } + close(fd); + strbuf_rtrim(sb_contents); + + /* + * Is it a symbolic ref? + */ + if (!starts_with(sb_contents->buf, "ref:")) { + /* + * Please note that FETCH_HEAD has a second + * line containing other data. + */ + if (get_sha1_hex(sb_contents->buf, sha1) || + (sb_contents->buf[40] != '\0' && !isspace(sb_contents->buf[40]))) { + if (flags) + *flags |= REF_ISBROKEN; + errno = EINVAL; + return NULL; + } + if (bad_name) { + hashclr(sha1); + if (flags) + *flags |= REF_ISBROKEN; + } + return refname; + } + if (flags) + *flags |= REF_ISSYMREF; + buf = sb_contents->buf + 4; + while (isspace(*buf)) + buf++; + strbuf_reset(sb_refname); + strbuf_addstr(sb_refname, buf); + refname = sb_refname->buf; + if (resolve_flags & RESOLVE_REF_NO_RECURSE) { + hashclr(sha1); + return refname; + } + if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) { + if (flags) + *flags |= REF_ISBROKEN; + + if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || + !refname_is_safe(buf)) { + errno = EINVAL; + return NULL; + } + bad_name = 1; + } + } +} + +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags) +{ + static struct strbuf sb_refname = STRBUF_INIT; + struct strbuf sb_contents = STRBUF_INIT; + struct strbuf sb_path = STRBUF_INIT; + const char *ret; + + ret = resolve_ref_1(refname, resolve_flags, sha1, flags, + &sb_refname, &sb_path, &sb_contents); + strbuf_release(&sb_path); + strbuf_release(&sb_contents); + return ret; +} + +/* + * Peel the entry (if possible) and return its new peel_status. If + * repeel is true, re-peel the entry even if there is an old peeled + * value that is already stored in it. + * + * It is OK to call this function with a packed reference entry that + * might be stale and might even refer to an object that has since + * been garbage-collected. In such a case, if the entry has + * REF_KNOWS_PEELED then leave the status unchanged and return + * PEEL_PEELED or PEEL_NON_TAG; otherwise, return PEEL_INVALID. + */ +static enum peel_status peel_entry(struct ref_entry *entry, int repeel) +{ + enum peel_status status; + + if (entry->flag & REF_KNOWS_PEELED) { + if (repeel) { + entry->flag &= ~REF_KNOWS_PEELED; + oidclr(&entry->u.value.peeled); + } else { + return is_null_oid(&entry->u.value.peeled) ? + PEEL_NON_TAG : PEEL_PEELED; + } + } + if (entry->flag & REF_ISBROKEN) + return PEEL_BROKEN; + if (entry->flag & REF_ISSYMREF) + return PEEL_IS_SYMREF; + + status = peel_object(entry->u.value.oid.hash, entry->u.value.peeled.hash); + if (status == PEEL_PEELED || status == PEEL_NON_TAG) + entry->flag |= REF_KNOWS_PEELED; + return status; +} + +int peel_ref(const char *refname, unsigned char *sha1) +{ + int flag; + unsigned char base[20]; + + if (current_ref && (current_ref->name == refname + || !strcmp(current_ref->name, refname))) { + if (peel_entry(current_ref, 0)) + return -1; + hashcpy(sha1, current_ref->u.value.peeled.hash); + return 0; + } + + if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag)) + return -1; + + /* + * If the reference is packed, read its ref_entry from the + * cache in the hope that we already know its peeled value. + * We only try this optimization on packed references because + * (a) forcing the filling of the loose reference cache could + * be expensive and (b) loose references anyway usually do not + * have REF_KNOWS_PEELED. + */ + if (flag & REF_ISPACKED) { + struct ref_entry *r = get_packed_ref(refname); + if (r) { + if (peel_entry(r, 0)) + return -1; + hashcpy(sha1, r->u.value.peeled.hash); + return 0; + } + } + + return peel_object(base, sha1); +} + +/* + * Call fn for each reference in the specified ref_cache, omitting + * references not in the containing_dir of base. fn is called for all + * references, including broken ones. If fn ever returns a non-zero + * value, stop the iteration and return that value; otherwise, return + * 0. + */ +static int do_for_each_entry(struct ref_cache *refs, const char *base, + each_ref_entry_fn fn, void *cb_data) +{ + struct packed_ref_cache *packed_ref_cache; + struct ref_dir *loose_dir; + struct ref_dir *packed_dir; + int retval = 0; + + /* + * We must make sure that all loose refs are read before accessing the + * packed-refs file; this avoids a race condition in which loose refs + * are migrated to the packed-refs file by a simultaneous process, but + * our in-memory view is from before the migration. get_packed_ref_cache() + * takes care of making sure our view is up to date with what is on + * disk. + */ + loose_dir = get_loose_refs(refs); + if (base && *base) { + loose_dir = find_containing_dir(loose_dir, base, 0); + } + if (loose_dir) + prime_ref_dir(loose_dir); + + packed_ref_cache = get_packed_ref_cache(refs); + acquire_packed_ref_cache(packed_ref_cache); + packed_dir = get_packed_ref_dir(packed_ref_cache); + if (base && *base) { + packed_dir = find_containing_dir(packed_dir, base, 0); + } + + if (packed_dir && loose_dir) { + sort_ref_dir(packed_dir); + sort_ref_dir(loose_dir); + retval = do_for_each_entry_in_dirs( + packed_dir, loose_dir, fn, cb_data); + } else if (packed_dir) { + sort_ref_dir(packed_dir); + retval = do_for_each_entry_in_dir( + packed_dir, 0, fn, cb_data); + } else if (loose_dir) { + sort_ref_dir(loose_dir); + retval = do_for_each_entry_in_dir( + loose_dir, 0, fn, cb_data); + } + + release_packed_ref_cache(packed_ref_cache); + return retval; +} + +/* + * Call fn for each reference in the specified ref_cache for which the + * refname begins with base. If trim is non-zero, then trim that many + * characters off the beginning of each refname before passing the + * refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to include + * broken references in the iteration. If fn ever returns a non-zero + * value, stop the iteration and return that value; otherwise, return + * 0. + */ +static int do_for_each_ref(struct ref_cache *refs, const char *base, + each_ref_fn fn, int trim, int flags, void *cb_data) +{ + struct ref_entry_cb data; + data.base = base; + data.trim = trim; + data.flags = flags; + data.fn = fn; + data.cb_data = cb_data; + + if (ref_paranoia < 0) + ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 0); + if (ref_paranoia) + data.flags |= DO_FOR_EACH_INCLUDE_BROKEN; + + return do_for_each_entry(refs, base, do_one_ref, &data); +} + +static int do_head_ref(const char *submodule, each_ref_fn fn, void *cb_data) +{ + struct object_id oid; + int flag; + + if (submodule) { + if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0) + return fn("HEAD", &oid, 0, cb_data); + + return 0; + } + + if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag)) + return fn("HEAD", &oid, flag, cb_data); + + return 0; +} + +int head_ref(each_ref_fn fn, void *cb_data) +{ + return do_head_ref(NULL, fn, cb_data); +} + +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_head_ref(submodule, fn, cb_data); +} + +int for_each_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(&ref_cache, "", fn, 0, 0, cb_data); +} + +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(get_ref_cache(submodule), "", fn, 0, 0, cb_data); +} + +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(&ref_cache, prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken) +{ + unsigned int flag = 0; + + if (broken) + flag = DO_FOR_EACH_INCLUDE_BROKEN; + return do_for_each_ref(&ref_cache, prefix, fn, 0, flag, cb_data); +} + +int for_each_ref_in_submodule(const char *submodule, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(get_ref_cache(submodule), prefix, fn, strlen(prefix), 0, cb_data); +} + +int for_each_replace_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(&ref_cache, git_replace_ref_base, fn, + strlen(git_replace_ref_base), 0, cb_data); +} + +int for_each_namespaced_ref(each_ref_fn fn, void *cb_data) +{ + struct strbuf buf = STRBUF_INIT; + int ret; + strbuf_addf(&buf, "%srefs/", get_git_namespace()); + ret = do_for_each_ref(&ref_cache, buf.buf, fn, 0, 0, cb_data); + strbuf_release(&buf); + return ret; +} + +int for_each_rawref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(&ref_cache, "", fn, 0, + DO_FOR_EACH_INCLUDE_BROKEN, cb_data); +} + +static void unlock_ref(struct ref_lock *lock) +{ + /* Do not free lock->lk -- atexit() still looks at them */ + if (lock->lk) + rollback_lock_file(lock->lk); + free(lock->ref_name); + free(lock->orig_ref_name); + free(lock); +} + +/* + * Verify that the reference locked by lock has the value old_sha1. + * Fail if the reference doesn't exist and mustexist is set. Return 0 + * on success. On error, write an error message to err, set errno, and + * return a negative value. + */ +static int verify_lock(struct ref_lock *lock, + const unsigned char *old_sha1, int mustexist, + struct strbuf *err) +{ + assert(err); + + if (read_ref_full(lock->ref_name, + mustexist ? RESOLVE_REF_READING : 0, + lock->old_oid.hash, NULL)) { + int save_errno = errno; + strbuf_addf(err, "can't verify ref %s", lock->ref_name); + errno = save_errno; + return -1; + } + if (hashcmp(lock->old_oid.hash, old_sha1)) { + strbuf_addf(err, "ref %s is at %s but expected %s", + lock->ref_name, + sha1_to_hex(lock->old_oid.hash), + sha1_to_hex(old_sha1)); + errno = EBUSY; + return -1; + } + return 0; +} + +static int remove_empty_directories(struct strbuf *path) +{ + /* + * we want to create a file but there is a directory there; + * if that is an empty directory (or a directory that contains + * only empty directories), remove them. + */ + return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY); +} + +/* + * Locks a ref returning the lock on success and NULL on failure. + * On failure errno is set to something meaningful. + */ +static struct ref_lock *lock_ref_sha1_basic(const char *refname, + const unsigned char *old_sha1, + const struct string_list *extras, + const struct string_list *skip, + unsigned int flags, int *type_p, + struct strbuf *err) +{ + struct strbuf ref_file = STRBUF_INIT; + struct strbuf orig_ref_file = STRBUF_INIT; + const char *orig_refname = refname; + struct ref_lock *lock; + int last_errno = 0; + int type, lflags; + int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); + int resolve_flags = 0; + int attempts_remaining = 3; + + assert(err); + + lock = xcalloc(1, sizeof(struct ref_lock)); + + if (mustexist) + resolve_flags |= RESOLVE_REF_READING; + if (flags & REF_DELETING) { + resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; + if (flags & REF_NODEREF) + resolve_flags |= RESOLVE_REF_NO_RECURSE; + } + + refname = resolve_ref_unsafe(refname, resolve_flags, + lock->old_oid.hash, &type); + if (!refname && errno == EISDIR) { + /* + * we are trying to lock foo but we used to + * have foo/bar which now does not exist; + * it is normal for the empty directory 'foo' + * to remain. + */ + strbuf_git_path(&orig_ref_file, "%s", orig_refname); + if (remove_empty_directories(&orig_ref_file)) { + last_errno = errno; + if (!verify_refname_available_dir(orig_refname, extras, skip, + get_loose_refs(&ref_cache), err)) + strbuf_addf(err, "there are still refs under '%s'", + orig_refname); + goto error_return; + } + refname = resolve_ref_unsafe(orig_refname, resolve_flags, + lock->old_oid.hash, &type); + } + if (type_p) + *type_p = type; + if (!refname) { + last_errno = errno; + if (last_errno != ENOTDIR || + !verify_refname_available_dir(orig_refname, extras, skip, + get_loose_refs(&ref_cache), err)) + strbuf_addf(err, "unable to resolve reference %s: %s", + orig_refname, strerror(last_errno)); + + goto error_return; + } + /* + * If the ref did not exist and we are creating it, make sure + * there is no existing packed ref whose name begins with our + * refname, nor a packed ref whose name is a proper prefix of + * our refname. + */ + if (is_null_oid(&lock->old_oid) && + verify_refname_available_dir(refname, extras, skip, + get_packed_refs(&ref_cache), err)) { + last_errno = ENOTDIR; + goto error_return; + } + + lock->lk = xcalloc(1, sizeof(struct lock_file)); + + lflags = 0; + if (flags & REF_NODEREF) { + refname = orig_refname; + lflags |= LOCK_NO_DEREF; + } + lock->ref_name = xstrdup(refname); + lock->orig_ref_name = xstrdup(orig_refname); + strbuf_git_path(&ref_file, "%s", refname); + + retry: + switch (safe_create_leading_directories_const(ref_file.buf)) { + case SCLD_OK: + break; /* success */ + case SCLD_VANISHED: + if (--attempts_remaining > 0) + goto retry; + /* fall through */ + default: + last_errno = errno; + strbuf_addf(err, "unable to create directory for %s", + ref_file.buf); + goto error_return; + } + + if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) { + last_errno = errno; + if (errno == ENOENT && --attempts_remaining > 0) + /* + * Maybe somebody just deleted one of the + * directories leading to ref_file. Try + * again: + */ + goto retry; + else { + unable_to_lock_message(ref_file.buf, errno, err); + goto error_return; + } + } + if (old_sha1 && verify_lock(lock, old_sha1, mustexist, err)) { + last_errno = errno; + goto error_return; + } + goto out; + + error_return: + unlock_ref(lock); + lock = NULL; + + out: + strbuf_release(&ref_file); + strbuf_release(&orig_ref_file); + errno = last_errno; + return lock; +} + +/* + * Write an entry to the packed-refs file for the specified refname. + * If peeled is non-NULL, write it as the entry's peeled value. + */ +static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1, + unsigned char *peeled) +{ + fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname); + if (peeled) + fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled)); +} + +/* + * An each_ref_entry_fn that writes the entry to a packed-refs file. + */ +static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data) +{ + enum peel_status peel_status = peel_entry(entry, 0); + + if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG) + error("internal error: %s is not a valid packed reference!", + entry->name); + write_packed_entry(cb_data, entry->name, entry->u.value.oid.hash, + peel_status == PEEL_PEELED ? + entry->u.value.peeled.hash : NULL); + return 0; +} + +/* + * Lock the packed-refs file for writing. Flags is passed to + * hold_lock_file_for_update(). Return 0 on success. On errors, set + * errno appropriately and return a nonzero value. + */ +static int lock_packed_refs(int flags) +{ + static int timeout_configured = 0; + static int timeout_value = 1000; + + struct packed_ref_cache *packed_ref_cache; + + if (!timeout_configured) { + git_config_get_int("core.packedrefstimeout", &timeout_value); + timeout_configured = 1; + } + + if (hold_lock_file_for_update_timeout( + &packlock, git_path("packed-refs"), + flags, timeout_value) < 0) + return -1; + /* + * Get the current packed-refs while holding the lock. If the + * packed-refs file has been modified since we last read it, + * this will automatically invalidate the cache and re-read + * the packed-refs file. + */ + packed_ref_cache = get_packed_ref_cache(&ref_cache); + packed_ref_cache->lock = &packlock; + /* Increment the reference count to prevent it from being freed: */ + acquire_packed_ref_cache(packed_ref_cache); + return 0; +} + +/* + * Write the current version of the packed refs cache from memory to + * disk. The packed-refs file must already be locked for writing (see + * lock_packed_refs()). Return zero on success. On errors, set errno + * and return a nonzero value + */ +static int commit_packed_refs(void) +{ + struct packed_ref_cache *packed_ref_cache = + get_packed_ref_cache(&ref_cache); + int error = 0; + int save_errno = 0; + FILE *out; + + if (!packed_ref_cache->lock) + die("internal error: packed-refs not locked"); + + out = fdopen_lock_file(packed_ref_cache->lock, "w"); + if (!out) + die_errno("unable to fdopen packed-refs descriptor"); + + fprintf_or_die(out, "%s", PACKED_REFS_HEADER); + do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache), + 0, write_packed_entry_fn, out); + + if (commit_lock_file(packed_ref_cache->lock)) { + save_errno = errno; + error = -1; + } + packed_ref_cache->lock = NULL; + release_packed_ref_cache(packed_ref_cache); + errno = save_errno; + return error; +} + +/* + * Rollback the lockfile for the packed-refs file, and discard the + * in-memory packed reference cache. (The packed-refs file will be + * read anew if it is needed again after this function is called.) + */ +static void rollback_packed_refs(void) +{ + struct packed_ref_cache *packed_ref_cache = + get_packed_ref_cache(&ref_cache); + + if (!packed_ref_cache->lock) + die("internal error: packed-refs not locked"); + rollback_lock_file(packed_ref_cache->lock); + packed_ref_cache->lock = NULL; + release_packed_ref_cache(packed_ref_cache); + clear_packed_ref_cache(&ref_cache); +} + +struct ref_to_prune { + struct ref_to_prune *next; + unsigned char sha1[20]; + char name[FLEX_ARRAY]; +}; + +struct pack_refs_cb_data { + unsigned int flags; + struct ref_dir *packed_refs; + struct ref_to_prune *ref_to_prune; +}; + +/* + * An each_ref_entry_fn that is run over loose references only. If + * the loose reference can be packed, add an entry in the packed ref + * cache. If the reference should be pruned, also add it to + * ref_to_prune in the pack_refs_cb_data. + */ +static int pack_if_possible_fn(struct ref_entry *entry, void *cb_data) +{ + struct pack_refs_cb_data *cb = cb_data; + enum peel_status peel_status; + struct ref_entry *packed_entry; + int is_tag_ref = starts_with(entry->name, "refs/tags/"); + + /* Do not pack per-worktree refs: */ + if (ref_type(entry->name) != REF_TYPE_NORMAL) + return 0; + + /* ALWAYS pack tags */ + if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref) + return 0; + + /* Do not pack symbolic or broken refs: */ + if ((entry->flag & REF_ISSYMREF) || !ref_resolves_to_object(entry)) + return 0; + + /* Add a packed ref cache entry equivalent to the loose entry. */ + peel_status = peel_entry(entry, 1); + if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG) + die("internal error peeling reference %s (%s)", + entry->name, oid_to_hex(&entry->u.value.oid)); + packed_entry = find_ref(cb->packed_refs, entry->name); + if (packed_entry) { + /* Overwrite existing packed entry with info from loose entry */ + packed_entry->flag = REF_ISPACKED | REF_KNOWS_PEELED; + oidcpy(&packed_entry->u.value.oid, &entry->u.value.oid); + } else { + packed_entry = create_ref_entry(entry->name, entry->u.value.oid.hash, + REF_ISPACKED | REF_KNOWS_PEELED, 0); + add_ref(cb->packed_refs, packed_entry); + } + oidcpy(&packed_entry->u.value.peeled, &entry->u.value.peeled); + + /* Schedule the loose reference for pruning if requested. */ + if ((cb->flags & PACK_REFS_PRUNE)) { + int namelen = strlen(entry->name) + 1; + struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen); + hashcpy(n->sha1, entry->u.value.oid.hash); + memcpy(n->name, entry->name, namelen); /* includes NUL */ + n->next = cb->ref_to_prune; + cb->ref_to_prune = n; + } + return 0; +} + +/* + * Remove empty parents, but spare refs/ and immediate subdirs. + * Note: munges *name. + */ +static void try_remove_empty_parents(char *name) +{ + char *p, *q; + int i; + p = name; + for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */ + while (*p && *p != '/') + p++; + /* tolerate duplicate slashes; see check_refname_format() */ + while (*p == '/') + p++; + } + for (q = p; *q; q++) + ; + while (1) { + while (q > p && *q != '/') + q--; + while (q > p && *(q-1) == '/') + q--; + if (q == p) + break; + *q = '\0'; + if (rmdir(git_path("%s", name))) + break; + } +} + +/* make sure nobody touched the ref, and unlink */ +static void prune_ref(struct ref_to_prune *r) +{ + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + + if (check_refname_format(r->name, 0)) + return; + + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_delete(transaction, r->name, r->sha1, + REF_ISPRUNING, NULL, &err) || + ref_transaction_commit(transaction, &err)) { + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return; + } + ref_transaction_free(transaction); + strbuf_release(&err); + try_remove_empty_parents(r->name); +} + +static void prune_refs(struct ref_to_prune *r) +{ + while (r) { + prune_ref(r); + r = r->next; + } +} + +int pack_refs(unsigned int flags) +{ + struct pack_refs_cb_data cbdata; + + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.flags = flags; + + lock_packed_refs(LOCK_DIE_ON_ERROR); + cbdata.packed_refs = get_packed_refs(&ref_cache); + + do_for_each_entry_in_dir(get_loose_refs(&ref_cache), 0, + pack_if_possible_fn, &cbdata); + + if (commit_packed_refs()) + die_errno("unable to overwrite old ref-pack file"); + + prune_refs(cbdata.ref_to_prune); + return 0; +} + +/* + * Rewrite the packed-refs file, omitting any refs listed in + * 'refnames'. On error, leave packed-refs unchanged, write an error + * message to 'err', and return a nonzero value. + * + * The refs in 'refnames' needn't be sorted. `err` must not be NULL. + */ +static int repack_without_refs(struct string_list *refnames, struct strbuf *err) +{ + struct ref_dir *packed; + struct string_list_item *refname; + int ret, needs_repacking = 0, removed = 0; + + assert(err); + + /* Look for a packed ref */ + for_each_string_list_item(refname, refnames) { + if (get_packed_ref(refname->string)) { + needs_repacking = 1; + break; + } + } + + /* Avoid locking if we have nothing to do */ + if (!needs_repacking) + return 0; /* no refname exists in packed refs */ + + if (lock_packed_refs(0)) { + unable_to_lock_message(git_path("packed-refs"), errno, err); + return -1; + } + packed = get_packed_refs(&ref_cache); + + /* Remove refnames from the cache */ + for_each_string_list_item(refname, refnames) + if (remove_entry(packed, refname->string) != -1) + removed = 1; + if (!removed) { + /* + * All packed entries disappeared while we were + * acquiring the lock. + */ + rollback_packed_refs(); + return 0; + } + + /* Write what remains */ + ret = commit_packed_refs(); + if (ret) + strbuf_addf(err, "unable to overwrite old ref-pack file: %s", + strerror(errno)); + return ret; +} + +static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) +{ + assert(err); + + if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) { + /* + * loose. The loose file name is the same as the + * lockfile name, minus ".lock": + */ + char *loose_filename = get_locked_file_path(lock->lk); + int res = unlink_or_msg(loose_filename, err); + free(loose_filename); + if (res) + return 1; + } + return 0; +} + +int delete_refs(struct string_list *refnames) +{ + struct strbuf err = STRBUF_INIT; + int i, result = 0; + + if (!refnames->nr) + return 0; + + result = repack_without_refs(refnames, &err); + if (result) { + /* + * If we failed to rewrite the packed-refs file, then + * it is unsafe to try to remove loose refs, because + * doing so might expose an obsolete packed value for + * a reference that might even point at an object that + * has been garbage collected. + */ + if (refnames->nr == 1) + error(_("could not delete reference %s: %s"), + refnames->items[0].string, err.buf); + else + error(_("could not delete references: %s"), err.buf); + + goto out; + } + + for (i = 0; i < refnames->nr; i++) { + const char *refname = refnames->items[i].string; + + if (delete_ref(refname, NULL, 0)) + result |= error(_("could not remove reference %s"), refname); + } + +out: + strbuf_release(&err); + return result; +} + +/* + * People using contrib's git-new-workdir have .git/logs/refs -> + * /some/other/path/.git/logs/refs, and that may live on another device. + * + * IOW, to avoid cross device rename errors, the temporary renamed log must + * live into logs/refs. + */ +#define TMP_RENAMED_LOG "logs/refs/.tmp-renamed-log" + +static int rename_tmp_log(const char *newrefname) +{ + int attempts_remaining = 4; + struct strbuf path = STRBUF_INIT; + int ret = -1; + + retry: + strbuf_reset(&path); + strbuf_git_path(&path, "logs/%s", newrefname); + switch (safe_create_leading_directories_const(path.buf)) { + case SCLD_OK: + break; /* success */ + case SCLD_VANISHED: + if (--attempts_remaining > 0) + goto retry; + /* fall through */ + default: + error("unable to create directory for %s", newrefname); + goto out; + } + + if (rename(git_path(TMP_RENAMED_LOG), path.buf)) { + if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) { + /* + * rename(a, b) when b is an existing + * directory ought to result in ISDIR, but + * Solaris 5.8 gives ENOTDIR. Sheesh. + */ + if (remove_empty_directories(&path)) { + error("Directory not empty: logs/%s", newrefname); + goto out; + } + goto retry; + } else if (errno == ENOENT && --attempts_remaining > 0) { + /* + * Maybe another process just deleted one of + * the directories in the path to newrefname. + * Try again from the beginning. + */ + goto retry; + } else { + error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s", + newrefname, strerror(errno)); + goto out; + } + } + ret = 0; +out: + strbuf_release(&path); + return ret; +} + +int verify_refname_available(const char *newname, + struct string_list *extras, + struct string_list *skip, + struct strbuf *err) +{ + struct ref_dir *packed_refs = get_packed_refs(&ref_cache); + struct ref_dir *loose_refs = get_loose_refs(&ref_cache); + + if (verify_refname_available_dir(newname, extras, skip, + packed_refs, err) || + verify_refname_available_dir(newname, extras, skip, + loose_refs, err)) + return -1; + + return 0; +} + +static int write_ref_to_lockfile(struct ref_lock *lock, + const unsigned char *sha1, struct strbuf *err); +static int commit_ref_update(struct ref_lock *lock, + const unsigned char *sha1, const char *logmsg, + int flags, struct strbuf *err); + +int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg) +{ + unsigned char sha1[20], orig_sha1[20]; + int flag = 0, logmoved = 0; + struct ref_lock *lock; + struct stat loginfo; + int log = !lstat(git_path("logs/%s", oldrefname), &loginfo); + const char *symref = NULL; + struct strbuf err = STRBUF_INIT; + + if (log && S_ISLNK(loginfo.st_mode)) + return error("reflog for %s is a symlink", oldrefname); + + symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING, + orig_sha1, &flag); + if (flag & REF_ISSYMREF) + return error("refname %s is a symbolic ref, renaming it is not supported", + oldrefname); + if (!symref) + return error("refname %s not found", oldrefname); + + if (!rename_ref_available(oldrefname, newrefname)) + return 1; + + if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG))) + return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s", + oldrefname, strerror(errno)); + + if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) { + error("unable to delete old %s", oldrefname); + goto rollback; + } + + if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) && + delete_ref(newrefname, sha1, REF_NODEREF)) { + if (errno==EISDIR) { + struct strbuf path = STRBUF_INIT; + int result; + + strbuf_git_path(&path, "%s", newrefname); + result = remove_empty_directories(&path); + strbuf_release(&path); + + if (result) { + error("Directory not empty: %s", newrefname); + goto rollback; + } + } else { + error("unable to delete existing %s", newrefname); + goto rollback; + } + } + + if (log && rename_tmp_log(newrefname)) + goto rollback; + + logmoved = log; + + lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL, &err); + if (!lock) { + error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf); + strbuf_release(&err); + goto rollback; + } + hashcpy(lock->old_oid.hash, orig_sha1); + + if (write_ref_to_lockfile(lock, orig_sha1, &err) || + commit_ref_update(lock, orig_sha1, logmsg, 0, &err)) { + error("unable to write current sha1 into %s: %s", newrefname, err.buf); + strbuf_release(&err); + goto rollback; + } + + return 0; + + rollback: + lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL, &err); + if (!lock) { + error("unable to lock %s for rollback: %s", oldrefname, err.buf); + strbuf_release(&err); + goto rollbacklog; + } + + flag = log_all_ref_updates; + log_all_ref_updates = 0; + if (write_ref_to_lockfile(lock, orig_sha1, &err) || + commit_ref_update(lock, orig_sha1, NULL, 0, &err)) { + error("unable to write current sha1 into %s: %s", oldrefname, err.buf); + strbuf_release(&err); + } + log_all_ref_updates = flag; + + rollbacklog: + if (logmoved && rename(git_path("logs/%s", newrefname), git_path("logs/%s", oldrefname))) + error("unable to restore logfile %s from %s: %s", + oldrefname, newrefname, strerror(errno)); + if (!logmoved && log && + rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", oldrefname))) + error("unable to restore logfile %s from "TMP_RENAMED_LOG": %s", + oldrefname, strerror(errno)); + + return 1; +} + +static int close_ref(struct ref_lock *lock) +{ + if (close_lock_file(lock->lk)) + return -1; + return 0; +} + +static int commit_ref(struct ref_lock *lock) +{ + if (commit_lock_file(lock->lk)) + return -1; + return 0; +} + +/* + * Create a reflog for a ref. If force_create = 0, the reflog will + * only be created for certain refs (those for which + * should_autocreate_reflog returns non-zero. Otherwise, create it + * regardless of the ref name. Fill in *err and return -1 on failure. + */ +static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create) +{ + int logfd, oflags = O_APPEND | O_WRONLY; + + strbuf_git_path(logfile, "logs/%s", refname); + if (force_create || should_autocreate_reflog(refname)) { + if (safe_create_leading_directories(logfile->buf) < 0) { + strbuf_addf(err, "unable to create directory for %s: " + "%s", logfile->buf, strerror(errno)); + return -1; + } + oflags |= O_CREAT; + } + + logfd = open(logfile->buf, oflags, 0666); + if (logfd < 0) { + if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR)) + return 0; + + if (errno == EISDIR) { + if (remove_empty_directories(logfile)) { + strbuf_addf(err, "There are still logs under " + "'%s'", logfile->buf); + return -1; + } + logfd = open(logfile->buf, oflags, 0666); + } + + if (logfd < 0) { + strbuf_addf(err, "unable to append to %s: %s", + logfile->buf, strerror(errno)); + return -1; + } + } + + adjust_shared_perm(logfile->buf); + close(logfd); + return 0; +} + + +int safe_create_reflog(const char *refname, int force_create, struct strbuf *err) +{ + int ret; + struct strbuf sb = STRBUF_INIT; + + ret = log_ref_setup(refname, &sb, err, force_create); + strbuf_release(&sb); + return ret; +} + +static int log_ref_write_fd(int fd, const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *committer, const char *msg) +{ + int msglen, written; + unsigned maxlen, len; + char *logrec; + + msglen = msg ? strlen(msg) : 0; + maxlen = strlen(committer) + msglen + 100; + logrec = xmalloc(maxlen); + len = xsnprintf(logrec, maxlen, "%s %s %s\n", + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), + committer); + if (msglen) + len += copy_reflog_msg(logrec + len - 1, msg) - 1; + + written = len <= maxlen ? write_in_full(fd, logrec, len) : -1; + free(logrec); + if (written != len) + return -1; + + return 0; +} + +static int log_ref_write_1(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + struct strbuf *logfile, int flags, + struct strbuf *err) +{ + int logfd, result, oflags = O_APPEND | O_WRONLY; + + if (log_all_ref_updates < 0) + log_all_ref_updates = !is_bare_repository(); + + result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG); + + if (result) + return result; + + logfd = open(logfile->buf, oflags); + if (logfd < 0) + return 0; + result = log_ref_write_fd(logfd, old_sha1, new_sha1, + git_committer_info(0), msg); + if (result) { + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strerror(errno)); + close(logfd); + return -1; + } + if (close(logfd)) { + strbuf_addf(err, "unable to append to %s: %s", logfile->buf, + strerror(errno)); + return -1; + } + return 0; +} + +static int log_ref_write(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err) +{ + return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags, + err); +} + +int files_log_ref_write(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err) +{ + struct strbuf sb = STRBUF_INIT; + int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags, + err); + strbuf_release(&sb); + return ret; +} + +/* + * Write sha1 into the open lockfile, then close the lockfile. On + * errors, rollback the lockfile, fill in *err and + * return -1. + */ +static int write_ref_to_lockfile(struct ref_lock *lock, + const unsigned char *sha1, struct strbuf *err) +{ + static char term = '\n'; + struct object *o; + int fd; + + o = parse_object(sha1); + if (!o) { + strbuf_addf(err, + "Trying to write ref %s with nonexistent object %s", + lock->ref_name, sha1_to_hex(sha1)); + unlock_ref(lock); + return -1; + } + if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { + strbuf_addf(err, + "Trying to write non-commit object %s to branch %s", + sha1_to_hex(sha1), lock->ref_name); + unlock_ref(lock); + return -1; + } + fd = get_lock_file_fd(lock->lk); + if (write_in_full(fd, sha1_to_hex(sha1), 40) != 40 || + write_in_full(fd, &term, 1) != 1 || + close_ref(lock) < 0) { + strbuf_addf(err, + "Couldn't write %s", get_lock_file_path(lock->lk)); + unlock_ref(lock); + return -1; + } + return 0; +} + +/* + * Commit a change to a loose reference that has already been written + * to the loose reference lockfile. Also update the reflogs if + * necessary, using the specified lockmsg (which can be NULL). + */ +static int commit_ref_update(struct ref_lock *lock, + const unsigned char *sha1, const char *logmsg, + int flags, struct strbuf *err) +{ + clear_loose_ref_cache(&ref_cache); + if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0 || + (strcmp(lock->ref_name, lock->orig_ref_name) && + log_ref_write(lock->orig_ref_name, lock->old_oid.hash, sha1, logmsg, flags, err) < 0)) { + char *old_msg = strbuf_detach(err, NULL); + strbuf_addf(err, "Cannot update the ref '%s': %s", + lock->ref_name, old_msg); + free(old_msg); + unlock_ref(lock); + return -1; + } + if (strcmp(lock->orig_ref_name, "HEAD") != 0) { + /* + * Special hack: If a branch is updated directly and HEAD + * points to it (may happen on the remote side of a push + * for example) then logically the HEAD reflog should be + * updated too. + * A generic solution implies reverse symref information, + * but finding all symrefs pointing to the given branch + * would be rather costly for this rare event (the direct + * update of a branch) to be worth it. So let's cheat and + * check with HEAD only which should cover 99% of all usage + * scenarios (even 100% of the default ones). + */ + unsigned char head_sha1[20]; + int head_flag; + const char *head_ref; + head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, + head_sha1, &head_flag); + if (head_ref && (head_flag & REF_ISSYMREF) && + !strcmp(head_ref, lock->ref_name)) { + struct strbuf log_err = STRBUF_INIT; + if (log_ref_write("HEAD", lock->old_oid.hash, sha1, + logmsg, 0, &log_err)) { + error("%s", log_err.buf); + strbuf_release(&log_err); + } + } + } + if (commit_ref(lock)) { + error("Couldn't set %s", lock->ref_name); + unlock_ref(lock); + return -1; + } + + unlock_ref(lock); + return 0; +} + +int create_symref(const char *ref_target, const char *refs_heads_master, + const char *logmsg) +{ + char *lockpath = NULL; + char ref[1000]; + int fd, len, written; + char *git_HEAD = git_pathdup("%s", ref_target); + unsigned char old_sha1[20], new_sha1[20]; + struct strbuf err = STRBUF_INIT; + + if (logmsg && read_ref(ref_target, old_sha1)) + hashclr(old_sha1); + + if (safe_create_leading_directories(git_HEAD) < 0) + return error("unable to create directory for %s", git_HEAD); + +#ifndef NO_SYMLINK_HEAD + if (prefer_symlink_refs) { + unlink(git_HEAD); + if (!symlink(refs_heads_master, git_HEAD)) + goto done; + fprintf(stderr, "no symlink - falling back to symbolic ref\n"); + } +#endif + + len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master); + if (sizeof(ref) <= len) { + error("refname too long: %s", refs_heads_master); + goto error_free_return; + } + lockpath = mkpathdup("%s.lock", git_HEAD); + fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + error("Unable to open %s for writing", lockpath); + goto error_free_return; + } + written = write_in_full(fd, ref, len); + if (close(fd) != 0 || written != len) { + error("Unable to write to %s", lockpath); + goto error_unlink_return; + } + if (rename(lockpath, git_HEAD) < 0) { + error("Unable to create %s", git_HEAD); + goto error_unlink_return; + } + if (adjust_shared_perm(git_HEAD)) { + error("Unable to fix permissions on %s", lockpath); + error_unlink_return: + unlink_or_warn(lockpath); + error_free_return: + free(lockpath); + free(git_HEAD); + return -1; + } + free(lockpath); + +#ifndef NO_SYMLINK_HEAD + done: +#endif + if (logmsg && !read_ref(refs_heads_master, new_sha1) && + log_ref_write(ref_target, old_sha1, new_sha1, logmsg, 0, &err)) { + error("%s", err.buf); + strbuf_release(&err); + } + + free(git_HEAD); + return 0; +} + +int reflog_exists(const char *refname) +{ + struct stat st; + + return !lstat(git_path("logs/%s", refname), &st) && + S_ISREG(st.st_mode); +} + +int delete_reflog(const char *refname) +{ + return remove_path(git_path("logs/%s", refname)); +} + +static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) +{ + unsigned char osha1[20], nsha1[20]; + char *email_end, *message; + unsigned long timestamp; + int tz; + + /* old SP new SP name <email> SP time TAB msg LF */ + if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || + get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || + get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || + !(email_end = strchr(sb->buf + 82, '>')) || + email_end[1] != ' ' || + !(timestamp = strtoul(email_end + 2, &message, 10)) || + !message || message[0] != ' ' || + (message[1] != '+' && message[1] != '-') || + !isdigit(message[2]) || !isdigit(message[3]) || + !isdigit(message[4]) || !isdigit(message[5])) + return 0; /* corrupt? */ + email_end[1] = '\0'; + tz = strtol(message + 1, NULL, 10); + if (message[6] != '\t') + message += 6; + else + message += 7; + return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); +} + +static char *find_beginning_of_line(char *bob, char *scan) +{ + while (bob < scan && *(--scan) != '\n') + ; /* keep scanning backwards */ + /* + * Return either beginning of the buffer, or LF at the end of + * the previous line. + */ + return scan; +} + +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data) +{ + struct strbuf sb = STRBUF_INIT; + FILE *logfp; + long pos; + int ret = 0, at_tail = 1; + + logfp = fopen(git_path("logs/%s", refname), "r"); + if (!logfp) + return -1; + + /* Jump to the end */ + if (fseek(logfp, 0, SEEK_END) < 0) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + pos = ftell(logfp); + while (!ret && 0 < pos) { + int cnt; + size_t nread; + char buf[BUFSIZ]; + char *endp, *scanp; + + /* Fill next block from the end */ + cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos; + if (fseek(logfp, pos - cnt, SEEK_SET)) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + nread = fread(buf, cnt, 1, logfp); + if (nread != 1) + return error("cannot read %d bytes from reflog for %s: %s", + cnt, refname, strerror(errno)); + pos -= cnt; + + scanp = endp = buf + cnt; + if (at_tail && scanp[-1] == '\n') + /* Looking at the final LF at the end of the file */ + scanp--; + at_tail = 0; + + while (buf < scanp) { + /* + * terminating LF of the previous line, or the beginning + * of the buffer. + */ + char *bp; + + bp = find_beginning_of_line(buf, scanp); + + if (*bp == '\n') { + /* + * The newline is the end of the previous line, + * so we know we have complete line starting + * at (bp + 1). Prefix it onto any prior data + * we collected for the line and process it. + */ + strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1)); + scanp = bp; + endp = bp + 1; + ret = show_one_reflog_ent(&sb, fn, cb_data); + strbuf_reset(&sb); + if (ret) + break; + } else if (!pos) { + /* + * We are at the start of the buffer, and the + * start of the file; there is no previous + * line, and we have everything for this one. + * Process it, and we can end the loop. + */ + strbuf_splice(&sb, 0, 0, buf, endp - buf); + ret = show_one_reflog_ent(&sb, fn, cb_data); + strbuf_reset(&sb); + break; + } + + if (bp == buf) { + /* + * We are at the start of the buffer, and there + * is more file to read backwards. Which means + * we are in the middle of a line. Note that we + * may get here even if *bp was a newline; that + * just means we are at the exact end of the + * previous line, rather than some spot in the + * middle. + * + * Save away what we have to be combined with + * the data from the next read. + */ + strbuf_splice(&sb, 0, 0, buf, endp - buf); + break; + } + } + + } + if (!ret && sb.len) + die("BUG: reverse reflog parser had leftover data"); + + fclose(logfp); + strbuf_release(&sb); + return ret; +} + +int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) +{ + FILE *logfp; + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + logfp = fopen(git_path("logs/%s", refname), "r"); + if (!logfp) + return -1; + + while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) + ret = show_one_reflog_ent(&sb, fn, cb_data); + fclose(logfp); + strbuf_release(&sb); + return ret; +} +/* + * Call fn for each reflog in the namespace indicated by name. name + * must be empty or end with '/'. Name will be used as a scratch + * space, but its contents will be restored before return. + */ +static int do_for_each_reflog(struct strbuf *name, each_ref_fn fn, void *cb_data) +{ + DIR *d = opendir(git_path("logs/%s", name->buf)); + int retval = 0; + struct dirent *de; + int oldlen = name->len; + + if (!d) + return name->len ? errno : 0; + + while ((de = readdir(d)) != NULL) { + struct stat st; + + if (de->d_name[0] == '.') + continue; + if (ends_with(de->d_name, ".lock")) + continue; + strbuf_addstr(name, de->d_name); + if (stat(git_path("logs/%s", name->buf), &st) < 0) { + ; /* silently ignore */ + } else { + if (S_ISDIR(st.st_mode)) { + strbuf_addch(name, '/'); + retval = do_for_each_reflog(name, fn, cb_data); + } else { + struct object_id oid; + + if (read_ref_full(name->buf, 0, oid.hash, NULL)) + retval = error("bad ref for %s", name->buf); + else + retval = fn(name->buf, &oid, 0, cb_data); + } + if (retval) + break; + } + strbuf_setlen(name, oldlen); + } + closedir(d); + return retval; +} + +int for_each_reflog(each_ref_fn fn, void *cb_data) +{ + int retval; + struct strbuf name; + strbuf_init(&name, PATH_MAX); + retval = do_for_each_reflog(&name, fn, cb_data); + strbuf_release(&name); + return retval; +} + +static int ref_update_reject_duplicates(struct string_list *refnames, + struct strbuf *err) +{ + int i, n = refnames->nr; + + assert(err); + + for (i = 1; i < n; i++) + if (!strcmp(refnames->items[i - 1].string, refnames->items[i].string)) { + strbuf_addf(err, + "Multiple updates for ref '%s' not allowed.", + refnames->items[i].string); + return 1; + } + return 0; +} + +int ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err) +{ + int ret = 0, i; + int n = transaction->nr; + struct ref_update **updates = transaction->updates; + struct string_list refs_to_delete = STRING_LIST_INIT_NODUP; + struct string_list_item *ref_to_delete; + struct string_list affected_refnames = STRING_LIST_INIT_NODUP; + + assert(err); + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: commit called for transaction that is not open"); + + if (!n) { + transaction->state = REF_TRANSACTION_CLOSED; + return 0; + } + + /* Fail if a refname appears more than once in the transaction: */ + for (i = 0; i < n; i++) + string_list_append(&affected_refnames, updates[i]->refname); + string_list_sort(&affected_refnames); + if (ref_update_reject_duplicates(&affected_refnames, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + /* + * Acquire all locks, verify old values if provided, check + * that new values are valid, and write new values to the + * lockfiles, ready to be activated. Only keep one lockfile + * open at a time to avoid running out of file descriptors. + */ + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if ((update->flags & REF_HAVE_NEW) && + is_null_sha1(update->new_sha1)) + update->flags |= REF_DELETING; + update->lock = lock_ref_sha1_basic( + update->refname, + ((update->flags & REF_HAVE_OLD) ? + update->old_sha1 : NULL), + &affected_refnames, NULL, + update->flags, + &update->type, + err); + if (!update->lock) { + char *reason; + + ret = (errno == ENOTDIR) + ? TRANSACTION_NAME_CONFLICT + : TRANSACTION_GENERIC_ERROR; + reason = strbuf_detach(err, NULL); + strbuf_addf(err, "cannot lock ref '%s': %s", + update->refname, reason); + free(reason); + goto cleanup; + } + if ((update->flags & REF_HAVE_NEW) && + !(update->flags & REF_DELETING)) { + int overwriting_symref = ((update->type & REF_ISSYMREF) && + (update->flags & REF_NODEREF)); + + if (!overwriting_symref && + !hashcmp(update->lock->old_oid.hash, update->new_sha1)) { + /* + * The reference already has the desired + * value, so we don't need to write it. + */ + } else if (write_ref_to_lockfile(update->lock, + update->new_sha1, + err)) { + char *write_err = strbuf_detach(err, NULL); + + /* + * The lock was freed upon failure of + * write_ref_to_lockfile(): + */ + update->lock = NULL; + strbuf_addf(err, + "cannot update the ref '%s': %s", + update->refname, write_err); + free(write_err); + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } else { + update->flags |= REF_NEEDS_COMMIT; + } + } + if (!(update->flags & REF_NEEDS_COMMIT)) { + /* + * We didn't have to write anything to the lockfile. + * Close it to free up the file descriptor: + */ + if (close_ref(update->lock)) { + strbuf_addf(err, "Couldn't close %s.lock", + update->refname); + goto cleanup; + } + } + } + + /* Perform updates first so live commits remain referenced */ + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (update->flags & REF_NEEDS_COMMIT) { + if (commit_ref_update(update->lock, + update->new_sha1, update->msg, + update->flags, err)) { + /* freed by commit_ref_update(): */ + update->lock = NULL; + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } else { + /* freed by commit_ref_update(): */ + update->lock = NULL; + } + } + } + + /* Perform deletes now that updates are safely completed */ + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if (update->flags & REF_DELETING) { + if (delete_ref_loose(update->lock, update->type, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + if (!(update->flags & REF_ISPRUNING)) + string_list_append(&refs_to_delete, + update->lock->ref_name); + } + } + + if (repack_without_refs(&refs_to_delete, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + for_each_string_list_item(ref_to_delete, &refs_to_delete) + unlink_or_warn(git_path("logs/%s", ref_to_delete->string)); + clear_loose_ref_cache(&ref_cache); + +cleanup: + transaction->state = REF_TRANSACTION_CLOSED; + + for (i = 0; i < n; i++) + if (updates[i]->lock) + unlock_ref(updates[i]->lock); + string_list_clear(&refs_to_delete, 0); + string_list_clear(&affected_refnames, 0); + return ret; +} + +static int ref_present(const char *refname, + const struct object_id *oid, int flags, void *cb_data) +{ + struct string_list *affected_refnames = cb_data; + + return string_list_has_string(affected_refnames, refname); +} + +int initial_ref_transaction_commit(struct ref_transaction *transaction, + struct strbuf *err) +{ + int ret = 0, i; + int n = transaction->nr; + struct ref_update **updates = transaction->updates; + struct string_list affected_refnames = STRING_LIST_INIT_NODUP; + + assert(err); + + if (transaction->state != REF_TRANSACTION_OPEN) + die("BUG: commit called for transaction that is not open"); + + /* Fail if a refname appears more than once in the transaction: */ + for (i = 0; i < n; i++) + string_list_append(&affected_refnames, updates[i]->refname); + string_list_sort(&affected_refnames); + if (ref_update_reject_duplicates(&affected_refnames, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + /* + * It's really undefined to call this function in an active + * repository or when there are existing references: we are + * only locking and changing packed-refs, so (1) any + * simultaneous processes might try to change a reference at + * the same time we do, and (2) any existing loose versions of + * the references that we are setting would have precedence + * over our values. But some remote helpers create the remote + * "HEAD" and "master" branches before calling this function, + * so here we really only check that none of the references + * that we are creating already exists. + */ + if (for_each_rawref(ref_present, &affected_refnames)) + die("BUG: initial ref transaction called with existing refs"); + + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if ((update->flags & REF_HAVE_OLD) && + !is_null_sha1(update->old_sha1)) + die("BUG: initial ref transaction with old_sha1 set"); + if (verify_refname_available(update->refname, + &affected_refnames, NULL, + err)) { + ret = TRANSACTION_NAME_CONFLICT; + goto cleanup; + } + } + + if (lock_packed_refs(0)) { + strbuf_addf(err, "unable to lock packed-refs file: %s", + strerror(errno)); + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + for (i = 0; i < n; i++) { + struct ref_update *update = updates[i]; + + if ((update->flags & REF_HAVE_NEW) && + !is_null_sha1(update->new_sha1)) + add_packed_ref(update->refname, update->new_sha1); + } + + if (commit_packed_refs()) { + strbuf_addf(err, "unable to commit packed-refs file: %s", + strerror(errno)); + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + +cleanup: + transaction->state = REF_TRANSACTION_CLOSED; + string_list_clear(&affected_refnames, 0); + return ret; +} + +struct expire_reflog_cb { + unsigned int flags; + reflog_expiry_should_prune_fn *should_prune_fn; + void *policy_cb; + FILE *newlog; + unsigned char last_kept_sha1[20]; +}; + +static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; + + if (cb->flags & EXPIRE_REFLOGS_REWRITE) + osha1 = cb->last_kept_sha1; + + if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, + message, policy_cb)) { + if (!cb->newlog) + printf("would prune %s", message); + else if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("prune %s", message); + } else { + if (cb->newlog) { + fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", + sha1_to_hex(osha1), sha1_to_hex(nsha1), + email, timestamp, tz, message); + hashcpy(cb->last_kept_sha1, nsha1); + } + if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("keep %s", message); + } + return 0; +} + +int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + static struct lock_file reflog_lock; + struct expire_reflog_cb cb; + struct ref_lock *lock; + char *log_file; + int status = 0; + int type; + struct strbuf err = STRBUF_INIT; + + memset(&cb, 0, sizeof(cb)); + cb.flags = flags; + cb.policy_cb = policy_cb_data; + cb.should_prune_fn = should_prune_fn; + + /* + * The reflog file is locked by holding the lock on the + * reference itself, plus we might need to update the + * reference if --updateref was specified: + */ + lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type, &err); + if (!lock) { + error("cannot lock ref '%s': %s", refname, err.buf); + strbuf_release(&err); + return -1; + } + if (!reflog_exists(refname)) { + unlock_ref(lock); + return 0; + } + + log_file = git_pathdup("logs/%s", refname); + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + /* + * Even though holding $GIT_DIR/logs/$reflog.lock has + * no locking implications, we use the lock_file + * machinery here anyway because it does a lot of the + * work we need, including cleaning up if the program + * exits unexpectedly. + */ + if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) { + struct strbuf err = STRBUF_INIT; + unable_to_lock_message(log_file, errno, &err); + error("%s", err.buf); + strbuf_release(&err); + goto failure; + } + cb.newlog = fdopen_lock_file(&reflog_lock, "w"); + if (!cb.newlog) { + error("cannot fdopen %s (%s)", + get_lock_file_path(&reflog_lock), strerror(errno)); + goto failure; + } + } + + (*prepare_fn)(refname, sha1, cb.policy_cb); + for_each_reflog_ent(refname, expire_reflog_ent, &cb); + (*cleanup_fn)(cb.policy_cb); + + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + /* + * It doesn't make sense to adjust a reference pointed + * to by a symbolic ref based on expiring entries in + * the symbolic reference's reflog. Nor can we update + * a reference if there are no remaining reflog + * entries. + */ + int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && + !(type & REF_ISSYMREF) && + !is_null_sha1(cb.last_kept_sha1); + + if (close_lock_file(&reflog_lock)) { + status |= error("couldn't write %s: %s", log_file, + strerror(errno)); + } else if (update && + (write_in_full(get_lock_file_fd(lock->lk), + sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 || + close_ref(lock) < 0)) { + status |= error("couldn't write %s", + get_lock_file_path(lock->lk)); + rollback_lock_file(&reflog_lock); + } else if (commit_lock_file(&reflog_lock)) { + status |= error("unable to commit reflog '%s' (%s)", + log_file, strerror(errno)); + } else if (update && commit_ref(lock)) { + status |= error("couldn't set %s", lock->ref_name); + } + } + free(log_file); + unlock_ref(lock); + return status; + + failure: + rollback_lock_file(&reflog_lock); + free(log_file); + unlock_ref(lock); + return -1; +} diff --git a/refs/refs-internal.h b/refs/refs-internal.h new file mode 100644 index 000000000..c7dded35f --- /dev/null +++ b/refs/refs-internal.h @@ -0,0 +1,200 @@ +#ifndef REFS_REFS_INTERNAL_H +#define REFS_REFS_INTERNAL_H + +/* + * Data structures and functions for the internal use of the refs + * module. Code outside of the refs module should use only the public + * functions defined in "refs.h", and should *not* include this file. + */ + +/* + * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken + * refs (i.e., because the reference is about to be deleted anyway). + */ +#define REF_DELETING 0x02 + +/* + * Used as a flag in ref_update::flags when a loose ref is being + * pruned. + */ +#define REF_ISPRUNING 0x04 + +/* + * Used as a flag in ref_update::flags when the reference should be + * updated to new_sha1. + */ +#define REF_HAVE_NEW 0x08 + +/* + * Used as a flag in ref_update::flags when old_sha1 should be + * checked. + */ +#define REF_HAVE_OLD 0x10 + +/* + * Used as a flag in ref_update::flags when the lockfile needs to be + * committed. + */ +#define REF_NEEDS_COMMIT 0x20 + +/* + * 0x40 is REF_FORCE_CREATE_REFLOG, so skip it if you're adding a + * value to ref_update::flags + */ + +/* + * Return true iff refname is minimally safe. "Safe" here means that + * deleting a loose reference by this name will not do any damage, for + * example by causing a file that is not a reference to be deleted. + * This function does not check that the reference name is legal; for + * that, use check_refname_format(). + * + * We consider a refname that starts with "refs/" to be safe as long + * as any ".." components that it might contain do not escape "refs/". + * Names that do not start with "refs/" are considered safe iff they + * consist entirely of upper case characters and '_' (like "HEAD" and + * "MERGE_HEAD" but not "config" or "FOO/BAR"). + */ +int refname_is_safe(const char *refname); + +enum peel_status { + /* object was peeled successfully: */ + PEEL_PEELED = 0, + + /* + * object cannot be peeled because the named object (or an + * object referred to by a tag in the peel chain), does not + * exist. + */ + PEEL_INVALID = -1, + + /* object cannot be peeled because it is not a tag: */ + PEEL_NON_TAG = -2, + + /* ref_entry contains no peeled value because it is a symref: */ + PEEL_IS_SYMREF = -3, + + /* + * ref_entry cannot be peeled because it is broken (i.e., the + * symbolic reference cannot even be resolved to an object + * name): + */ + PEEL_BROKEN = -4 +}; + +/* + * Peel the named object; i.e., if the object is a tag, resolve the + * tag recursively until a non-tag is found. If successful, store the + * result to sha1 and return PEEL_PEELED. If the object is not a tag + * or is not valid, return PEEL_NON_TAG or PEEL_INVALID, respectively, + * and leave sha1 unchanged. + */ +enum peel_status peel_object(const unsigned char *name, unsigned char *sha1); + +/* + * Return 0 if a reference named refname could be created without + * conflicting with the name of an existing reference. Otherwise, + * return a negative value and write an explanation to err. If extras + * is non-NULL, it is a list of additional refnames with which refname + * is not allowed to conflict. If skip is non-NULL, ignore potential + * conflicts with refs in skip (e.g., because they are scheduled for + * deletion in the same operation). Behavior is undefined if the same + * name is listed in both extras and skip. + * + * Two reference names conflict if one of them exactly matches the + * leading components of the other; e.g., "foo/bar" conflicts with + * both "foo" and with "foo/bar/baz" but not with "foo/bar" or + * "foo/barbados". + * + * extras and skip must be sorted. + */ +int verify_refname_available(const char *newname, + struct string_list *extras, + struct string_list *skip, + struct strbuf *err); + +/* + * Copy the reflog message msg to buf, which has been allocated sufficiently + * large, while cleaning up the whitespaces. Especially, convert LF to space, + * because reflog file is one line per entry. + */ +int copy_reflog_msg(char *buf, const char *msg); + +int should_autocreate_reflog(const char *refname); + +/** + * Information needed for a single ref update. Set new_sha1 to the new + * value or to null_sha1 to delete the ref. To check the old value + * while the ref is locked, set (flags & REF_HAVE_OLD) and set + * old_sha1 to the old value, or to null_sha1 to ensure the ref does + * not exist before update. + */ +struct ref_update { + /* + * If (flags & REF_HAVE_NEW), set the reference to this value: + */ + unsigned char new_sha1[20]; + /* + * If (flags & REF_HAVE_OLD), check that the reference + * previously had this value: + */ + unsigned char old_sha1[20]; + /* + * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, + * REF_DELETING, and REF_ISPRUNING: + */ + unsigned int flags; + struct ref_lock *lock; + int type; + char *msg; + const char refname[FLEX_ARRAY]; +}; + +/* + * Transaction states. + * OPEN: The transaction is in a valid state and can accept new updates. + * An OPEN transaction can be committed. + * CLOSED: A closed transaction is no longer active and no other operations + * than free can be used on it in this state. + * A transaction can either become closed by successfully committing + * an active transaction or if there is a failure while building + * the transaction thus rendering it failed/inactive. + */ +enum ref_transaction_state { + REF_TRANSACTION_OPEN = 0, + REF_TRANSACTION_CLOSED = 1 +}; + +/* + * Data structure for holding a reference transaction, which can + * consist of checks and updates to multiple references, carried out + * as atomically as possible. This structure is opaque to callers. + */ +struct ref_transaction { + struct ref_update **updates; + size_t alloc; + size_t nr; + enum ref_transaction_state state; +}; + +int files_log_ref_write(const char *refname, const unsigned char *old_sha1, + const unsigned char *new_sha1, const char *msg, + int flags, struct strbuf *err); + +/* + * Check for entries in extras that are within the specified + * directory, where dirname is a reference directory name including + * the trailing slash (e.g., "refs/heads/foo/"). Ignore any + * conflicting references that are found in skip. If there is a + * conflicting reference, return its name. + * + * extras and skip must be sorted lists of reference names. Either one + * can be NULL, signifying the empty list. + */ +const char *find_descendant_ref(const char *dirname, + const struct string_list *extras, + const struct string_list *skip); + +int rename_ref_available(const char *oldname, const char *newname); + +#endif /* REFS_REFS_INTERNAL_H */ diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index b1673b3e8..093832fef 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -68,6 +68,13 @@ test_expect_success 'blame 1 author' ' check_count A 2 ' +test_expect_success 'blame by tag objects' ' + git tag -m "test tag" testTag && + git tag -m "test tag #2" testTag2 testTag && + check_count -h testTag A 2 && + check_count -h testTag2 A 2 +' + test_expect_success 'setup B lines' ' echo "2A quick brown fox jumps over the" >>file && echo "lazy dog" >>file && diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index 75482254a..f9ae1d780 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh @@ -6,6 +6,14 @@ # a subdirectory called "$git" TEST_NO_CREATE_REPO=NoThanks +# Some operations require multiple attempts to be successful. Define +# here the maximal retry timeout in seconds. +RETRY_TIMEOUT=60 + +# Sometimes p4d seems to hang. Terminate the p4d process automatically after +# the defined timeout in seconds. +P4D_TIMEOUT=300 + . ./test-lib.sh if ! test_have_prereq PYTHON @@ -36,6 +44,15 @@ native_path() { echo "$path" } +# On Solaris the 'date +%s' function is not supported and therefore we +# need this replacement. +# Attention: This function is not safe again against time offset updates +# at runtime (e.g. via NTP). The 'clock_gettime(CLOCK_MONOTONIC)' +# function could fix that but it is not in Python until 3.3. +time_in_seconds() { + python -c 'import time; print int(time.time())' +} + # Try to pick a unique port: guess a large number, then hope # no more than one of each test is running. # @@ -57,6 +74,15 @@ cli="$TRASH_DIRECTORY/cli" git="$TRASH_DIRECTORY/git" pidfile="$TRASH_DIRECTORY/p4d.pid" +# Sometimes "prove" seems to hang on exit because p4d is still running +cleanup() { + if test -f "$pidfile" + then + kill -9 $(cat "$pidfile") 2>/dev/null && exit 255 + fi +} +trap cleanup EXIT + # git p4 submit generates a temp file, which will # not get cleaned up if the submission fails. Don't # clutter up /tmp on the test machine. @@ -81,6 +107,19 @@ start_p4d() { # will be caught with the "kill -0" check below. i=${P4D_START_PATIENCE:-300} pid=$(cat "$pidfile") + + timeout=$(($(time_in_seconds) + $P4D_TIMEOUT)) + while true + do + if test $(time_in_seconds) -gt $timeout + then + kill -9 $pid + exit 1 + fi + sleep 1 + done & + watchdog_pid=$! + ready= while test $i -gt 0 do @@ -121,22 +160,36 @@ p4_add_user() { EOF } +retry_until_success() { + timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) + until "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout + do + sleep 1 + done +} + +retry_until_fail() { + timeout=$(($(time_in_seconds) + $RETRY_TIMEOUT)) + until ! "$@" 2>/dev/null || test $(time_in_seconds) -gt $timeout + do + sleep 1 + done +} + kill_p4d() { pid=$(cat "$pidfile") - # it had better exist for the first kill - kill $pid && - for i in 1 2 3 4 5 ; do - kill $pid >/dev/null 2>&1 || break - sleep 1 - done && + retry_until_fail kill $pid + retry_until_fail kill -9 $pid # complain if it would not die test_must_fail kill $pid >/dev/null 2>&1 && - rm -rf "$db" "$cli" "$pidfile" + rm -rf "$db" "$cli" "$pidfile" && + retry_until_fail kill -9 $watchdog_pid } cleanup_git() { - rm -rf "$git" && - mkdir "$git" + retry_until_success rm -r "$git" + test_must_fail test -d "$git" && + retry_until_success mkdir "$git" } marshal_dump() { diff --git a/t/perf/p7000-filter-branch.sh b/t/perf/p7000-filter-branch.sh new file mode 100755 index 000000000..15ee5d1d5 --- /dev/null +++ b/t/perf/p7000-filter-branch.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='performance of filter-branch' +. ./perf-lib.sh + +test_perf_default_repo +test_checkout_worktree + +test_expect_success 'mark bases for tests' ' + git tag -f tip && + git tag -f base HEAD~100 +' + +test_perf 'noop filter' ' + git checkout --detach tip && + git filter-branch -f base..HEAD +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index dc0979702..e66b7cb69 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -176,6 +176,18 @@ test_expect_success 'integer overflow in timestamps is reported' ' grep "error in commit $new.*integer overflow" out ' +test_expect_success 'commit with NUL in header' ' + git cat-file commit HEAD >basis && + sed "s/author ./author Q/" <basis | q_to_nul >commit-NUL-header && + new=$(git hash-object -t commit -w --stdin <commit-NUL-header) && + test_when_finished "remove_object $new" && + git update-ref refs/heads/bogus "$new" && + test_when_finished "git update-ref -d refs/heads/bogus" && + test_must_fail git fsck 2>out && + cat out && + grep "error in commit $new.*unterminated header: NUL at offset" out +' + test_expect_success 'malformatted tree object' ' test_when_finished "git update-ref -d refs/tags/wrong" && test_when_finished "remove_object \$T" && @@ -276,6 +288,26 @@ test_expect_success 'tag with bad tagger' ' grep "error in tag .*: invalid author/committer" out ' +test_expect_success 'tag with NUL in header' ' + sha=$(git rev-parse HEAD) && + q_to_nul >tag-NUL-header <<-EOF && + object $sha + type commit + tag contains-Q-in-header + tagger T A Gger <tagger@example.com> 1234567890 -0000 + + This is an invalid tag. + EOF + + tag=$(git hash-object --literally -t tag -w --stdin <tag-NUL-header) && + test_when_finished "remove_object $tag" && + echo $tag >.git/refs/tags/wrong && + test_when_finished "git update-ref -d refs/tags/wrong" && + test_must_fail git fsck --tags 2>out && + cat out && + grep "error in tag $tag.*unterminated header: NUL at offset" out +' + test_expect_success 'cleaned up' ' git fsck >actual 2>&1 && test_cmp empty actual diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 44f3d5fb2..89224edcc 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -100,7 +100,7 @@ test_expect_success 'push with receive.fsckobjects' ' git config receive.fsckobjects true && git config transfer.fsckobjects false ) && - test_must_fail git push --porcelain dst master:refs/heads/test >act && + test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act && test_cmp exp act ' @@ -111,8 +111,7 @@ test_expect_success 'push with transfer.fsckobjects' ' cd dst && git config transfer.fsckobjects true ) && - test_must_fail git push --porcelain dst master:refs/heads/test >act && - test_cmp exp act + test_must_fail ok=sigpipe git push --porcelain dst master:refs/heads/test >act ' cat >bogus-commit <<\EOF diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index ec22c9844..0a87e195e 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1162,15 +1162,15 @@ do mk_empty shallow && ( cd shallow && - test_must_fail git fetch ../testrepo/.git $SHA1_3 && - test_must_fail git fetch ../testrepo/.git $SHA1_1 && + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_3 && + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_1 && git --git-dir=../testrepo/.git config uploadpack.allowreachablesha1inwant true && git fetch ../testrepo/.git $SHA1_1 && git cat-file commit $SHA1_1 && test_must_fail git cat-file commit $SHA1_2 && git fetch ../testrepo/.git $SHA1_2 && git cat-file commit $SHA1_2 && - test_must_fail git fetch ../testrepo/.git $SHA1_3 + test_must_fail ok=sigpipe git fetch ../testrepo/.git $SHA1_3 ) ' done diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh index 6f9916a39..ba975bb35 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -109,23 +109,20 @@ test_expect_success 'push to URL' ' diff expected actual ' -# Test that filling pipe buffers doesn't cause failure -# Too slow to leave enabled for general use -if false -then - printf 'parent1\nrepo1\n' >expected - nr=1000 - while test $nr -lt 2000 - do - nr=$(( $nr + 1 )) - git branch b/$nr $COMMIT3 - echo "refs/heads/b/$nr $COMMIT3 refs/heads/b/$nr $_z40" >>expected - done - - test_expect_success 'push many refs' ' - git push parent1 "refs/heads/b/*:refs/heads/b/*" && - diff expected actual - ' -fi +test_expect_success 'set up many-ref tests' ' + { + nr=1000 + while test $nr -lt 2000 + do + nr=$(( $nr + 1 )) + echo "create refs/heads/b/$nr $COMMIT3" + done + } | git update-ref --stdin +' + +test_expect_success 'sigpipe does not cause pre-push hook failure' ' + echo "exit 0" | write_script "$HOOK" && + git push parent1 "refs/heads/b/*:refs/heads/b/*" +' test_done diff --git a/t/t5813-proto-disable-ssh.sh b/t/t5813-proto-disable-ssh.sh index ad877d774..a954ead8a 100755 --- a/t/t5813-proto-disable-ssh.sh +++ b/t/t5813-proto-disable-ssh.sh @@ -14,7 +14,7 @@ test_expect_success 'setup repository to clone' ' ' test_proto "host:path" ssh "remote:repo.git" -test_proto "ssh://" ssh "ssh://remote/$PWD/remote/repo.git" -test_proto "git+ssh://" ssh "git+ssh://remote/$PWD/remote/repo.git" +test_proto "ssh://" ssh "ssh://remote$PWD/remote/repo.git" +test_proto "git+ssh://" ssh "git+ssh://remote$PWD/remote/repo.git" test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 377c648e0..869e0bf07 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -418,4 +418,11 @@ test_expect_success 'filter commit message without trailing newline' ' test_cmp expect actual ' +test_expect_success 'tree-filter deals with object name vs pathname ambiguity' ' + test_when_finished "git reset --hard original" && + ambiguous=$(git rev-list -1 HEAD) && + git filter-branch --tree-filter "mv file.t $ambiguous" HEAD^.. && + git show HEAD:$ambiguous +' + test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 5b4a5ce06..3c49536e0 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1555,6 +1555,88 @@ test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' ' grep "^!someone@example\.org!$" commandline1 ' +test_dump_aliases () { + msg="$1" && shift && + filetype="$1" && shift && + printf '%s\n' "$@" >expect && + cat >.tmp-email-aliases && + + test_expect_success $PREREQ "$msg" ' + clean_fake_sendmail && rm -fr outdir && + git config --replace-all sendemail.aliasesfile \ + "$(pwd)/.tmp-email-aliases" && + git config sendemail.aliasfiletype "$filetype" && + git send-email --dump-aliases 2>errors >actual && + test_cmp expect actual + ' +} + +test_dump_aliases '--dump-aliases sendmail format' \ + 'sendmail' \ + 'abgroup' \ + 'alice' \ + 'bcgrp' \ + 'bob' \ + 'chloe' <<-\EOF + alice: Alice W Land <awol@example.com> + bob: Robert Bobbyton <bob@example.com> + chloe: chloe@example.com + abgroup: alice, bob + bcgrp: bob, chloe, Other <o@example.com> + EOF + +test_dump_aliases '--dump-aliases mutt format' \ + 'mutt' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'donald' <<-\EOF + alias alice Alice W Land <awol@example.com> + alias donald Donald C Carlton <donc@example.com> + alias bob Robert Bobbyton <bob@example.com> + alias chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases mailrc format' \ + 'mailrc' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + alias alice Alice W Land <awol@example.com> + alias eve Eve <eve@example.com> + alias bob Robert Bobbyton <bob@example.com> + alias chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases pine format' \ + 'pine' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + alice Alice W Land <awol@example.com> + eve Eve <eve@example.com> + bob Robert Bobbyton <bob@example.com> + chloe chloe@example.com + EOF + +test_dump_aliases '--dump-aliases gnus format' \ + 'gnus' \ + 'alice' \ + 'bob' \ + 'chloe' \ + 'eve' <<-\EOF + (define-mail-alias "alice" "awol@example.com") + (define-mail-alias "eve" "eve@example.com") + (define-mail-alias "bob" "bob@example.com") + (define-mail-alias "chloe" "chloe@example.com") + EOF + +test_expect_success '--dump-aliases must be used alone' ' + test_must_fail git send-email --dump-aliases --to=janice@example.com -1 refs/heads/accounting +' + test_sendmail_aliases () { msg="$1" && shift && expect="$@" && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 9984c48b5..14a938402 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -47,1077 +47,1075 @@ file5_data='an inline file. file6_data='#!/bin/sh echo "$@"' ->empty - ### ### series A ### -test_tick - test_expect_success 'empty stream succeeds' ' git fast-import </dev/null ' -cat >input <<INPUT_END -blob -mark :2 -data <<EOF -$file2_data -EOF - -blob -mark :3 -data <<END -$file3_data -END - -blob -mark :4 -data $file4_len -$file4_data -commit refs/heads/master -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -initial -COMMIT - -M 644 :2 file2 -M 644 :3 file3 -M 755 :4 file4 - -tag series-A -from :5 -data <<EOF -An annotated tag without a tagger -EOF - -tag series-A-blob -from :3 -data <<EOF -An annotated tag that annotates a blob. -EOF - -INPUT_END -test_expect_success \ - 'A: create pack from stdin' \ - 'git fast-import --export-marks=marks.out <input && - git whatchanged master' +test_expect_success 'A: create pack from stdin' ' + test_tick && + cat >input <<-INPUT_END && + blob + mark :2 + data <<EOF + $file2_data + EOF + + blob + mark :3 + data <<END + $file3_data + END + + blob + mark :4 + data $file4_len + $file4_data + commit refs/heads/master + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initial + COMMIT + + M 644 :2 file2 + M 644 :3 file3 + M 755 :4 file4 + + tag series-A + from :5 + data <<EOF + An annotated tag without a tagger + EOF + + tag series-A-blob + from :3 + data <<EOF + An annotated tag that annotates a blob. + EOF + + INPUT_END + git fast-import --export-marks=marks.out <input && + git whatchanged master +' test_expect_success 'A: verify pack' ' verify_packs ' -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -initial -EOF -test_expect_success \ - 'A: verify commit' \ - 'git cat-file commit master | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -100644 blob file2 -100644 blob file3 -100755 blob file4 -EOF -test_expect_success \ - 'A: verify tree' \ - 'git cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$file2_data" >expect -test_expect_success \ - 'A: verify file2' \ - 'git cat-file blob master:file2 >actual && test_cmp expect actual' - -echo "$file3_data" >expect -test_expect_success \ - 'A: verify file3' \ - 'git cat-file blob master:file3 >actual && test_cmp expect actual' - -printf "$file4_data" >expect -test_expect_success \ - 'A: verify file4' \ - 'git cat-file blob master:file4 >actual && test_cmp expect actual' - -cat >expect <<EOF -object $(git rev-parse refs/heads/master) -type commit -tag series-A - -An annotated tag without a tagger -EOF +test_expect_success 'A: verify commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + initial + EOF + git cat-file commit master | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify tree' ' + cat >expect <<-EOF && + 100644 blob file2 + 100644 blob file3 + 100755 blob file4 + EOF + git cat-file -p master^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file2' ' + echo "$file2_data" >expect && + git cat-file blob master:file2 >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file3' ' + echo "$file3_data" >expect && + git cat-file blob master:file3 >actual && + test_cmp expect actual +' + +test_expect_success 'A: verify file4' ' + printf "$file4_data" >expect && + git cat-file blob master:file4 >actual && + test_cmp expect actual +' + test_expect_success 'A: verify tag/series-A' ' + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master) + type commit + tag series-A + + An annotated tag without a tagger + EOF git cat-file tag tags/series-A >actual && test_cmp expect actual ' -cat >expect <<EOF -object $(git rev-parse refs/heads/master:file3) -type blob -tag series-A-blob - -An annotated tag that annotates a blob. -EOF test_expect_success 'A: verify tag/series-A-blob' ' + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master:file3) + type blob + tag series-A-blob + + An annotated tag that annotates a blob. + EOF git cat-file tag tags/series-A-blob >actual && test_cmp expect actual ' -cat >expect <<EOF -:2 `git rev-parse --verify master:file2` -:3 `git rev-parse --verify master:file3` -:4 `git rev-parse --verify master:file4` -:5 `git rev-parse --verify master^0` -EOF -test_expect_success \ - 'A: verify marks output' \ - 'test_cmp expect marks.out' +test_expect_success 'A: verify marks output' ' + cat >expect <<-EOF && + :2 `git rev-parse --verify master:file2` + :3 `git rev-parse --verify master:file3` + :4 `git rev-parse --verify master:file4` + :5 `git rev-parse --verify master^0` + EOF + test_cmp expect marks.out +' -test_expect_success \ - 'A: verify marks import' \ - 'git fast-import \ +test_expect_success 'A: verify marks import' ' + git fast-import \ --import-marks=marks.out \ --export-marks=marks.new \ </dev/null && - test_cmp expect marks.new' - -test_tick -new_blob=$(echo testing | git hash-object --stdin) -cat >input <<INPUT_END -tag series-A-blob-2 -from $(git rev-parse refs/heads/master:file3) -data <<EOF -Tag blob by sha1. -EOF - -blob -mark :6 -data <<EOF -testing -EOF - -commit refs/heads/new_blob -committer <> 0 +0000 -data 0 -M 644 :6 new_blob -#pretend we got sha1 from fast-import -ls "new_blob" - -tag series-A-blob-3 -from $new_blob -data <<EOF -Tag new_blob. -EOF -INPUT_END - -cat >expect <<EOF -object $(git rev-parse refs/heads/master:file3) -type blob -tag series-A-blob-2 - -Tag blob by sha1. -object $new_blob -type blob -tag series-A-blob-3 - -Tag new_blob. -EOF - -test_expect_success \ - 'A: tag blob by sha1' \ - 'git fast-import <input && + test_cmp expect marks.new +' + +test_expect_success 'A: tag blob by sha1' ' + test_tick && + new_blob=$(echo testing | git hash-object --stdin) && + cat >input <<-INPUT_END && + tag series-A-blob-2 + from $(git rev-parse refs/heads/master:file3) + data <<EOF + Tag blob by sha1. + EOF + + blob + mark :6 + data <<EOF + testing + EOF + + commit refs/heads/new_blob + committer <> 0 +0000 + data 0 + M 644 :6 new_blob + #pretend we got sha1 from fast-import + ls "new_blob" + + tag series-A-blob-3 + from $new_blob + data <<EOF + Tag new_blob. + EOF + INPUT_END + + cat >expect <<-EOF && + object $(git rev-parse refs/heads/master:file3) + type blob + tag series-A-blob-2 + + Tag blob by sha1. + object $new_blob + type blob + tag series-A-blob-3 + + Tag new_blob. + EOF + + git fast-import <input && git cat-file tag tags/series-A-blob-2 >actual && git cat-file tag tags/series-A-blob-3 >>actual && - test_cmp expect actual' + test_cmp expect actual +' + +test_expect_success 'A: verify marks import does not crash' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/verify--import-marks + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + recreate from :5 + COMMIT -test_tick -cat >input <<INPUT_END -commit refs/heads/verify--import-marks -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -recreate from :5 -COMMIT + from :5 + M 755 :2 copy-of-file2 -from :5 -M 755 :2 copy-of-file2 + INPUT_END -INPUT_END -test_expect_success \ - 'A: verify marks import does not crash' \ - 'git fast-import --import-marks=marks.out <input && - git whatchanged verify--import-marks' + git fast-import --import-marks=marks.out <input && + git whatchanged verify--import-marks +' test_expect_success 'A: verify pack' ' verify_packs ' -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2 -EOF -git diff-tree -M -r master verify--import-marks >actual -test_expect_success \ - 'A: verify diff' \ - 'compare_diff_raw expect actual && - test `git rev-parse --verify master:file2` \ - = `git rev-parse --verify verify--import-marks:copy-of-file2`' - -test_tick -mt=$(git hash-object --stdin < /dev/null) -: >input.blob -: >marks.exp -: >tree.exp - -cat >input.commit <<EOF -commit refs/heads/verify--dump-marks -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -test the sparse array dumping routines with exponentially growing marks -COMMIT -EOF - -i=0 -l=4 -m=6 -n=7 -while test "$i" -lt 27; do - cat >>input.blob <<EOF -blob -mark :$l -data 0 -blob -mark :$m -data 0 -blob -mark :$n -data 0 -EOF - echo "M 100644 :$l l$i" >>input.commit - echo "M 100644 :$m m$i" >>input.commit - echo "M 100644 :$n n$i" >>input.commit - - echo ":$l $mt" >>marks.exp - echo ":$m $mt" >>marks.exp - echo ":$n $mt" >>marks.exp - - printf "100644 blob $mt\tl$i\n" >>tree.exp - printf "100644 blob $mt\tm$i\n" >>tree.exp - printf "100644 blob $mt\tn$i\n" >>tree.exp - - l=$(($l + $l)) - m=$(($m + $m)) - n=$(($l + $n)) - - i=$((1 + $i)) -done - -sort tree.exp > tree.exp_s +test_expect_success 'A: verify diff' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A copy-of-file2 + EOF + git diff-tree -M -r master verify--import-marks >actual && + compare_diff_raw expect actual && + test `git rev-parse --verify master:file2` \ + = `git rev-parse --verify verify--import-marks:copy-of-file2` +' test_expect_success 'A: export marks with large values' ' + test_tick && + mt=$(git hash-object --stdin < /dev/null) && + >input.blob && + >marks.exp && + >tree.exp && + + cat >input.commit <<-EOF && + commit refs/heads/verify--dump-marks + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + test the sparse array dumping routines with exponentially growing marks + COMMIT + EOF + + i=0 l=4 m=6 n=7 && + while test "$i" -lt 27 + do + cat >>input.blob <<-EOF && + blob + mark :$l + data 0 + blob + mark :$m + data 0 + blob + mark :$n + data 0 + EOF + echo "M 100644 :$l l$i" >>input.commit && + echo "M 100644 :$m m$i" >>input.commit && + echo "M 100644 :$n n$i" >>input.commit && + + echo ":$l $mt" >>marks.exp && + echo ":$m $mt" >>marks.exp && + echo ":$n $mt" >>marks.exp && + + printf "100644 blob $mt\tl$i\n" >>tree.exp && + printf "100644 blob $mt\tm$i\n" >>tree.exp && + printf "100644 blob $mt\tn$i\n" >>tree.exp && + + l=$(($l + $l)) && + m=$(($m + $m)) && + n=$(($l + $n)) && + + i=$((1 + $i)) || return 1 + done && + + sort tree.exp > tree.exp_s && + cat input.blob input.commit | git fast-import --export-marks=marks.large && git ls-tree refs/heads/verify--dump-marks >tree.out && test_cmp tree.exp_s tree.out && - test_cmp marks.exp marks.large' + test_cmp marks.exp marks.large +' ### ### series B ### -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -mark :1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT +test_expect_success 'B: fail on invalid blob sha1' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + mark :1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/master + M 755 0000000000000000000000000000000000000001 zero1 -from refs/heads/master -M 755 0000000000000000000000000000000000000001 zero1 + INPUT_END + + test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" && + test_must_fail git fast-import <input +' + +test_expect_success 'B: accept branch name "TEMP_TAG"' ' + cat >input <<-INPUT_END && + commit TEMP_TAG + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + tag base + COMMIT + + from refs/heads/master + + INPUT_END + + test_when_finished "rm -f .git/TEMP_TAG + git gc + git prune" && + git fast-import <input && + test -f .git/TEMP_TAG && + test `git rev-parse master` = `git rev-parse TEMP_TAG^` +' -INPUT_END -test_expect_success 'B: fail on invalid blob sha1' ' - test_must_fail git fast-import <input -' -rm -f .git/objects/pack_* .git/objects/index_* - -cat >input <<INPUT_END -commit TEMP_TAG -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -tag base -COMMIT - -from refs/heads/master - -INPUT_END -test_expect_success \ - 'B: accept branch name "TEMP_TAG"' \ - 'git fast-import <input && - test -f .git/TEMP_TAG && - test `git rev-parse master` = `git rev-parse TEMP_TAG^`' -rm -f .git/TEMP_TAG - -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null - -cat >input <<INPUT_END -commit refs/heads/empty-committer-1 -committer <> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: accept empty committer' ' + cat >input <<-INPUT_END && + commit refs/heads/empty-committer-1 + committer <> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/empty-committer-1 + git gc + git prune" && git fast-import <input && out=$(git fsck) && echo "$out" && test -z "$out" ' -git update-ref -d refs/heads/empty-committer-1 || true - -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null -cat >input <<INPUT_END -commit refs/heads/empty-committer-2 -committer <a@b.com> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: accept and fixup committer with no name' ' + cat >input <<-INPUT_END && + commit refs/heads/empty-committer-2 + committer <a@b.com> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/empty-committer-2 + git gc + git prune" && git fast-import <input && out=$(git fsck) && echo "$out" && test -z "$out" ' -git update-ref -d refs/heads/empty-committer-2 || true -git gc 2>/dev/null >/dev/null -git prune 2>/dev/null >/dev/null - -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name email> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (1)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name email> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <e<mail> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (2)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <e<mail> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <email>> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (3)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <email>> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name <email $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (4)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name <email $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true -cat >input <<INPUT_END -commit refs/heads/invalid-committer -committer Name<email> $GIT_COMMITTER_DATE -data <<COMMIT -empty commit -COMMIT -INPUT_END test_expect_success 'B: fail on invalid committer (5)' ' + cat >input <<-INPUT_END && + commit refs/heads/invalid-committer + committer Name<email> $GIT_COMMITTER_DATE + data <<COMMIT + empty commit + COMMIT + INPUT_END + + test_when_finished "git update-ref -d refs/heads/invalid-committer" && test_must_fail git fast-import <input ' -git update-ref -d refs/heads/invalid-committer || true ### ### series C ### -newf=`echo hi newf | git hash-object -w --stdin` -oldf=`git rev-parse --verify master:file2` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -second -COMMIT - -from refs/heads/master -M 644 $oldf file2/oldf -M 755 $newf file2/newf -D file3 - -INPUT_END -test_expect_success \ - 'C: incremental import create pack from stdin' \ - 'git fast-import <input && - git whatchanged branch' +test_expect_success 'C: incremental import create pack from stdin' ' + newf=`echo hi newf | git hash-object -w --stdin` && + oldf=`git rev-parse --verify master:file2` && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + second + COMMIT + + from refs/heads/master + M 644 $oldf file2/oldf + M 755 $newf file2/newf + D file3 + + INPUT_END + + git fast-import <input && + git whatchanged branch +' test_expect_success 'C: verify pack' ' verify_packs ' -test_expect_success \ - 'C: validate reuse existing blob' \ - 'test $newf = `git rev-parse --verify branch:file2/newf` && - test $oldf = `git rev-parse --verify branch:file2/oldf`' - -cat >expect <<EOF -parent `git rev-parse --verify master^0` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -second -EOF -test_expect_success \ - 'C: verify commit' \ - 'git cat-file commit branch | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A file2/newf -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2 file2/oldf -:100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D file3 -EOF -git diff-tree -M -r master branch >actual -test_expect_success \ - 'C: validate rename result' \ - 'compare_diff_raw expect actual' +test_expect_success 'C: validate reuse existing blob' ' + test $newf = `git rev-parse --verify branch:file2/newf` && + test $oldf = `git rev-parse --verify branch:file2/oldf` +' + +test_expect_success 'C: verify commit' ' + cat >expect <<-EOF && + parent `git rev-parse --verify master^0` + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + second + EOF + + git cat-file commit branch | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'C: validate rename result' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A file2/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2 file2/oldf + :100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D file3 + EOF + git diff-tree -M -r master branch >actual && + compare_diff_raw expect actual +' ### ### series D ### -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third -COMMIT - -from refs/heads/branch^0 -M 644 inline newdir/interesting -data <<EOF -$file5_data -EOF - -M 755 inline newdir/exec.sh -data <<EOF -$file6_data -EOF - -INPUT_END -test_expect_success \ - 'D: inline data in commit' \ - 'git fast-import <input && - git whatchanged branch' +test_expect_success 'D: inline data in commit' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third + COMMIT + + from refs/heads/branch^0 + M 644 inline newdir/interesting + data <<EOF + $file5_data + EOF + + M 755 inline newdir/exec.sh + data <<EOF + $file6_data + EOF + + INPUT_END + + git fast-import <input && + git whatchanged branch +' test_expect_success 'D: verify pack' ' verify_packs ' -cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A newdir/exec.sh -:000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A newdir/interesting -EOF -git diff-tree -M -r branch^ branch >actual -test_expect_success \ - 'D: validate new files added' \ - 'compare_diff_raw expect actual' +test_expect_success 'D: validate new files added' ' + cat >expect <<-EOF && + :000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A newdir/exec.sh + :000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A newdir/interesting + EOF + git diff-tree -M -r branch^ branch >actual && + compare_diff_raw expect actual +' -echo "$file5_data" >expect -test_expect_success \ - 'D: verify file5' \ - 'git cat-file blob branch:newdir/interesting >actual && - test_cmp expect actual' +test_expect_success 'D: verify file5' ' + echo "$file5_data" >expect && + git cat-file blob branch:newdir/interesting >actual && + test_cmp expect actual +' -echo "$file6_data" >expect -test_expect_success \ - 'D: verify file6' \ - 'git cat-file blob branch:newdir/exec.sh >actual && - test_cmp expect actual' +test_expect_success 'D: verify file6' ' + echo "$file6_data" >expect && + git cat-file blob branch:newdir/exec.sh >actual && + test_cmp expect actual +' ### ### series E ### -cat >input <<INPUT_END -commit refs/heads/branch -author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> Tue Feb 6 11:22:18 2007 -0500 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> Tue Feb 6 12:35:02 2007 -0500 -data <<COMMIT -RFC 2822 type date -COMMIT +test_expect_success 'E: rfc2822 date, --date-format=raw' ' + cat >input <<-INPUT_END && + commit refs/heads/branch + author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> Tue Feb 6 11:22:18 2007 -0500 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> Tue Feb 6 12:35:02 2007 -0500 + data <<COMMIT + RFC 2822 type date + COMMIT -from refs/heads/branch^0 + from refs/heads/branch^0 -INPUT_END -test_expect_success 'E: rfc2822 date, --date-format=raw' ' - test_must_fail git fast-import --date-format=raw <input + INPUT_END + + test_must_fail git fast-import --date-format=raw <input +' +test_expect_success 'E: rfc2822 date, --date-format=rfc2822' ' + git fast-import --date-format=rfc2822 <input ' -test_expect_success \ - 'E: rfc2822 date, --date-format=rfc2822' \ - 'git fast-import --date-format=rfc2822 <input' test_expect_success 'E: verify pack' ' verify_packs ' -cat >expect <<EOF -author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1170783302 -0500 +test_expect_success 'E: verify commit' ' + cat >expect <<-EOF && + author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1170783302 -0500 -RFC 2822 type date -EOF -test_expect_success \ - 'E: verify commit' \ - 'git cat-file commit branch | sed 1,2d >actual && - test_cmp expect actual' + RFC 2822 type date + EOF + git cat-file commit branch | sed 1,2d >actual && + test_cmp expect actual +' ### ### series F ### -old_branch=`git rev-parse --verify branch^0` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -losing things already? -COMMIT - -from refs/heads/branch~1 - -reset refs/heads/other -from refs/heads/branch - -INPUT_END -test_expect_success \ - 'F: non-fast-forward update skips' \ - 'if git fast-import <input - then - echo BAD gfi did not fail - return 1 - else - if test $old_branch = `git rev-parse --verify branch^0` - then - : branch unaffected and failure returned - return 0 - else - echo BAD gfi changed branch $old_branch - return 1 - fi - fi - ' +test_expect_success 'F: non-fast-forward update skips' ' + old_branch=`git rev-parse --verify branch^0` && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + losing things already? + COMMIT + + from refs/heads/branch~1 + + reset refs/heads/other + from refs/heads/branch + + INPUT_END + + test_must_fail git fast-import <input && + # branch must remain unaffected + test $old_branch = `git rev-parse --verify branch^0` +' test_expect_success 'F: verify pack' ' verify_packs ' -cat >expect <<EOF -tree `git rev-parse branch~1^{tree}` -parent `git rev-parse branch~1` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE +test_expect_success 'F: verify other commit' ' + cat >expect <<-EOF && + tree `git rev-parse branch~1^{tree}` + parent `git rev-parse branch~1` + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -losing things already? -EOF -test_expect_success \ - 'F: verify other commit' \ - 'git cat-file commit other >actual && - test_cmp expect actual' + losing things already? + EOF + git cat-file commit other >actual && + test_cmp expect actual +' ### ### series G ### -old_branch=`git rev-parse --verify branch^0` -test_tick -cat >input <<INPUT_END -commit refs/heads/branch -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -losing things already? -COMMIT +test_expect_success 'G: non-fast-forward update forced' ' + old_branch=`git rev-parse --verify branch^0` && + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/branch + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + losing things already? + COMMIT -from refs/heads/branch~1 + from refs/heads/branch~1 -INPUT_END -test_expect_success \ - 'G: non-fast-forward update forced' \ - 'git fast-import --force <input' + INPUT_END + git fast-import --force <input +' test_expect_success 'G: verify pack' ' verify_packs ' -test_expect_success \ - 'G: branch changed, but logged' \ - 'test $old_branch != `git rev-parse --verify branch^0` && - test $old_branch = `git rev-parse --verify branch@{1}`' +test_expect_success 'G: branch changed, but logged' ' + test $old_branch != `git rev-parse --verify branch^0` && + test $old_branch = `git rev-parse --verify branch@{1}` +' ### ### series H ### -test_tick -cat >input <<INPUT_END -commit refs/heads/H -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third -COMMIT - -from refs/heads/branch^0 -M 644 inline i-will-die -data <<EOF -this file will never exist. -EOF - -deleteall -M 644 inline h/e/l/lo -data <<EOF -$file5_data -EOF - -INPUT_END -test_expect_success \ - 'H: deletall, add 1' \ - 'git fast-import <input && - git whatchanged H' +test_expect_success 'H: deletall, add 1' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/H + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third + COMMIT + + from refs/heads/branch^0 + M 644 inline i-will-die + data <<EOF + this file will never exist. + EOF + + deleteall + M 644 inline h/e/l/lo + data <<EOF + $file5_data + EOF + + INPUT_END + git fast-import <input && + git whatchanged H +' test_expect_success 'H: verify pack' ' verify_packs ' -cat >expect <<EOF -:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf -:100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file2/oldf -:100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D file4 -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting h/e/l/lo -:100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D newdir/exec.sh -EOF -git diff-tree -M -r H^ H >actual -test_expect_success \ - 'H: validate old files removed, new files added' \ - 'compare_diff_raw expect actual' - -echo "$file5_data" >expect -test_expect_success \ - 'H: verify file' \ - 'git cat-file blob H:h/e/l/lo >actual && - test_cmp expect actual' +test_expect_success 'H: validate old files removed, new files added' ' + cat >expect <<-EOF && + :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file2/newf + :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file2/oldf + :100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D file4 + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting h/e/l/lo + :100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D newdir/exec.sh + EOF + git diff-tree -M -r H^ H >actual && + compare_diff_raw expect actual +' + +test_expect_success 'H: verify file' ' + echo "$file5_data" >expect && + git cat-file blob H:h/e/l/lo >actual && + test_cmp expect actual +' ### ### series I ### -cat >input <<INPUT_END -commit refs/heads/export-boundary -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -we have a border. its only 40 characters wide. -COMMIT +test_expect_success 'I: export-pack-edges' ' + cat >input <<-INPUT_END && + commit refs/heads/export-boundary + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + we have a border. its only 40 characters wide. + COMMIT -from refs/heads/branch + from refs/heads/branch -INPUT_END -test_expect_success \ - 'I: export-pack-edges' \ - 'git fast-import --export-pack-edges=edges.list <input' + INPUT_END + git fast-import --export-pack-edges=edges.list <input +' -cat >expect <<EOF -.git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary` -EOF -test_expect_success \ - 'I: verify edge list' \ - 'sed -e s/pack-.*pack/pack-.pack/ edges.list >actual && - test_cmp expect actual' +test_expect_success 'I: verify edge list' ' + cat >expect <<-EOF && + .git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary` + EOF + sed -e s/pack-.*pack/pack-.pack/ edges.list >actual && + test_cmp expect actual +' ### ### series J ### -cat >input <<INPUT_END -commit refs/heads/J -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create J -COMMIT - -from refs/heads/branch - -reset refs/heads/J - -commit refs/heads/J -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -initialize J -COMMIT - -INPUT_END -test_expect_success \ - 'J: reset existing branch creates empty commit' \ - 'git fast-import <input' -test_expect_success \ - 'J: branch has 1 commit, empty tree' \ - 'test 1 = `git rev-list J | wc -l` && - test 0 = `git ls-tree J | wc -l`' - -cat >input <<INPUT_END -reset refs/heads/J2 - -tag wrong_tag -from refs/heads/J2 -data <<EOF -Tag branch that was reset. -EOF -INPUT_END -test_expect_success \ - 'J: tag must fail on empty branch' \ - 'test_must_fail git fast-import <input' +test_expect_success 'J: reset existing branch creates empty commit' ' + cat >input <<-INPUT_END && + commit refs/heads/J + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create J + COMMIT + + from refs/heads/branch + + reset refs/heads/J + + commit refs/heads/J + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initialize J + COMMIT + + INPUT_END + git fast-import <input +' +test_expect_success 'J: branch has 1 commit, empty tree' ' + test 1 = `git rev-list J | wc -l` && + test 0 = `git ls-tree J | wc -l` +' + +test_expect_success 'J: tag must fail on empty branch' ' + cat >input <<-INPUT_END && + reset refs/heads/J2 + + tag wrong_tag + from refs/heads/J2 + data <<EOF + Tag branch that was reset. + EOF + INPUT_END + test_must_fail git fast-import <input +' + ### ### series K ### -cat >input <<INPUT_END -commit refs/heads/K -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create K -COMMIT +test_expect_success 'K: reinit branch with from' ' + cat >input <<-INPUT_END && + commit refs/heads/K + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create K + COMMIT -from refs/heads/branch + from refs/heads/branch -commit refs/heads/K -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -redo K -COMMIT + commit refs/heads/K + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + redo K + COMMIT -from refs/heads/branch^1 + from refs/heads/branch^1 -INPUT_END -test_expect_success \ - 'K: reinit branch with from' \ - 'git fast-import <input' -test_expect_success \ - 'K: verify K^1 = branch^1' \ - 'test `git rev-parse --verify branch^1` \ - = `git rev-parse --verify K^1`' + INPUT_END + git fast-import <input +' +test_expect_success 'K: verify K^1 = branch^1' ' + test `git rev-parse --verify branch^1` \ + = `git rev-parse --verify K^1` +' ### ### series L ### -cat >input <<INPUT_END -blob -mark :1 -data <<EOF -some data -EOF - -blob -mark :2 -data <<EOF -other data -EOF - -commit refs/heads/L -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -create L -COMMIT - -M 644 :1 b. -M 644 :1 b/other -M 644 :1 ba - -commit refs/heads/L -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -update L -COMMIT - -M 644 :2 b. -M 644 :2 b/other -M 644 :2 ba -INPUT_END - -cat >expect <<EXPECT_END -:100644 100644 4268632... 55d3a52... M b. -:040000 040000 0ae5cac... 443c768... M b -:100644 100644 4268632... 55d3a52... M ba -EXPECT_END - -test_expect_success \ - 'L: verify internal tree sorting' \ - 'git fast-import <input && - git diff-tree --abbrev --raw L^ L >output && - test_cmp expect output' - -cat >input <<INPUT_END -blob -mark :1 -data <<EOF -the data -EOF - -commit refs/heads/L2 -committer C O Mitter <committer@example.com> 1112912473 -0700 -data <<COMMIT -init L2 -COMMIT -M 644 :1 a/b/c -M 644 :1 a/b/d -M 644 :1 a/e/f - -commit refs/heads/L2 -committer C O Mitter <committer@example.com> 1112912473 -0700 -data <<COMMIT -update L2 -COMMIT -C a g -C a/e g/b -M 644 :1 g/b/h -INPUT_END - -cat <<EOF >expect -g/b/f -g/b/h -EOF - -test_expect_success \ - 'L: nested tree copy does not corrupt deltas' \ - 'git fast-import <input && +test_expect_success 'L: verify internal tree sorting' ' + cat >input <<-INPUT_END && + blob + mark :1 + data <<EOF + some data + EOF + + blob + mark :2 + data <<EOF + other data + EOF + + commit refs/heads/L + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + create L + COMMIT + + M 644 :1 b. + M 644 :1 b/other + M 644 :1 ba + + commit refs/heads/L + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + update L + COMMIT + + M 644 :2 b. + M 644 :2 b/other + M 644 :2 ba + INPUT_END + + cat >expect <<-EXPECT_END && + :100644 100644 4268632... 55d3a52... M b. + :040000 040000 0ae5cac... 443c768... M b + :100644 100644 4268632... 55d3a52... M ba + EXPECT_END + + git fast-import <input && + git diff-tree --abbrev --raw L^ L >output && + test_cmp expect output +' + +test_expect_success 'L: nested tree copy does not corrupt deltas' ' + cat >input <<-INPUT_END && + blob + mark :1 + data <<EOF + the data + EOF + + commit refs/heads/L2 + committer C O Mitter <committer@example.com> 1112912473 -0700 + data <<COMMIT + init L2 + COMMIT + M 644 :1 a/b/c + M 644 :1 a/b/d + M 644 :1 a/e/f + + commit refs/heads/L2 + committer C O Mitter <committer@example.com> 1112912473 -0700 + data <<COMMIT + update L2 + COMMIT + C a g + C a/e g/b + M 644 :1 g/b/h + INPUT_END + + cat >expect <<-\EOF && + g/b/f + g/b/h + EOF + + test_when_finished "git update-ref -d refs/heads/L2" && + git fast-import <input && git ls-tree L2 g/b/ >tmp && cat tmp | cut -f 2 >actual && test_cmp expect actual && - git fsck `git rev-parse L2`' - -git update-ref -d refs/heads/L2 + git fsck `git rev-parse L2` +' ### ### series M ### -test_tick -cat >input <<INPUT_END -commit refs/heads/M1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/branch^0 -R file2/newf file2/n.e.w.f - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf file2/n.e.w.f -EOF -test_expect_success \ - 'M: rename file in same subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M1^ M1 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/branch^0 -R file2/newf i/am/new/to/you - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf i/am/new/to/you -EOF -test_expect_success \ - 'M: rename file to new subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M2^ M2 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file rename -COMMIT - -from refs/heads/M2^0 -R i other/sub - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you other/sub/am/new/to/you -EOF -test_expect_success \ - 'M: rename subdirectory to new subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M3^ M3 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/M4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -rename root -COMMIT - -from refs/heads/M2^0 -R "" sub - -INPUT_END - -cat >expect <<EOF -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2/oldf sub/file2/oldf -:100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 R100 file4 sub/file4 -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you sub/i/am/new/to/you -:100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 R100 newdir/exec.sh sub/newdir/exec.sh -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting sub/newdir/interesting -EOF -test_expect_success \ - 'M: rename root to subdirectory' \ - 'git fast-import <input && - git diff-tree -M -r M4^ M4 >actual && - cat actual && - compare_diff_raw expect actual' +test_expect_success 'M: rename file in same subdirectory' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/M1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/branch^0 + R file2/newf file2/n.e.w.f + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf file2/n.e.w.f + EOF + git fast-import <input && + git diff-tree -M -r M1^ M1 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename file to new subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/branch^0 + R file2/newf i/am/new/to/you + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 file2/newf i/am/new/to/you + EOF + git fast-import <input && + git diff-tree -M -r M2^ M2 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename subdirectory to new subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file rename + COMMIT + + from refs/heads/M2^0 + R i other/sub + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you other/sub/am/new/to/you + EOF + git fast-import <input && + git diff-tree -M -r M3^ M3 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'M: rename root to subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/M4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + rename root + COMMIT + + from refs/heads/M2^0 + R "" sub + + INPUT_END + + cat >expect <<-EOF && + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100 file2/oldf sub/file2/oldf + :100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 R100 file4 sub/file4 + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100 i/am/new/to/you sub/i/am/new/to/you + :100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 R100 newdir/exec.sh sub/newdir/exec.sh + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100 newdir/interesting sub/newdir/interesting + EOF + git fast-import <input && + git diff-tree -M -r M4^ M4 >actual && + cat actual && + compare_diff_raw expect actual +' ### ### series N ### -test_tick -cat >input <<INPUT_END -commit refs/heads/N1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -file copy -COMMIT - -from refs/heads/branch^0 -C file2/newf file2/n.e.w.f - -INPUT_END - -cat >expect <<EOF -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file2/n.e.w.f -EOF -test_expect_success \ - 'N: copy file in same subdirectory' \ - 'git fast-import <input && - git diff-tree -C --find-copies-harder -r N1^ N1 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/N2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -clean directory copy -COMMIT - -from refs/heads/branch^0 -C file2 file3 - -commit refs/heads/N2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -modify directory copy -COMMIT - -M 644 inline file3/file5 -data <<EOF -$file5_data -EOF - -INPUT_END - -cat >expect <<EOF -:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 -:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf -:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf -EOF -test_expect_success \ - 'N: copy then modify subdirectory' \ - 'git fast-import <input && - git diff-tree -C --find-copies-harder -r N2^^ N2 >actual && - compare_diff_raw expect actual' - -cat >input <<INPUT_END -commit refs/heads/N3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -dirty directory copy -COMMIT - -from refs/heads/branch^0 -M 644 inline file2/file5 -data <<EOF -$file5_data -EOF - -C file2 file3 -D file2/file5 - -INPUT_END - -test_expect_success \ - 'N: copy dirty subdirectory' \ - 'git fast-import <input && - test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}`' - -test_expect_success \ - 'N: copy directory by id' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy file in same subdirectory' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/N1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + file copy + COMMIT + + from refs/heads/branch^0 + C file2/newf file2/n.e.w.f + + INPUT_END + + cat >expect <<-EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file2/n.e.w.f + EOF + git fast-import <input && + git diff-tree -C --find-copies-harder -r N1^ N1 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'N: copy then modify subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/N2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + clean directory copy + COMMIT + + from refs/heads/branch^0 + C file2 file3 + + commit refs/heads/N2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + modify directory copy + COMMIT + + M 644 inline file3/file5 + data <<EOF + $file5_data + EOF + + INPUT_END + + cat >expect <<-EOF && + :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf EOF - subdir=$(git rev-parse refs/heads/branch^0:file2) && - cat >input <<-INPUT_END && + git fast-import <input && + git diff-tree -C --find-copies-harder -r N2^^ N2 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'N: copy dirty subdirectory' ' + cat >input <<-INPUT_END && + commit refs/heads/N3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + dirty directory copy + COMMIT + + from refs/heads/branch^0 + M 644 inline file2/file5 + data <<EOF + $file5_data + EOF + + C file2 file3 + D file2/file5 + + INPUT_END + + git fast-import <input && + test `git rev-parse N2^{tree}` = `git rev-parse N3^{tree}` +' + +test_expect_success 'N: copy directory by id' ' + cat >expect <<-\EOF && + :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf + :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf + EOF + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && commit refs/heads/N4 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1127,9 +1125,10 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $subdir file3 INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N4^ N4 >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4^ N4 >actual && + compare_diff_raw expect actual +' test_expect_success PIPE 'N: read and copy directory' ' cat >expect <<-\EOF && @@ -1202,14 +1201,13 @@ test_expect_success PIPE 'N: empty directory reads as missing' ' test_cmp expect actual ' -test_expect_success \ - 'N: copy root directory by tree hash' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy root directory by tree hash' ' + cat >expect <<-\EOF && :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D file3/newf :100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D file3/oldf EOF - root=$(git rev-parse refs/heads/branch^0^{tree}) && - cat >input <<-INPUT_END && + root=$(git rev-parse refs/heads/branch^0^{tree}) && + cat >input <<-INPUT_END && commit refs/heads/N6 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1219,20 +1217,20 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $root "" INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N4 N6 >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4 N6 >actual && + compare_diff_raw expect actual +' -test_expect_success \ - 'N: copy root by path' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: copy root by path' ' + cat >expect <<-\EOF && :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf oldroot/file2/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf oldroot/file2/oldf :100755 100755 85df50785d62d3b05ab03d9cbf7e4a0b49449730 85df50785d62d3b05ab03d9cbf7e4a0b49449730 C100 file4 oldroot/file4 :100755 100755 e74b7d465e52746be2b4bae983670711e6e66657 e74b7d465e52746be2b4bae983670711e6e66657 C100 newdir/exec.sh oldroot/newdir/exec.sh :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting oldroot/newdir/interesting EOF - cat >input <<-INPUT_END && + cat >input <<-INPUT_END && commit refs/heads/N-copy-root-path committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1242,21 +1240,21 @@ test_expect_success \ from refs/heads/branch^0 C "" oldroot INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && - compare_diff_raw expect actual' + git fast-import <input && + git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && + compare_diff_raw expect actual +' -test_expect_success \ - 'N: delete directory by copying' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: delete directory by copying' ' + cat >expect <<-\EOF && OBJID :100644 000000 OBJID OBJID D foo/bar/qux OBJID :000000 100644 OBJID OBJID A foo/bar/baz :000000 100644 OBJID OBJID A foo/bar/qux EOF - empty_tree=$(git mktree </dev/null) && - cat >input <<-INPUT_END && + empty_tree=$(git mktree </dev/null) && + cat >input <<-INPUT_END && commit refs/heads/N-delete committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1282,21 +1280,21 @@ test_expect_success \ M 040000 $empty_tree foo/bar/qux INPUT_END - git fast-import <input && - git rev-list N-delete | + git fast-import <input && + git rev-list N-delete | git diff-tree -r --stdin --root --always | sed -e "s/$_x40/OBJID/g" >actual && - test_cmp expect actual' + test_cmp expect actual +' -test_expect_success \ - 'N: modify copied tree' \ - 'cat >expect <<-\EOF && +test_expect_success 'N: modify copied tree' ' + cat >expect <<-\EOF && :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100 newdir/interesting file3/file5 :100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100 file2/newf file3/newf :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100 file2/oldf file3/oldf EOF - subdir=$(git rev-parse refs/heads/branch^0:file2) && - cat >input <<-INPUT_END && + subdir=$(git rev-parse refs/heads/branch^0:file2) && + cat >input <<-INPUT_END && commit refs/heads/N5 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1317,14 +1315,14 @@ test_expect_success \ $file5_data EOF INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N5^^ N5 >actual && - compare_diff_raw expect actual' - -test_expect_success \ - 'N: reject foo/ syntax' \ - 'subdir=$(git rev-parse refs/heads/branch^0:file2) && - test_must_fail git fast-import <<-INPUT_END + git fast-import <input && + git diff-tree -C --find-copies-harder -r N5^^ N5 >actual && + compare_diff_raw expect actual +' + +test_expect_success 'N: reject foo/ syntax' ' + subdir=$(git rev-parse refs/heads/branch^0:file2) && + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5B committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1333,11 +1331,11 @@ test_expect_success \ from refs/heads/branch^0 M 040000 $subdir file3/ - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in copy source' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in copy source' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5C committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1346,11 +1344,11 @@ test_expect_success \ from refs/heads/branch^0 C file2/ file3 - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in rename source' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in rename source' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5D committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1359,11 +1357,11 @@ test_expect_success \ from refs/heads/branch^0 R file2/ file3 - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: reject foo/ syntax in ls argument' \ - 'test_must_fail git fast-import <<-INPUT_END +test_expect_success 'N: reject foo/ syntax in ls argument' ' + test_must_fail git fast-import <<-INPUT_END commit refs/heads/N5E committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1372,13 +1370,13 @@ test_expect_success \ from refs/heads/branch^0 ls "file2/" - INPUT_END' + INPUT_END +' -test_expect_success \ - 'N: copy to root by id and modify' \ - 'echo "hello, world" >expect.foo && - echo hello >expect.bar && - git fast-import <<-SETUP_END && +test_expect_success 'N: copy to root by id and modify' ' + echo "hello, world" >expect.foo && + echo hello >expect.bar && + git fast-import <<-SETUP_END && commit refs/heads/N7 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1392,8 +1390,8 @@ test_expect_success \ EOF SETUP_END - tree=$(git rev-parse --verify N7:) && - git fast-import <<-INPUT_END && + tree=$(git rev-parse --verify N7:) && + git fast-import <<-INPUT_END && commit refs/heads/N8 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1406,15 +1404,15 @@ test_expect_success \ hello, world EOF INPUT_END - git show N8:foo/foo >actual.foo && - git show N8:foo/bar >actual.bar && - test_cmp expect.foo actual.foo && - test_cmp expect.bar actual.bar' - -test_expect_success \ - 'N: extract subtree' \ - 'branch=$(git rev-parse --verify refs/heads/branch^{tree}) && - cat >input <<-INPUT_END && + git show N8:foo/foo >actual.foo && + git show N8:foo/bar >actual.bar && + test_cmp expect.foo actual.foo && + test_cmp expect.bar actual.bar +' + +test_expect_success 'N: extract subtree' ' + branch=$(git rev-parse --verify refs/heads/branch^{tree}) && + cat >input <<-INPUT_END && commit refs/heads/N9 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1424,14 +1422,14 @@ test_expect_success \ M 040000 $branch "" C "newdir" "" INPUT_END - git fast-import <input && - git diff --exit-code branch:newdir N9' - -test_expect_success \ - 'N: modify subtree, extract it, and modify again' \ - 'echo hello >expect.baz && - echo hello, world >expect.qux && - git fast-import <<-SETUP_END && + git fast-import <input && + git diff --exit-code branch:newdir N9 +' + +test_expect_success 'N: modify subtree, extract it, and modify again' ' + echo hello >expect.baz && + echo hello, world >expect.qux && + git fast-import <<-SETUP_END && commit refs/heads/N10 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1445,8 +1443,8 @@ test_expect_success \ EOF SETUP_END - tree=$(git rev-parse --verify N10:) && - git fast-import <<-INPUT_END && + tree=$(git rev-parse --verify N10:) && + git fast-import <<-INPUT_END && commit refs/heads/N11 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE data <<COMMIT @@ -1461,676 +1459,692 @@ test_expect_success \ R "foo" "" C "bar/qux" "bar/quux" INPUT_END - git show N11:bar/baz >actual.baz && - git show N11:bar/qux >actual.qux && - git show N11:bar/quux >actual.quux && - test_cmp expect.baz actual.baz && - test_cmp expect.qux actual.qux && - test_cmp expect.qux actual.quux' + git show N11:bar/baz >actual.baz && + git show N11:bar/qux >actual.qux && + git show N11:bar/quux >actual.quux && + test_cmp expect.baz actual.baz && + test_cmp expect.qux actual.qux && + test_cmp expect.qux actual.quux' ### ### series O ### -cat >input <<INPUT_END -#we will -commit refs/heads/O1 -# -- ignore all of this text -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -# $GIT_COMMITTER_NAME has inserted here for his benefit. -data <<COMMIT -dirty directory copy -COMMIT - -# don't forget the import blank line! -# -# yes, we started from our usual base of branch^0. -# i like branch^0. -from refs/heads/branch^0 -# and we need to reuse file2/file5 from N3 above. -M 644 inline file2/file5 -# otherwise the tree will be different -data <<EOF -$file5_data -EOF - -# don't forget to copy file2 to file3 -C file2 file3 -# -# or to delete file5 from file2. -D file2/file5 -# are we done yet? - -INPUT_END - -test_expect_success \ - 'O: comments are all skipped' \ - 'git fast-import <input && - test `git rev-parse N3` = `git rev-parse O1`' - -cat >input <<INPUT_END -commit refs/heads/O2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -dirty directory copy -COMMIT -from refs/heads/branch^0 -M 644 inline file2/file5 -data <<EOF -$file5_data -EOF -C file2 file3 -D file2/file5 - -INPUT_END - -test_expect_success \ - 'O: blank lines not necessary after data commands' \ - 'git fast-import <input && - test `git rev-parse N3` = `git rev-parse O2`' - -test_expect_success \ - 'O: repack before next test' \ - 'git repack -a -d' - -cat >input <<INPUT_END -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zstring -COMMIT -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zof -COMMIT -checkpoint -commit refs/heads/O3 -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zempty -COMMIT -checkpoint -commit refs/heads/O3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zcommits -COMMIT -reset refs/tags/O3-2nd -from :5 -reset refs/tags/O3-3rd -from :5 -INPUT_END - -cat >expect <<INPUT_END -string -of -empty -commits -INPUT_END -test_expect_success \ - 'O: blank lines not necessary after other commands' \ - 'git fast-import <input && - test 8 = `find .git/objects/pack -type f | wc -l` && - test `git rev-parse refs/tags/O3-2nd` = `git rev-parse O3^` && - git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && - test_cmp expect actual' - -cat >input <<INPUT_END -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zstring -COMMIT -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zof -COMMIT -progress Two commits down, 2 to go! -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zempty -COMMIT -progress Three commits down, 1 to go! -commit refs/heads/O4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -zcommits -COMMIT -progress I'm done! -INPUT_END -test_expect_success \ - 'O: progress outputs as requested by input' \ - 'git fast-import <input >actual && - grep "progress " <input >expect && - test_cmp expect actual' +test_expect_success 'O: comments are all skipped' ' + cat >input <<-INPUT_END && + #we will + commit refs/heads/O1 + # -- ignore all of this text + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + # $GIT_COMMITTER_NAME has inserted here for his benefit. + data <<COMMIT + dirty directory copy + COMMIT + + # do not forget the import blank line! + # + # yes, we started from our usual base of branch^0. + # i like branch^0. + from refs/heads/branch^0 + # and we need to reuse file2/file5 from N3 above. + M 644 inline file2/file5 + # otherwise the tree will be different + data <<EOF + $file5_data + EOF + + # do not forget to copy file2 to file3 + C file2 file3 + # + # or to delete file5 from file2. + D file2/file5 + # are we done yet? + + INPUT_END + + git fast-import <input && + test `git rev-parse N3` = `git rev-parse O1` +' + +test_expect_success 'O: blank lines not necessary after data commands' ' + cat >input <<-INPUT_END && + commit refs/heads/O2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + dirty directory copy + COMMIT + from refs/heads/branch^0 + M 644 inline file2/file5 + data <<EOF + $file5_data + EOF + C file2 file3 + D file2/file5 + + INPUT_END + + git fast-import <input && + test `git rev-parse N3` = `git rev-parse O2` +' + +test_expect_success 'O: repack before next test' ' + git repack -a -d +' + +test_expect_success 'O: blank lines not necessary after other commands' ' + cat >input <<-INPUT_END && + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zstring + COMMIT + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zof + COMMIT + checkpoint + commit refs/heads/O3 + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zempty + COMMIT + checkpoint + commit refs/heads/O3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zcommits + COMMIT + reset refs/tags/O3-2nd + from :5 + reset refs/tags/O3-3rd + from :5 + INPUT_END + + cat >expect <<-INPUT_END && + string + of + empty + commits + INPUT_END + + git fast-import <input && + test 8 = `find .git/objects/pack -type f | wc -l` && + test `git rev-parse refs/tags/O3-2nd` = `git rev-parse O3^` && + git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && + test_cmp expect actual +' + +test_expect_success 'O: progress outputs as requested by input' ' + cat >input <<-INPUT_END && + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zstring + COMMIT + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zof + COMMIT + progress Two commits down, 2 to go! + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zempty + COMMIT + progress Three commits down, 1 to go! + commit refs/heads/O4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + zcommits + COMMIT + progress done! + INPUT_END + git fast-import <input >actual && + grep "progress " <input >expect && + test_cmp expect actual +' ### ### series P (gitlinks) ### -cat >input <<INPUT_END -blob -mark :1 -data 10 -test file - -reset refs/heads/sub -commit refs/heads/sub -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 12 -sub_initial -M 100644 :1 file - -blob -mark :3 -data <<DATAEND -[submodule "sub"] - path = sub - url = "`pwd`/sub" -DATAEND - -commit refs/heads/subuse1 -mark :4 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 8 -initial -from refs/heads/master -M 100644 :3 .gitmodules -M 160000 :2 sub - -blob -mark :5 -data 20 -test file -more data - -commit refs/heads/sub -mark :6 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 11 -sub_second -from :2 -M 100644 :5 file - -commit refs/heads/subuse1 -mark :7 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 7 -second -from :4 -M 160000 :6 sub - -INPUT_END - -test_expect_success \ - 'P: superproject & submodule mix' \ - 'git fast-import <input && - git checkout subuse1 && - rm -rf sub && mkdir sub && (cd sub && - git init && - git fetch --update-head-ok .. refs/heads/sub:refs/heads/master && - git checkout master) && - git submodule init && - git submodule update' - -SUBLAST=$(git rev-parse --verify sub) -SUBPREV=$(git rev-parse --verify sub^) - -cat >input <<INPUT_END -blob -mark :1 -data <<DATAEND -[submodule "sub"] - path = sub - url = "`pwd`/sub" -DATAEND - -commit refs/heads/subuse2 -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 8 -initial -from refs/heads/master -M 100644 :1 .gitmodules -M 160000 $SUBPREV sub - -commit refs/heads/subuse2 -mark :3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data 7 -second -from :2 -M 160000 $SUBLAST sub - -INPUT_END - -test_expect_success \ - 'P: verbatim SHA gitlinks' \ - 'git branch -D sub && - git gc && git prune && - git fast-import <input && - test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1)' - -test_tick -cat >input <<INPUT_END -commit refs/heads/subuse3 -mark :1 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT - -from refs/heads/subuse2 -M 160000 inline sub -data <<DATA -$SUBPREV -DATA - -INPUT_END +test_expect_success 'P: superproject & submodule mix' ' + cat >input <<-INPUT_END && + blob + mark :1 + data 10 + test file -test_expect_success 'P: fail on inline gitlink' ' - test_must_fail git fast-import <input' + reset refs/heads/sub + commit refs/heads/sub + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 12 + sub_initial + M 100644 :1 file + + blob + mark :3 + data <<DATAEND + [submodule "sub"] + path = sub + url = "`pwd`/sub" + DATAEND + + commit refs/heads/subuse1 + mark :4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 8 + initial + from refs/heads/master + M 100644 :3 .gitmodules + M 160000 :2 sub + + blob + mark :5 + data 20 + test file + more data + + commit refs/heads/sub + mark :6 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 11 + sub_second + from :2 + M 100644 :5 file + + commit refs/heads/subuse1 + mark :7 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 7 + second + from :4 + M 160000 :6 sub + + INPUT_END + + git fast-import <input && + git checkout subuse1 && + rm -rf sub && + mkdir sub && + ( + cd sub && + git init && + git fetch --update-head-ok .. refs/heads/sub:refs/heads/master && + git checkout master + ) && + git submodule init && + git submodule update +' -test_tick -cat >input <<INPUT_END -blob -mark :1 -data <<DATA -$SUBPREV -DATA +test_expect_success 'P: verbatim SHA gitlinks' ' + SUBLAST=$(git rev-parse --verify sub) && + SUBPREV=$(git rev-parse --verify sub^) && -commit refs/heads/subuse3 -mark :2 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -corrupt -COMMIT + cat >input <<-INPUT_END && + blob + mark :1 + data <<DATAEND + [submodule "sub"] + path = sub + url = "`pwd`/sub" + DATAEND -from refs/heads/subuse2 -M 160000 :1 sub + commit refs/heads/subuse2 + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 8 + initial + from refs/heads/master + M 100644 :1 .gitmodules + M 160000 $SUBPREV sub -INPUT_END + commit refs/heads/subuse2 + mark :3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data 7 + second + from :2 + M 160000 $SUBLAST sub + + INPUT_END + + git branch -D sub && + git gc && + git prune && + git fast-import <input && + test $(git rev-parse --verify subuse2) = $(git rev-parse --verify subuse1) +' + +test_expect_success 'P: fail on inline gitlink' ' + test_tick && + cat >input <<-INPUT_END && + commit refs/heads/subuse3 + mark :1 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/subuse2 + M 160000 inline sub + data <<DATA + $SUBPREV + DATA + + INPUT_END + + test_must_fail git fast-import <input +' test_expect_success 'P: fail on blob mark in gitlink' ' - test_must_fail git fast-import <input' + test_tick && + cat >input <<-INPUT_END && + blob + mark :1 + data <<DATA + $SUBPREV + DATA + + commit refs/heads/subuse3 + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + corrupt + COMMIT + + from refs/heads/subuse2 + M 160000 :1 sub + + INPUT_END + + test_must_fail git fast-import <input +' ### ### series Q (notes) ### -note1_data="The first note for the first commit" -note2_data="The first note for the second commit" -note3_data="The first note for the third commit" -note1b_data="The second note for the first commit" -note1c_data="The third note for the first commit" -note2b_data="The second note for the second commit" - -test_tick -cat >input <<INPUT_END -blob -mark :2 -data <<EOF -$file2_data -EOF - -commit refs/heads/notes-test -mark :3 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -first (:3) -COMMIT - -M 644 :2 file2 - -blob -mark :4 -data $file4_len -$file4_data -commit refs/heads/notes-test -mark :5 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -second (:5) -COMMIT - -M 644 :4 file4 - -commit refs/heads/notes-test -mark :6 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -third (:6) -COMMIT - -M 644 inline file5 -data <<EOF -$file5_data -EOF - -M 755 inline file6 -data <<EOF -$file6_data -EOF - -blob -mark :7 -data <<EOF -$note1_data -EOF - -blob -mark :8 -data <<EOF -$note2_data -EOF - -commit refs/notes/foobar -mark :9 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:9) -COMMIT - -N :7 :3 -N :8 :5 -N inline :6 -data <<EOF -$note3_data -EOF - -commit refs/notes/foobar -mark :10 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:10) -COMMIT - -N inline :3 -data <<EOF -$note1b_data -EOF - -commit refs/notes/foobar2 -mark :11 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:11) -COMMIT - -N inline :3 -data <<EOF -$note1c_data -EOF - -commit refs/notes/foobar -mark :12 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -notes (:12) -COMMIT - -deleteall -N inline :5 -data <<EOF -$note2b_data -EOF - -INPUT_END - -test_expect_success \ - 'Q: commit notes' \ - 'git fast-import <input && - git whatchanged notes-test' +test_expect_success 'Q: commit notes' ' + note1_data="The first note for the first commit" && + note2_data="The first note for the second commit" && + note3_data="The first note for the third commit" && + note1b_data="The second note for the first commit" && + note1c_data="The third note for the first commit" && + note2b_data="The second note for the second commit" && + + test_tick && + cat >input <<-INPUT_END && + blob + mark :2 + data <<EOF + $file2_data + EOF + + commit refs/heads/notes-test + mark :3 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + first (:3) + COMMIT + + M 644 :2 file2 + + blob + mark :4 + data $file4_len + $file4_data + commit refs/heads/notes-test + mark :5 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + second (:5) + COMMIT + + M 644 :4 file4 + + commit refs/heads/notes-test + mark :6 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + third (:6) + COMMIT + + M 644 inline file5 + data <<EOF + $file5_data + EOF + + M 755 inline file6 + data <<EOF + $file6_data + EOF + + blob + mark :7 + data <<EOF + $note1_data + EOF + + blob + mark :8 + data <<EOF + $note2_data + EOF + + commit refs/notes/foobar + mark :9 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:9) + COMMIT + + N :7 :3 + N :8 :5 + N inline :6 + data <<EOF + $note3_data + EOF + + commit refs/notes/foobar + mark :10 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:10) + COMMIT + + N inline :3 + data <<EOF + $note1b_data + EOF + + commit refs/notes/foobar2 + mark :11 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:11) + COMMIT + + N inline :3 + data <<EOF + $note1c_data + EOF + + commit refs/notes/foobar + mark :12 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + notes (:12) + COMMIT + + deleteall + N inline :5 + data <<EOF + $note2b_data + EOF + + INPUT_END + + git fast-import <input && + git whatchanged notes-test +' test_expect_success 'Q: verify pack' ' verify_packs ' -commit1=$(git rev-parse notes-test~2) -commit2=$(git rev-parse notes-test^) -commit3=$(git rev-parse notes-test) - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -first (:3) -EOF -test_expect_success \ - 'Q: verify first commit' \ - 'git cat-file commit notes-test~2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -parent $commit1 -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -second (:5) -EOF -test_expect_success \ - 'Q: verify second commit' \ - 'git cat-file commit notes-test^ | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -parent $commit2 -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -third (:6) -EOF -test_expect_success \ - 'Q: verify third commit' \ - 'git cat-file commit notes-test | sed 1d >actual && - test_cmp expect actual' - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:9) -EOF -test_expect_success \ - 'Q: verify first notes commit' \ - 'git cat-file commit refs/notes/foobar~2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -100644 blob $commit2 -100644 blob $commit3 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify first notes tree' \ - 'git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1_data" >expect -test_expect_success \ - 'Q: verify first note for first commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit1 >actual && test_cmp expect actual' - -echo "$note2_data" >expect -test_expect_success \ - 'Q: verify first note for second commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit2 >actual && test_cmp expect actual' - -echo "$note3_data" >expect -test_expect_success \ - 'Q: verify first note for third commit' \ - 'git cat-file blob refs/notes/foobar~2:$commit3 >actual && test_cmp expect actual' - -cat >expect <<EOF -parent `git rev-parse --verify refs/notes/foobar~2` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:10) -EOF -test_expect_success \ - 'Q: verify second notes commit' \ - 'git cat-file commit refs/notes/foobar^ | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -100644 blob $commit2 -100644 blob $commit3 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify second notes tree' \ - 'git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1b_data" >expect -test_expect_success \ - 'Q: verify second note for first commit' \ - 'git cat-file blob refs/notes/foobar^:$commit1 >actual && test_cmp expect actual' - -echo "$note2_data" >expect -test_expect_success \ - 'Q: verify first note for second commit' \ - 'git cat-file blob refs/notes/foobar^:$commit2 >actual && test_cmp expect actual' - -echo "$note3_data" >expect -test_expect_success \ - 'Q: verify first note for third commit' \ - 'git cat-file blob refs/notes/foobar^:$commit3 >actual && test_cmp expect actual' - -cat >expect <<EOF -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:11) -EOF -test_expect_success \ - 'Q: verify third notes commit' \ - 'git cat-file commit refs/notes/foobar2 | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit1 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify third notes tree' \ - 'git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note1c_data" >expect -test_expect_success \ - 'Q: verify third note for first commit' \ - 'git cat-file blob refs/notes/foobar2:$commit1 >actual && test_cmp expect actual' - -cat >expect <<EOF -parent `git rev-parse --verify refs/notes/foobar^` -author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - -notes (:12) -EOF -test_expect_success \ - 'Q: verify fourth notes commit' \ - 'git cat-file commit refs/notes/foobar | sed 1d >actual && - test_cmp expect actual' - -cat >expect.unsorted <<EOF -100644 blob $commit2 -EOF -cat expect.unsorted | sort >expect -test_expect_success \ - 'Q: verify fourth notes tree' \ - 'git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && - test_cmp expect actual' - -echo "$note2b_data" >expect -test_expect_success \ - 'Q: verify second note for second commit' \ - 'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual' - -cat >input <<EOF -reset refs/heads/Q0 - -commit refs/heads/note-Q0 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -Note for an empty branch. -COMMIT - -N inline refs/heads/Q0 -data <<NOTE -some note -NOTE -EOF -test_expect_success \ - 'Q: deny note on empty branch' \ - 'test_must_fail git fast-import <input' +test_expect_success 'Q: verify first commit' ' + commit1=$(git rev-parse notes-test~2) && + commit2=$(git rev-parse notes-test^) && + commit3=$(git rev-parse notes-test) && + + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + first (:3) + EOF + git cat-file commit notes-test~2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second commit' ' + cat >expect <<-EOF && + parent $commit1 + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + second (:5) + EOF + git cat-file commit notes-test^ | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third commit' ' + cat >expect <<-EOF && + parent $commit2 + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + third (:6) + EOF + git cat-file commit notes-test | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first notes commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:9) + EOF + git cat-file commit refs/notes/foobar~2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + 100644 blob $commit2 + 100644 blob $commit3 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for first commit' ' + echo "$note1_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for second commit' ' + echo "$note2_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for third commit' ' + echo "$note3_data" >expect && + git cat-file blob refs/notes/foobar~2:$commit3 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second notes commit' ' + cat >expect <<-EOF && + parent `git rev-parse --verify refs/notes/foobar~2` + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:10) + EOF + git cat-file commit refs/notes/foobar^ | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + 100644 blob $commit2 + 100644 blob $commit3 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second note for first commit' ' + echo "$note1b_data" >expect && + git cat-file blob refs/notes/foobar^:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for second commit' ' + echo "$note2_data" >expect && + git cat-file blob refs/notes/foobar^:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify first note for third commit' ' + echo "$note3_data" >expect && + git cat-file blob refs/notes/foobar^:$commit3 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third notes commit' ' + cat >expect <<-EOF && + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:11) + EOF + git cat-file commit refs/notes/foobar2 | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit1 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify third note for first commit' ' + echo "$note1c_data" >expect && + git cat-file blob refs/notes/foobar2:$commit1 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify fourth notes commit' ' + cat >expect <<-EOF && + parent `git rev-parse --verify refs/notes/foobar^` + author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + + notes (:12) + EOF + git cat-file commit refs/notes/foobar | sed 1d >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify fourth notes tree' ' + cat >expect.unsorted <<-EOF && + 100644 blob $commit2 + EOF + cat expect.unsorted | sort >expect && + git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && + test_cmp expect actual +' + +test_expect_success 'Q: verify second note for second commit' ' + echo "$note2b_data" >expect && + git cat-file blob refs/notes/foobar:$commit2 >actual && + test_cmp expect actual +' + +test_expect_success 'Q: deny note on empty branch' ' + cat >input <<-EOF && + reset refs/heads/Q0 + + commit refs/heads/note-Q0 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + Note for an empty branch. + COMMIT + + N inline refs/heads/Q0 + data <<NOTE + some note + NOTE + EOF + test_must_fail git fast-import <input +' ### ### series R (feature and option) ### -cat >input <<EOF -feature no-such-feature-exists -EOF - test_expect_success 'R: abort on unsupported feature' ' + cat >input <<-EOF && + feature no-such-feature-exists + EOF + test_must_fail git fast-import <input ' -cat >input <<EOF -feature date-format=now -EOF - test_expect_success 'R: supported feature is accepted' ' + cat >input <<-EOF && + feature date-format=now + EOF + git fast-import <input ' -cat >input << EOF -blob -data 3 -hi -feature date-format=now -EOF - test_expect_success 'R: abort on receiving feature after data command' ' + cat >input <<-EOF && + blob + data 3 + hi + feature date-format=now + EOF + test_must_fail git fast-import <input ' -cat >input << EOF -feature import-marks=git.marks -feature import-marks=git2.marks -EOF - test_expect_success 'R: only one import-marks feature allowed per stream' ' + cat >input <<-EOF && + feature import-marks=git.marks + feature import-marks=git2.marks + EOF + test_must_fail git fast-import <input ' -cat >input << EOF -feature export-marks=git.marks -blob -mark :1 -data 3 -hi +test_expect_success 'R: export-marks feature results in a marks file being created' ' + cat >input <<-EOF && + feature export-marks=git.marks + blob + mark :1 + data 3 + hi -EOF + EOF -test_expect_success \ - 'R: export-marks feature results in a marks file being created' \ - 'cat input | git fast-import && - grep :1 git.marks' + cat input | git fast-import && + grep :1 git.marks +' -test_expect_success \ - 'R: export-marks options can be overridden by commandline options' \ - 'cat input | git fast-import --export-marks=other.marks && - grep :1 other.marks' +test_expect_success 'R: export-marks options can be overridden by commandline options' ' + cat input | git fast-import --export-marks=other.marks && + grep :1 other.marks +' test_expect_success 'R: catch typo in marks file name' ' test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null && @@ -2234,62 +2248,62 @@ test_expect_success 'R: feature import-marks-if-exists' ' test_cmp expect io.marks ' -cat >input << EOF -feature import-marks=marks.out -feature export-marks=marks.new -EOF - -test_expect_success \ - 'R: import to output marks works without any content' \ - 'cat input | git fast-import && - test_cmp marks.out marks.new' +test_expect_success 'R: import to output marks works without any content' ' + cat >input <<-EOF && + feature import-marks=marks.out + feature export-marks=marks.new + EOF -cat >input <<EOF -feature import-marks=nonexistent.marks -feature export-marks=marks.new -EOF + cat input | git fast-import && + test_cmp marks.out marks.new +' -test_expect_success \ - 'R: import marks prefers commandline marks file over the stream' \ - 'cat input | git fast-import --import-marks=marks.out && - test_cmp marks.out marks.new' +test_expect_success 'R: import marks prefers commandline marks file over the stream' ' + cat >input <<-EOF && + feature import-marks=nonexistent.marks + feature export-marks=marks.new + EOF + cat input | git fast-import --import-marks=marks.out && + test_cmp marks.out marks.new +' -cat >input <<EOF -feature import-marks=nonexistent.marks -feature export-marks=combined.marks -EOF test_expect_success 'R: multiple --import-marks= should be honoured' ' - head -n2 marks.out > one.marks && - tail -n +3 marks.out > two.marks && - git fast-import --import-marks=one.marks --import-marks=two.marks <input && - test_cmp marks.out combined.marks -' + cat >input <<-EOF && + feature import-marks=nonexistent.marks + feature export-marks=combined.marks + EOF -cat >input <<EOF -feature relative-marks -feature import-marks=relative.in -feature export-marks=relative.out -EOF + head -n2 marks.out > one.marks && + tail -n +3 marks.out > two.marks && + git fast-import --import-marks=one.marks --import-marks=two.marks <input && + test_cmp marks.out combined.marks +' test_expect_success 'R: feature relative-marks should be honoured' ' - mkdir -p .git/info/fast-import/ && - cp marks.new .git/info/fast-import/relative.in && - git fast-import <input && - test_cmp marks.new .git/info/fast-import/relative.out -' + cat >input <<-EOF && + feature relative-marks + feature import-marks=relative.in + feature export-marks=relative.out + EOF -cat >input <<EOF -feature relative-marks -feature import-marks=relative.in -feature no-relative-marks -feature export-marks=non-relative.out -EOF + mkdir -p .git/info/fast-import/ && + cp marks.new .git/info/fast-import/relative.in && + git fast-import <input && + test_cmp marks.new .git/info/fast-import/relative.out +' test_expect_success 'R: feature no-relative-marks should be honoured' ' - git fast-import <input && - test_cmp marks.new non-relative.out + cat >input <<-EOF && + feature relative-marks + feature import-marks=relative.in + feature no-relative-marks + feature export-marks=non-relative.out + EOF + + git fast-import <input && + test_cmp marks.new non-relative.out ' test_expect_success 'R: feature ls supported' ' @@ -2330,12 +2344,12 @@ test_expect_success !MINGW 'R: in-stream cat-blob-fd not respected' ' cat-blob $blob EOF test_cmp expect actual.3 && - test_cmp empty actual.1 && + test_must_be_empty actual.1 && git fast-import 3>actual.3 >actual.1 <<-EOF && option cat-blob-fd=3 cat-blob $blob EOF - test_cmp empty actual.3 && + test_must_be_empty actual.3 && test_cmp expect actual.1 ' @@ -2549,17 +2563,17 @@ test_expect_success PIPE 'R: print staged blob within commit' ' test_cmp expect actual ' -cat >input << EOF -option git quiet -blob -data 3 -hi +test_expect_success 'R: quiet option results in no stats being output' ' + cat >input <<-EOF && + option git quiet + blob + data 3 + hi -EOF + EOF -test_expect_success 'R: quiet option results in no stats being output' ' - cat input | git fast-import 2> output && - test_cmp empty output + cat input | git fast-import 2> output && + test_must_be_empty output ' test_expect_success 'R: feature done means terminating "done" is mandatory' ' @@ -2604,16 +2618,16 @@ test_expect_success 'R: terminating "done" within commit' ' test_cmp expect actual ' -cat >input <<EOF -option git non-existing-option -EOF - test_expect_success 'R: die on unknown option' ' - test_must_fail git fast-import <input + cat >input <<-EOF && + option git non-existing-option + EOF + + test_must_fail git fast-import <input ' test_expect_success 'R: unknown commandline options are rejected' '\ - test_must_fail git fast-import --non-existing-option < /dev/null + test_must_fail git fast-import --non-existing-option < /dev/null ' test_expect_success 'R: die on invalid option argument' ' @@ -2624,41 +2638,41 @@ test_expect_success 'R: die on invalid option argument' ' test_must_fail git fast-import --depth="5 elephants" </dev/null ' -cat >input <<EOF -option non-existing-vcs non-existing-option -EOF - test_expect_success 'R: ignore non-git options' ' - git fast-import <input + cat >input <<-EOF && + option non-existing-vcs non-existing-option + EOF + + git fast-import <input ' ## ## R: very large blobs ## -blobsize=$((2*1024*1024 + 53)) -test-genrandom bar $blobsize >expect -cat >input <<INPUT_END -commit refs/heads/big-file -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -R - big file -COMMIT - -M 644 inline big1 -data $blobsize -INPUT_END -cat expect >>input -cat >>input <<INPUT_END -M 644 inline big2 -data $blobsize -INPUT_END -cat expect >>input -echo >>input - -test_expect_success \ - 'R: blob bigger than threshold' \ - 'test_create_repo R && - git --git-dir=R/.git fast-import --big-file-threshold=1 <input' +test_expect_success 'R: blob bigger than threshold' ' + blobsize=$((2*1024*1024 + 53)) && + test-genrandom bar $blobsize >expect && + cat >input <<-INPUT_END && + commit refs/heads/big-file + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + R - big file + COMMIT + + M 644 inline big1 + data $blobsize + INPUT_END + cat expect >>input && + cat >>input <<-INPUT_END && + M 644 inline big2 + data $blobsize + INPUT_END + cat expect >>input && + echo >>input && + + test_create_repo R && + git --git-dir=R/.git fast-import --big-file-threshold=1 <input +' test_expect_success 'R: verify created pack' ' ( @@ -2667,17 +2681,18 @@ test_expect_success 'R: verify created pack' ' ) ' -test_expect_success \ - 'R: verify written objects' \ - 'git --git-dir=R/.git cat-file blob big-file:big1 >actual && - test_cmp_bin expect actual && - a=$(git --git-dir=R/.git rev-parse big-file:big1) && - b=$(git --git-dir=R/.git rev-parse big-file:big2) && - test $a = $b' -test_expect_success \ - 'R: blob appears only once' \ - 'n=$(grep $a verify | wc -l) && - test 1 = $n' +test_expect_success 'R: verify written objects' ' + git --git-dir=R/.git cat-file blob big-file:big1 >actual && + test_cmp_bin expect actual && + a=$(git --git-dir=R/.git rev-parse big-file:big1) && + b=$(git --git-dir=R/.git rev-parse big-file:big2) && + test $a = $b +' + +test_expect_success 'R: blob appears only once' ' + n=$(grep $a verify | wc -l) && + test 1 = $n +' ### ### series S @@ -2710,46 +2725,46 @@ test_expect_success \ # # Invalid dataref .. # -test_tick - -cat >input <<INPUT_END -commit refs/heads/S -mark :301 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -commit 1 -COMMIT -M 100644 inline hello.c -data <<BLOB -blob 1 -BLOB - -commit refs/heads/S -mark :302 -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -commit 2 -COMMIT -from :301 -M 100644 inline hello.c -data <<BLOB -blob 2 -BLOB - -blob -mark :403 -data <<BLOB -blob 3 -BLOB - -blob -mark :202 -data <<BLOB -note 2 -BLOB -INPUT_END - test_expect_success 'S: initialize for S tests' ' + test_tick && + + cat >input <<-INPUT_END && + commit refs/heads/S + mark :301 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit 1 + COMMIT + M 100644 inline hello.c + data <<BLOB + blob 1 + BLOB + + commit refs/heads/S + mark :302 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit 2 + COMMIT + from :301 + M 100644 inline hello.c + data <<BLOB + blob 2 + BLOB + + blob + mark :403 + data <<BLOB + blob 3 + BLOB + + blob + mark :202 + data <<BLOB + note 2 + BLOB + INPUT_END + git fast-import --export-marks=marks <input ' @@ -3001,103 +3016,103 @@ test_expect_success 'T: empty reset doesnt delete branch' ' ### series U (filedelete) ### -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -test setup -COMMIT -M 100644 inline hello.c -data <<BLOB -blob 1 -BLOB -M 100644 inline good/night.txt -data <<BLOB -sleep well -BLOB -M 100644 inline good/bye.txt -data <<BLOB -au revoir -BLOB - -INPUT_END - test_expect_success 'U: initialize for U tests' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + test setup + COMMIT + M 100644 inline hello.c + data <<BLOB + blob 1 + BLOB + M 100644 inline good/night.txt + data <<BLOB + sleep well + BLOB + M 100644 inline good/bye.txt + data <<BLOB + au revoir + BLOB + + INPUT_END + git fast-import <input ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -delete good/night.txt -COMMIT -from refs/heads/U^0 -D good/night.txt +test_expect_success 'U: filedelete file succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + delete good/night.txt + COMMIT + from refs/heads/U^0 + D good/night.txt -INPUT_END + INPUT_END -test_expect_success 'U: filedelete file succeeds' ' git fast-import <input ' -cat >expect <<EOF -:100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D good/night.txt -EOF +test_expect_success 'U: validate file delete result' ' + cat >expect <<-EOF && + :100644 000000 2907ebb4bf85d91bf0716bb3bd8a68ef48d6da76 0000000000000000000000000000000000000000 D good/night.txt + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate file delete result' ' compare_diff_raw expect actual ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -delete good dir -COMMIT -from refs/heads/U^0 -D good +test_expect_success 'U: filedelete directory succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + delete good dir + COMMIT + from refs/heads/U^0 + D good -INPUT_END + INPUT_END -test_expect_success 'U: filedelete directory succeeds' ' git fast-import <input ' -cat >expect <<EOF -:100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D good/bye.txt -EOF +test_expect_success 'U: validate directory delete result' ' + cat >expect <<-EOF && + :100644 000000 69cb75792f55123d8389c156b0b41c2ff00ed507 0000000000000000000000000000000000000000 D good/bye.txt + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate directory delete result' ' compare_diff_raw expect actual ' -cat >input <<INPUT_END -commit refs/heads/U -committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE -data <<COMMIT -must succeed -COMMIT -from refs/heads/U^0 -D "" +test_expect_success 'U: filedelete root succeeds' ' + cat >input <<-INPUT_END && + commit refs/heads/U + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + must succeed + COMMIT + from refs/heads/U^0 + D "" -INPUT_END + INPUT_END -test_expect_success 'U: filedelete root succeeds' ' - git fast-import <input + git fast-import <input ' -cat >expect <<EOF -:100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D hello.c -EOF +test_expect_success 'U: validate root delete result' ' + cat >expect <<-EOF && + :100644 000000 c18147dc648481eeb65dc5e66628429a64843327 0000000000000000000000000000000000000000 D hello.c + EOF -git diff-tree -M -r U^1 U >actual + git diff-tree -M -r U^1 U >actual && -test_expect_success 'U: validate root delete result' ' compare_diff_raw expect actual ' diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 90d41ed95..0730f18d0 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -241,6 +241,22 @@ test_expect_success 'unresolvable host in P4PORT should display error' ' ) ' +test_expect_success 'submit from detached head' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + git checkout p4/master && + >detached_head_test && + git add detached_head_test && + git commit -m "add detached_head" && + git config git-p4.skipSubmitEdit true && + git p4 submit && + git p4 rebase && + git log p4/master | grep detached_head + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 1f74a8838..593152817 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -389,7 +389,7 @@ test_expect_success 'description with Jobs section and bogus following text' ' ( cd "$cli" && p4 revert desc6 && - rm desc6 + rm -f desc6 ) ' diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 6b68777b9..af82049f8 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -273,11 +273,36 @@ test_expect_success 'prompt - dirty status indicator - dirty index and worktree' test_cmp expected "$actual" ' -test_expect_success 'prompt - dirty status indicator - before root commit' ' - printf " (master #)" >expected && +test_expect_success 'prompt - dirty status indicator - orphan branch - clean' ' + printf " (orphan #)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + git reset --hard && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - orphan branch - dirty index' ' + printf " (orphan +)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + ( + GIT_PS1_SHOWDIRTYSTATE=y && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + +test_expect_success 'prompt - dirty status indicator - orphan branch - dirty index and worktree' ' + printf " (orphan *+)" >expected && + test_when_finished "git checkout master" && + git checkout --orphan orphan && + >file && ( GIT_PS1_SHOWDIRTYSTATE=y && - cd otherrepo && __git_ps1 >"$actual" ) && test_cmp expected "$actual" diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 73e37a1f6..c64e5a502 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -569,6 +569,21 @@ test_line_count () { fi } +# Returns success if a comma separated string of keywords ($1) contains a +# given keyword ($2). +# Examples: +# `list_contains "foo,bar" bar` returns 0 +# `list_contains "foo" bar` returns 1 + +list_contains () { + case ",$1," in + *,$2,*) + return 0 + ;; + esac + return 1 +} + # This is not among top-level (test_expect_success | test_expect_failure) # but is a prefix that can be used in the test script, like: # @@ -582,18 +597,34 @@ test_line_count () { # the failure could be due to a segv. We want a controlled failure. test_must_fail () { + case "$1" in + ok=*) + _test_ok=${1#ok=} + shift + ;; + *) + _test_ok= + ;; + esac "$@" exit_code=$? - if test $exit_code = 0; then + if test $exit_code -eq 0 && ! list_contains "$_test_ok" success + then echo >&2 "test_must_fail: command succeeded: $*" return 1 - elif test $exit_code -gt 129 && test $exit_code -le 192; then + elif test $exit_code -eq 141 && list_contains "$_test_ok" sigpipe + then + return 0 + elif test $exit_code -gt 129 && test $exit_code -le 192 + then echo >&2 "test_must_fail: died by signal: $*" return 1 - elif test $exit_code = 127; then + elif test $exit_code -eq 127 + then echo >&2 "test_must_fail: command not found: $*" return 1 - elif test $exit_code = 126; then + elif test $exit_code -eq 126 + then echo >&2 "test_must_fail: valgrind error: $*" return 1 fi @@ -612,16 +643,7 @@ test_must_fail () { # because we want to notice if it fails due to segv. test_might_fail () { - "$@" - exit_code=$? - if test $exit_code -gt 129 && test $exit_code -le 192; then - echo >&2 "test_might_fail: died by signal: $*" - return 1 - elif test $exit_code = 127; then - echo >&2 "test_might_fail: command not found: $*" - return 1 - fi - return 0 + test_must_fail ok=success "$@" } # Similar to test_must_fail and test_might_fail, but check that a diff --git a/transport.c b/transport.c index 1171b0594..67f366687 100644 --- a/transport.c +++ b/transport.c @@ -15,6 +15,7 @@ #include "submodule.h" #include "string-list.h" #include "sha1-array.h" +#include "sigchain.h" /* rsync support */ @@ -1127,6 +1128,8 @@ static int run_pre_push_hook(struct transport *transport, return -1; } + sigchain_push(SIGPIPE, SIG_IGN); + strbuf_init(&buf, 256); for (r = remote_refs; r; r = r->next) { @@ -1140,8 +1143,10 @@ static int run_pre_push_hook(struct transport *transport, r->peer_ref->name, oid_to_hex(&r->new_oid), r->name, oid_to_hex(&r->old_oid)); - if (write_in_full(proc.in, buf.buf, buf.len) != buf.len) { - ret = -1; + if (write_in_full(proc.in, buf.buf, buf.len) < 0) { + /* We do not mind if a hook does not read all refs. */ + if (errno != EPIPE) + ret = -1; break; } } @@ -1152,6 +1157,8 @@ static int run_pre_push_hook(struct transport *transport, if (!ret) ret = x; + sigchain_pop(SIGPIPE); + x = finish_command(&proc); if (!ret) ret = x; diff --git a/wt-status.c b/wt-status.c index 82b87c1ea..bba25960b 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1317,15 +1317,14 @@ static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, target += strlen(" to "); strbuf_reset(&cb->buf); hashcpy(cb->nsha1, nsha1); - for (end = target; *end && *end != '\n'; end++) - ; - if (!memcmp(target, "HEAD", end - target)) { + end = strchrnul(target, '\n'); + strbuf_add(&cb->buf, target, end - target); + if (!strcmp(cb->buf.buf, "HEAD")) { /* HEAD is relative. Resolve it to the right reflog entry. */ + strbuf_reset(&cb->buf); strbuf_addstr(&cb->buf, find_unique_abbrev(nsha1, DEFAULT_ABBREV)); - return 1; } - strbuf_add(&cb->buf, target, end - target); return 1; } |