diff options
63 files changed, 2393 insertions, 536 deletions
diff --git a/.gitignore b/.gitignore index 63c918c66..e0b91befb 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ git-clone git-commit git-commit-tree git-config -git-convert-objects git-count-objects git-cvsexportcommit git-cvsimport diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt new file mode 100644 index 000000000..ceee85723 --- /dev/null +++ b/Documentation/RelNotes-1.5.4.txt @@ -0,0 +1,35 @@ +GIT v1.5.4 Release Notes +======================== + +Updates since v1.5.3 +-------------------- + + * git-reset is now built-in. + + * git-send-email can optionally talk over ssmtp and use SMTP-AUTH. + + * git-rebase learned --whitespace option. + + * git-remote knows --mirror mode. + + * git-merge can call the "post-merge" hook. + + * git-pack-objects can optionally run deltification with multiple threads. + + * git-archive can optionally substitute keywords in files marked with + export-subst attribute. + + * Various Perforce importer updates. + +Fixes since v1.5.3 +------------------ + +All of the fixes in v1.5.3 maintenance series are included in +this release, unless otherwise noted. + +-- +exec >/var/tmp/1 +O=v1.5.3.2-99-ge4b2890 +echo O=`git describe refs/heads/master` +git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint + diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 4ee76eaf9..1061fd8bc 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -94,7 +94,6 @@ git-clone mainporcelain git-commit mainporcelain git-commit-tree plumbingmanipulators git-config ancillarymanipulators -git-convert-objects ancillarymanipulators git-count-objects ancillaryinterrogators git-cvsexportcommit foreignscminterface git-cvsimport foreignscminterface diff --git a/Documentation/config.txt b/Documentation/config.txt index 866e0534b..015910f27 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -630,9 +630,17 @@ pack.deltaCacheSize:: A value of 0 means no limit. Defaults to 0. pack.deltaCacheLimit:: - The maxium size of a delta, that is cached in + The maximum size of a delta, that is cached in gitlink:git-pack-objects[1]. Defaults to 1000. +pack.threads:: + Specifies the number of threads to spawn when searching for best + delta matches. This requires that gitlink:git-pack-objects[1] + be compiled with pthreads otherwise this option is ignored with a + warning. This is meant to reduce packing time on multiprocessor + machines. The required amount of memory for the delta search window + is however multiplied by the number of threads. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index d18259d93..5237ab0c0 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -169,6 +169,14 @@ base-name:: length, this option typically shrinks the resulting packfile by 3-5 per-cent. +--threads=<n>:: + Specifies the number of threads to spawn when searching for best + delta matches. This requires that pack-objects be compiled with + pthreads otherwise this option is ignored with a warning. + This is meant to reduce packing time on multiprocessor machines. + The required amount of memory for the delta search window is + however multiplied by the number of threads. + --index-version=<version>[,<offset>]:: This is intended to be used by the test suite only. It allows to force the version for the generated pack index, and to force diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 61b1810db..0858fa8a6 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-C<n>] - [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>] +'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] + [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] + [--onto <newbase>] <upstream> [<branch>] 'git-rebase' --continue | --skip | --abort DESCRIPTION @@ -209,6 +210,10 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +--whitespace=<nowarn|warn|error|error-all|strip>:: + This flag is passed to the `git-apply` program + (see gitlink:git-apply[1]) that applies the patch. + -i, \--interactive:: Make a list of the commits which are about to be rebased. Let the user edit that list before rebasing. This mode can also be used to diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 61a6022ce..94b9f1777 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-remote' -'git-remote' add [-t <branch>] [-m <branch>] [-f] <name> <url> +'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url> 'git-remote' show <name> 'git-remote' prune <name> 'git-remote' update [group] @@ -45,6 +45,10 @@ multiple branches without grabbing all branches. With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set up to point at remote's `<master>` branch instead of whatever branch the `HEAD` at the remote repository actually points at. ++ +In mirror mode, enabled with `--mirror`, the refs will not be stored +in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option +only makes sense in bare repositories. 'show':: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 16bfd7be2..1ec61affa 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -75,6 +75,12 @@ The --cc option must be repeated for each user you want on the cc list. Make git-send-email less verbose. One line per email should be all that is output. +--identity:: + A configuration identity. When given, causes values in the + 'sendemail.<identity>' subsection to take precedence over + values in the 'sendemail' section. The default identity is + the value of 'sendemail.identity'. + --smtp-server:: If set, specifies the outgoing SMTP server to use (e.g. `smtp.example.com` or a raw IP address). Alternatively it can @@ -85,6 +91,17 @@ The --cc option must be repeated for each user you want on the cc list. `/usr/lib/sendmail` if such program is available, or `localhost` otherwise. +--smtp-user, --smtp-pass:: + Username and password for SMTP-AUTH. Defaults are the values of + the configuration values 'sendemail.smtpuser' and + 'sendemail.smtppass', but see also 'sendemail.identity'. + If not set, authentication is not attempted. + +--smtp-ssl:: + If set, connects to the SMTP server using SSL. + Default is the value of the 'sendemail.smtpssl' configuration value; + if that is unspecified, does not use SSL. + --subject:: Specify the initial subject of the email thread. Only necessary if --compose is also set. If --compose @@ -122,6 +139,13 @@ The --to option must be repeated for each user you want on the to list. CONFIGURATION ------------- +sendemail.identity:: + The default configuration identity. When specified, + 'sendemail.<identity>.<item>' will have higher precedence than + 'sendemail.<item>'. This is useful to declare multiple SMTP + identities and to hoist sensitive authentication information + out of the repository and into the global configuation file. + sendemail.aliasesfile:: To avoid typing long email addresses, point this to one or more email aliases files. You must also supply 'sendemail.aliasfiletype'. @@ -141,7 +165,16 @@ sendemail.chainreplyto:: parameter. sendemail.smtpserver:: - Default smtp server to use. + Default SMTP server to use. + +sendemail.smtpuser:: + Default SMTP-AUTH username. + +sendemail.smtppass:: + Default SMTP-AUTH password. + +sendemail.smtpssl:: + Boolean value specifying the default to the '--smtp-ssl' parameter. Author ------ diff --git a/Documentation/git.txt b/Documentation/git.txt index a7cd91acc..cb59639f7 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -46,6 +46,7 @@ Documentation for older releases are available here: * link:v1.5.3/git.html[documentation for release 1.5.3] * release notes for + link:RelNotes-1.5.3.2.txt[1.5.3.2], link:RelNotes-1.5.3.1.txt[1.5.3.1]. * release notes for diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index dd51aa11e..20cf8ff81 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -409,6 +409,23 @@ frotz unspecified ---------------------------------------------------------------- +Creating an archive +~~~~~~~~~~~~~~~~~~~ + +`export-subst` +^^^^^^^^^^^^^^ + +If the attribute `export-subst` is set for a file then git will expand +several placeholders when adding this file to an archive. The +expansion depends on the availability of a commit ID, i.e. if +gitlink:git-archive[1] has been given a tree instead of a commit or a +tag then no replacement will be done. The placeholders are the same +as those for the option `--pretty=format:` of gitlink:git-log[1], +except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` +in the file. E.g. the string `$Format:%H$` will be replaced by the +commit hash. + + GIT --- Part of the gitlink:git[7] suite diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index c39edc57c..58b954759 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -87,6 +87,19 @@ parameter, and is invoked after a commit is made. This hook is meant primarily for notification, and cannot affect the outcome of `git-commit`. +post-merge +----------- + +This hook is invoked by `git-merge`, which happens when a `git pull` +is done on a local repository. The hook takes a single parameter, a status +flag specifying whether or not the merge being done was a squash merge. +This hook cannot affect the outcome of `git-merge`. + +This hook can be used in conjunction with a corresponding pre-commit hook to +save and restore any form of metadata associated with the working tree +(eg: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl +for an example of how to do this. + [[pre-receive]] pre-receive ----------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 7fbde233b..3c0032cec 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.3.2.GIT +DEF_VER=v1.5.3.GIT LF=' ' @@ -28,6 +28,8 @@ all:: # # Define NO_STRCASESTR if you don't have strcasestr. # +# Define NO_MEMMEM if you don't have memmem. +# # Define NO_STRLCPY if you don't have strlcpy. # # Define NO_STRTOUMAX if you don't have strtoumax in the C library. @@ -122,6 +124,9 @@ all:: # If not set it defaults to the bare 'wish'. If it is set to the empty # string then NO_TCLTK will be forced (this is used by configure script). # +# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit +# parallel delta searching when packing objects. +# GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -206,7 +211,7 @@ SCRIPT_SH = \ git-ls-remote.sh \ git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh git-rebase--interactive.sh \ - git-repack.sh git-request-pull.sh git-reset.sh \ + git-repack.sh git-request-pull.sh \ git-sh-setup.sh \ git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ @@ -228,7 +233,7 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-convert-objects$X git-fetch-pack$X \ + git-fetch-pack$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-fast-import$X \ git-daemon$X \ @@ -353,6 +358,7 @@ BUILTIN_OBJS = \ builtin-reflog.o \ builtin-config.o \ builtin-rerere.o \ + builtin-reset.o \ builtin-rev-list.o \ builtin-rev-parse.o \ builtin-revert.o \ @@ -396,12 +402,14 @@ ifeq ($(uname_S),Darwin) NEEDS_LIBICONV = YesPlease OLD_ICONV = UnfortunatelyYes NO_STRLCPY = YesPlease + NO_MEMMEM = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NO_HSTRERROR = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease @@ -424,6 +432,7 @@ ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease NO_D_INO_IN_DIRENT = YesPlease NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NO_SYMLINK_HEAD = YesPlease NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes @@ -437,11 +446,13 @@ ifeq ($(uname_O),Cygwin) endif ifeq ($(uname_S),FreeBSD) NEEDS_LIBICONV = YesPlease + NO_MEMMEM = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib @@ -456,6 +467,7 @@ ifeq ($(uname_S),NetBSD) endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease + NO_MEMMEM = YesPlease NO_STRLCPY = YesPlease NEEDS_LIBICONV=YesPlease endif @@ -467,6 +479,7 @@ ifeq ($(uname_S),IRIX64) NO_IPV6=YesPlease NO_SETENV=YesPlease NO_STRCASESTR=YesPlease + NO_MEMMEM = YesPlease NO_STRLCPY = YesPlease NO_SOCKADDR_STORAGE=YesPlease SHELL_PATH=/usr/gnu/bin/bash @@ -661,6 +674,15 @@ ifdef NO_HSTRERROR COMPAT_CFLAGS += -DNO_HSTRERROR COMPAT_OBJS += compat/hstrerror.o endif +ifdef NO_MEMMEM + COMPAT_CFLAGS += -DNO_MEMMEM + COMPAT_OBJS += compat/memmem.o +endif + +ifdef THREADED_DELTA_SEARCH + BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH + EXTLIBS += -lpthread +endif ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks @@ -1 +1 @@ -Documentation/RelNotes-1.5.3.3.txt
\ No newline at end of file +Documentation/RelNotes-1.5.4.txt
\ No newline at end of file diff --git a/archive-tar.c b/archive-tar.c index 66fe3e375..c0d95dab0 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -17,6 +17,7 @@ static unsigned long offset; static time_t archive_time; static int tar_umask = 002; static int verbose; +static const struct commit *commit; /* writes out the whole block, but only if it is full */ static void write_if_needed(void) @@ -285,7 +286,8 @@ static int write_tar_entry(const unsigned char *sha1, buffer = NULL; size = 0; } else { - buffer = convert_sha1_file(path.buf, sha1, mode, &type, &size); + buffer = sha1_file_to_archive(path.buf, sha1, mode, &type, + &size, commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); } @@ -304,6 +306,7 @@ int write_tar_archive(struct archiver_args *args) archive_time = args->time; verbose = args->verbose; + commit = args->commit; if (args->commit_sha1) write_global_extended_header(args->commit_sha1); diff --git a/archive-zip.c b/archive-zip.c index 444e1623d..74e30f620 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -12,6 +12,7 @@ static int verbose; static int zip_date; static int zip_time; +static const struct commit *commit; static unsigned char *zip_dir; static unsigned int zip_dir_size; @@ -191,11 +192,13 @@ static int write_zip_entry(const unsigned char *sha1, compressed_size = 0; } else if (S_ISREG(mode) || S_ISLNK(mode)) { method = 0; - attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0; + attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : + (mode & 0111) ? ((mode) << 16) : 0; if (S_ISREG(mode) && zlib_compression_level != 0) method = 8; result = 0; - buffer = convert_sha1_file(path, sha1, mode, &type, &size); + buffer = sha1_file_to_archive(path, sha1, mode, &type, &size, + commit); if (!buffer) die("cannot read %s", sha1_to_hex(sha1)); crc = crc32(crc, buffer, size); @@ -229,7 +232,8 @@ static int write_zip_entry(const unsigned char *sha1, } copy_le32(dirent.magic, 0x02014b50); - copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0); + copy_le16(dirent.creator_version, + S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0); copy_le16(dirent.version, 10); copy_le16(dirent.flags, 0); copy_le16(dirent.compression_method, method); @@ -316,6 +320,7 @@ int write_zip_archive(struct archiver_args *args) zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE); zip_dir_size = ZIP_DIRECTORY_MIN_SIZE; verbose = args->verbose; + commit = args->commit; if (args->base && plen > 0 && args->base[plen - 1] == '/') { char *base = xstrdup(args->base); @@ -8,6 +8,7 @@ struct archiver_args { const char *base; struct tree *tree; const unsigned char *commit_sha1; + const struct commit *commit; time_t time; const char **pathspec; unsigned int verbose : 1; @@ -42,4 +43,6 @@ extern int write_tar_archive(struct archiver_args *); extern int write_zip_archive(struct archiver_args *); extern void *parse_extra_zip_args(int argc, const char **argv); +extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit); + #endif /* ARCHIVE_H */ diff --git a/builtin-add.c b/builtin-add.c index 3d8b8b4f8..0d7d0ce42 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -103,7 +103,6 @@ static void update_callback(struct diff_queue_struct *q, break; case DIFF_STATUS_DELETED: remove_file_from_cache(path); - cache_tree_invalidate_path(active_cache_tree, path); if (verbose) printf("remove '%s'\n", path); break; diff --git a/builtin-apply.c b/builtin-apply.c index 5cc90e68f..6777231c6 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2430,7 +2430,6 @@ static void remove_file(struct patch *patch, int rmdir_empty) if (update_index) { if (remove_file_from_cache(patch->old_name) < 0) die("unable to remove %s from index", patch->old_name); - cache_tree_invalidate_path(active_cache_tree, patch->old_name); } if (!cached) { if (S_ISGITLINK(patch->old_mode)) { @@ -2585,7 +2584,6 @@ static void create_file(struct patch *patch) mode = S_IFREG | 0644; create_one_file(path, mode, buf, size); add_index_file(path, mode, buf, size); - cache_tree_invalidate_path(active_cache_tree, path); } /* phase zero is to remove, phase one is to create */ diff --git a/builtin-archive.c b/builtin-archive.c index 187491bc1..a90c65ce5 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -10,6 +10,7 @@ #include "exec_cmd.h" #include "pkt-line.h" #include "sideband.h" +#include "attr.h" static const char archive_usage[] = \ "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]"; @@ -80,6 +81,100 @@ static int run_remote_archiver(const char *remote, int argc, return !!rv; } +static void *format_subst(const struct commit *commit, const char *format, + unsigned long *sizep) +{ + unsigned long len = *sizep, result_len = 0; + const char *a = format; + char *result = NULL; + + for (;;) { + const char *b, *c; + char *fmt, *formatted = NULL; + unsigned long a_len, fmt_len, formatted_len, allocated = 0; + + b = memmem(a, len, "$Format:", 8); + if (!b || a + len < b + 9) + break; + c = memchr(b + 8, '$', len - 8); + if (!c) + break; + + a_len = b - a; + fmt_len = c - b - 8; + fmt = xmalloc(fmt_len + 1); + memcpy(fmt, b + 8, fmt_len); + fmt[fmt_len] = '\0'; + + formatted_len = format_commit_message(commit, fmt, &formatted, + &allocated); + free(fmt); + result = xrealloc(result, result_len + a_len + formatted_len); + memcpy(result + result_len, a, a_len); + memcpy(result + result_len + a_len, formatted, formatted_len); + result_len += a_len + formatted_len; + len -= c + 1 - a; + a = c + 1; + } + + if (result && len) { + result = xrealloc(result, result_len + len); + memcpy(result + result_len, a, len); + result_len += len; + } + + *sizep = result_len; + + return result; +} + +static void *convert_to_archive(const char *path, + const void *src, unsigned long *sizep, + const struct commit *commit) +{ + static struct git_attr *attr_export_subst; + struct git_attr_check check[1]; + + if (!commit) + return NULL; + + if (!attr_export_subst) + attr_export_subst = git_attr("export-subst", 12); + + check[0].attr = attr_export_subst; + if (git_checkattr(path, ARRAY_SIZE(check), check)) + return NULL; + if (!ATTR_TRUE(check[0].value)) + return NULL; + + return format_subst(commit, src, sizep); +} + +void *sha1_file_to_archive(const char *path, const unsigned char *sha1, + unsigned int mode, enum object_type *type, + unsigned long *size, + const struct commit *commit) +{ + void *buffer, *converted; + + buffer = read_sha1_file(sha1, type, size); + if (buffer && S_ISREG(mode)) { + converted = convert_to_working_tree(path, buffer, size); + if (converted) { + free(buffer); + buffer = converted; + } + + converted = convert_to_archive(path, buffer, size, commit); + if (converted) { + free(buffer); + buffer = converted; + } + } + + return buffer; +} + static int init_archiver(const char *name, struct archiver *ar) { int rv = -1, i; @@ -109,7 +204,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, const unsigned char *commit_sha1; time_t archive_time; struct tree *tree; - struct commit *commit; + const struct commit *commit; unsigned char sha1[20]; if (get_sha1(name, sha1)) @@ -142,6 +237,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, } ar_args->tree = tree; ar_args->commit_sha1 = commit_sha1; + ar_args->commit = commit; ar_args->time = archive_time; } diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index e2f8ede9a..24c7e6f7d 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -31,24 +31,19 @@ static void show_new(enum object_type type, unsigned char *sha1_new) find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); } -static int update_ref(const char *action, +static int update_ref_env(const char *action, const char *refname, unsigned char *sha1, unsigned char *oldval) { char msg[1024]; char *rla = getenv("GIT_REFLOG_ACTION"); - static struct ref_lock *lock; if (!rla) rla = "(reflog update)"; - snprintf(msg, sizeof(msg), "%s: %s", rla, action); - lock = lock_any_ref_for_update(refname, oldval, 0); - if (!lock) - return 1; - if (write_ref_sha1(lock, sha1, msg) < 0) - return 1; - return 0; + if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg)) + warning("reflog message too long: %.*s...", 50, msg); + return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR); } static int update_local_ref(const char *name, @@ -88,7 +83,7 @@ static int update_local_ref(const char *name, fprintf(stderr, "* %s: storing %s\n", name, note); show_new(type, sha1_new); - return update_ref(msg, name, sha1_new, NULL); + return update_ref_env(msg, name, sha1_new, NULL); } if (!hashcmp(sha1_old, sha1_new)) { @@ -102,7 +97,7 @@ static int update_local_ref(const char *name, if (!strncmp(name, "refs/tags/", 10)) { fprintf(stderr, "* %s: updating with %s\n", name, note); show_new(type, sha1_new); - return update_ref("updating tag", name, sha1_new, NULL); + return update_ref_env("updating tag", name, sha1_new, NULL); } current = lookup_commit_reference(sha1_old); @@ -117,7 +112,7 @@ static int update_local_ref(const char *name, fprintf(stderr, "* %s: fast forward to %s\n", name, note); fprintf(stderr, " old..new: %s..%s\n", oldh, newh); - return update_ref("fast forward", name, sha1_new, sha1_old); + return update_ref_env("fast forward", name, sha1_new, sha1_old); } if (!force) { fprintf(stderr, @@ -131,7 +126,7 @@ static int update_local_ref(const char *name, "* %s: forcing update to non-fast forward %s\n", name, note); fprintf(stderr, " old...new: %s...%s\n", oldh, newh); - return update_ref("forced-update", name, sha1_new, sha1_old); + return update_ref_env("forced-update", name, sha1_new, sha1_old); } static int append_fetch_head(FILE *fp, diff --git a/builtin-mv.c b/builtin-mv.c index 3563216ac..b95b7d286 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -276,11 +276,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) add_file_to_cache(path, verbose); } - for (i = 0; i < deleted.nr; i++) { - const char *path = deleted.items[i].path; - remove_file_from_cache(path); - cache_tree_invalidate_path(active_cache_tree, path); - } + for (i = 0; i < deleted.nr; i++) + remove_file_from_cache(deleted.items[i].path); if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 12509faa7..a15906bdb 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -15,12 +15,16 @@ #include "list-objects.h" #include "progress.h" +#ifdef THREADED_DELTA_SEARCH +#include <pthread.h> +#endif + static const char pack_usage[] = "\ git-pack-objects [{ -q | --progress | --all-progress }] \n\ [--max-pack-size=N] [--local] [--incremental] \n\ [--window=N] [--window-memory=N] [--depth=N] \n\ [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\ - [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ + [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\ [--stdout | base-name] [<ref-list | <object-list]"; struct object_entry { @@ -68,6 +72,7 @@ static int progress = 1; static int window = 10; static uint32_t pack_size_limit; static int depth = 50; +static int delta_search_threads = 1; static int pack_to_stdout; static int num_preferred_base; static struct progress progress_state; @@ -78,7 +83,6 @@ static unsigned long delta_cache_size = 0; static unsigned long max_delta_cache_size = 0; static unsigned long cache_max_small_delta_size = 1000; -static unsigned long window_memory_usage = 0; static unsigned long window_memory_limit = 0; /* @@ -1291,6 +1295,31 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, return 0; } +#ifdef THREADED_DELTA_SEARCH + +static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER; +#define read_lock() pthread_mutex_lock(&read_mutex) +#define read_unlock() pthread_mutex_unlock(&read_mutex) + +static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER; +#define cache_lock() pthread_mutex_lock(&cache_mutex) +#define cache_unlock() pthread_mutex_unlock(&cache_mutex) + +static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER; +#define progress_lock() pthread_mutex_lock(&progress_mutex) +#define progress_unlock() pthread_mutex_unlock(&progress_mutex) + +#else + +#define read_lock() (void)0 +#define read_unlock() (void)0 +#define cache_lock() (void)0 +#define cache_unlock() (void)0 +#define progress_lock() (void)0 +#define progress_unlock() (void)0 + +#endif + /* * We search for deltas _backwards_ in a list sorted by type and * by size, so that we see progressively smaller and smaller files. @@ -1300,7 +1329,7 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, * one. */ static int try_delta(struct unpacked *trg, struct unpacked *src, - unsigned max_depth) + unsigned max_depth, unsigned long *mem_usage) { struct object_entry *trg_entry = trg->entry; struct object_entry *src_entry = src->entry; @@ -1313,12 +1342,6 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, if (trg_entry->type != src_entry->type) return -1; - /* We do not compute delta to *create* objects we are not - * going to pack. - */ - if (trg_entry->preferred_base) - return -1; - /* * We do not bother to try a delta that we discarded * on an earlier try, but only when reusing delta data. @@ -1355,24 +1378,28 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* Load data if not already done */ if (!trg->data) { + read_lock(); trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz); + read_unlock(); if (!trg->data) die("object %s cannot be read", sha1_to_hex(trg_entry->idx.sha1)); if (sz != trg_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(trg_entry->idx.sha1), sz, trg_size); - window_memory_usage += sz; + *mem_usage += sz; } if (!src->data) { + read_lock(); src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz); + read_unlock(); if (!src->data) die("object %s cannot be read", sha1_to_hex(src_entry->idx.sha1)); if (sz != src_size) die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(src_entry->idx.sha1), sz, src_size); - window_memory_usage += sz; + *mem_usage += sz; } if (!src->index) { src->index = create_delta_index(src->data, src_size); @@ -1382,7 +1409,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, warning("suboptimal pack - out of memory"); return 0; } - window_memory_usage += sizeof_delta_index(src->index); + *mem_usage += sizeof_delta_index(src->index); } delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); @@ -1402,17 +1429,27 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, trg_entry->delta_size = delta_size; trg->depth = src->depth + 1; + /* + * Handle memory allocation outside of the cache + * accounting lock. Compiler will optimize the strangeness + * away when THREADED_DELTA_SEARCH is not defined. + */ + if (trg_entry->delta_data) + free(trg_entry->delta_data); + cache_lock(); if (trg_entry->delta_data) { delta_cache_size -= trg_entry->delta_size; - free(trg_entry->delta_data); trg_entry->delta_data = NULL; } - if (delta_cacheable(src_size, trg_size, delta_size)) { - trg_entry->delta_data = xrealloc(delta_buf, delta_size); delta_cache_size += trg_entry->delta_size; - } else + cache_unlock(); + trg_entry->delta_data = xrealloc(delta_buf, delta_size); + } else { + cache_unlock(); free(delta_buf); + } + return 1; } @@ -1429,68 +1466,60 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n) return m; } -static void free_unpacked(struct unpacked *n) +static unsigned long free_unpacked(struct unpacked *n) { - window_memory_usage -= sizeof_delta_index(n->index); + unsigned long freed_mem = sizeof_delta_index(n->index); free_delta_index(n->index); n->index = NULL; if (n->data) { + freed_mem += n->entry->size; free(n->data); n->data = NULL; - window_memory_usage -= n->entry->size; } n->entry = NULL; n->depth = 0; + return freed_mem; } -static void find_deltas(struct object_entry **list, int window, int depth) +static void find_deltas(struct object_entry **list, unsigned list_size, + int window, int depth, unsigned *processed) { - uint32_t i = nr_objects, idx = 0, count = 0, processed = 0; + uint32_t i = list_size, idx = 0, count = 0; unsigned int array_size = window * sizeof(struct unpacked); struct unpacked *array; - int max_depth; + unsigned long mem_usage = 0; - if (!nr_objects) - return; array = xmalloc(array_size); memset(array, 0, array_size); - if (progress) - start_progress(&progress_state, "Deltifying %u objects...", "", nr_result); do { struct object_entry *entry = list[--i]; struct unpacked *n = array + idx; - int j; - - if (!entry->preferred_base) - processed++; - - if (progress) - display_progress(&progress_state, processed); + int j, max_depth, best_base = -1; - if (entry->delta) - /* This happens if we decided to reuse existing - * delta from a pack. "!no_reuse_delta &&" is implied. - */ - continue; - - if (entry->size < 50) - continue; - - if (entry->no_try_delta) - continue; - - free_unpacked(n); + mem_usage -= free_unpacked(n); n->entry = entry; while (window_memory_limit && - window_memory_usage > window_memory_limit && + mem_usage > window_memory_limit && count > 1) { uint32_t tail = (idx + window - count) % window; - free_unpacked(array + tail); + mem_usage -= free_unpacked(array + tail); count--; } + /* We do not compute delta to *create* objects we are not + * going to pack. + */ + if (entry->preferred_base) + goto next; + + progress_lock(); + (*processed)++; + if (progress) + display_progress(&progress_state, *processed); + progress_unlock(); + /* * If the current object is at pack edge, take the depth the * objects that depend on the current object into account @@ -1505,6 +1534,7 @@ static void find_deltas(struct object_entry **list, int window, int depth) j = window; while (--j > 0) { + int ret; uint32_t other_idx = idx + j; struct unpacked *m; if (other_idx >= window) @@ -1512,8 +1542,11 @@ static void find_deltas(struct object_entry **list, int window, int depth) m = array + other_idx; if (!m->entry) break; - if (try_delta(n, m, max_depth) < 0) + ret = try_delta(n, m, max_depth, &mem_usage); + if (ret < 0) break; + else if (ret > 0) + best_base = other_idx; } /* if we made n a delta, and if n is already at max @@ -1523,6 +1556,23 @@ static void find_deltas(struct object_entry **list, int window, int depth) if (entry->delta && depth <= n->depth) continue; + /* + * Move the best delta base up in the window, after the + * currently deltified object, to keep it longer. It will + * be the first base object to be attempted next. + */ + if (entry->delta) { + struct unpacked swap = array[best_base]; + int dist = (window + idx - best_base) % window; + int dst = best_base; + while (dist--) { + int src = (dst + 1) % window; + array[dst] = array[src]; + dst = src; + } + array[dst] = swap; + } + next: idx++; if (count + 1 < window) @@ -1531,9 +1581,6 @@ static void find_deltas(struct object_entry **list, int window, int depth) idx = 0; } while (i > 0); - if (progress) - stop_progress(&progress_state); - for (i = 0; i < window; ++i) { free_delta_index(array[i].index); free(array[i].data); @@ -1541,21 +1588,145 @@ static void find_deltas(struct object_entry **list, int window, int depth) free(array); } +#ifdef THREADED_DELTA_SEARCH + +struct thread_params { + pthread_t thread; + struct object_entry **list; + unsigned list_size; + int window; + int depth; + unsigned *processed; +}; + +static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER; +static struct thread_params *data_requester; + +static void *threaded_find_deltas(void *arg) +{ + struct thread_params *me = arg; + + for (;;) { + pthread_mutex_lock(&data_request); + data_requester = me; + pthread_mutex_unlock(&data_provider); + pthread_mutex_lock(&data_ready); + pthread_mutex_unlock(&data_request); + + if (!me->list_size) + return NULL; + + find_deltas(me->list, me->list_size, + me->window, me->depth, me->processed); + } +} + +static void ll_find_deltas(struct object_entry **list, unsigned list_size, + int window, int depth, unsigned *processed) +{ + struct thread_params *target, p[delta_search_threads]; + int i, ret; + unsigned chunk_size; + + if (delta_search_threads <= 1) { + find_deltas(list, list_size, window, depth, processed); + return; + } + + pthread_mutex_lock(&data_provider); + pthread_mutex_lock(&data_ready); + + for (i = 0; i < delta_search_threads; i++) { + p[i].window = window; + p[i].depth = depth; + p[i].processed = processed; + ret = pthread_create(&p[i].thread, NULL, + threaded_find_deltas, &p[i]); + if (ret) + die("unable to create thread: %s", strerror(ret)); + } + + /* this should be auto-tuned somehow */ + chunk_size = window * 1000; + + do { + unsigned sublist_size = chunk_size; + if (sublist_size > list_size) + sublist_size = list_size; + + /* try to split chunks on "path" boundaries */ + while (sublist_size < list_size && list[sublist_size]->hash && + list[sublist_size]->hash == list[sublist_size-1]->hash) + sublist_size++; + + pthread_mutex_lock(&data_provider); + target = data_requester; + target->list = list; + target->list_size = sublist_size; + pthread_mutex_unlock(&data_ready); + + list += sublist_size; + list_size -= sublist_size; + if (!sublist_size) { + pthread_join(target->thread, NULL); + i--; + } + } while (i); +} + +#else +#define ll_find_deltas find_deltas +#endif + static void prepare_pack(int window, int depth) { struct object_entry **delta_list; - uint32_t i; + uint32_t i, n, nr_deltas; get_object_details(); - if (!window || !depth) + if (!nr_objects || !window || !depth) return; delta_list = xmalloc(nr_objects * sizeof(*delta_list)); - for (i = 0; i < nr_objects; i++) - delta_list[i] = objects + i; - qsort(delta_list, nr_objects, sizeof(*delta_list), type_size_sort); - find_deltas(delta_list, window+1, depth); + nr_deltas = n = 0; + + for (i = 0; i < nr_objects; i++) { + struct object_entry *entry = objects + i; + + if (entry->delta) + /* This happens if we decided to reuse existing + * delta from a pack. "!no_reuse_delta &&" is implied. + */ + continue; + + if (entry->size < 50) + continue; + + if (entry->no_try_delta) + continue; + + if (!entry->preferred_base) + nr_deltas++; + + delta_list[n++] = entry; + } + + if (nr_deltas) { + unsigned nr_done = 0; + if (progress) + start_progress(&progress_state, + "Deltifying %u objects...", "", + nr_deltas); + qsort(delta_list, n, sizeof(*delta_list), type_size_sort); + ll_find_deltas(delta_list, n, window+1, depth, &nr_done); + if (progress) + stop_progress(&progress_state); + if (nr_done != nr_deltas) + die("inconsistency with delta count"); + } free(delta_list); } @@ -1591,6 +1762,17 @@ static int git_pack_config(const char *k, const char *v) cache_max_small_delta_size = git_config_int(k, v); return 0; } + if (!strcmp(k, "pack.threads")) { + delta_search_threads = git_config_int(k, v); + if (delta_search_threads < 1) + die("invalid number of threads specified (%d)", + delta_search_threads); +#ifndef THREADED_DELTA_SEARCH + if (delta_search_threads > 1) + warning("no threads support, ignoring %s", k); +#endif + return 0; + } return git_default_config(k, v); } @@ -1750,6 +1932,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) usage(pack_usage); continue; } + if (!prefixcmp(arg, "--threads=")) { + char *end; + delta_search_threads = strtoul(arg+10, &end, 0); + if (!arg[10] || *end || delta_search_threads < 1) + usage(pack_usage); +#ifndef THREADED_DELTA_SEARCH + if (delta_search_threads > 1) + warning("no threads support, " + "ignoring %s", arg); +#endif + continue; + } if (!prefixcmp(arg, "--depth=")) { char *end; depth = strtoul(arg+8, &end, 0); diff --git a/builtin-reset.c b/builtin-reset.c new file mode 100644 index 000000000..99d5c082a --- /dev/null +++ b/builtin-reset.c @@ -0,0 +1,279 @@ +/* + * "git reset" builtin command + * + * Copyright (c) 2007 Carlos Rica + * + * Based on git-reset.sh, which is + * + * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano + */ +#include "cache.h" +#include "tag.h" +#include "object.h" +#include "commit.h" +#include "run-command.h" +#include "refs.h" +#include "diff.h" +#include "diffcore.h" +#include "tree.h" + +static const char builtin_reset_usage[] = +"git-reset [--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]"; + +static char *args_to_str(const char **argv) +{ + char *buf = NULL; + unsigned long len, space = 0, nr = 0; + + for (; *argv; argv++) { + len = strlen(*argv); + ALLOC_GROW(buf, nr + 1 + len, space); + if (nr) + buf[nr++] = ' '; + memcpy(buf + nr, *argv, len); + nr += len; + } + ALLOC_GROW(buf, nr + 1, space); + buf[nr] = '\0'; + + return buf; +} + +static inline int is_merge(void) +{ + return !access(git_path("MERGE_HEAD"), F_OK); +} + +static int unmerged_files(void) +{ + char b; + ssize_t len; + struct child_process cmd; + const char *argv_ls_files[] = {"ls-files", "--unmerged", NULL}; + + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = argv_ls_files; + cmd.git_cmd = 1; + cmd.out = -1; + + if (start_command(&cmd)) + die("Could not run sub-command: git ls-files"); + + len = xread(cmd.out, &b, 1); + if (len < 0) + die("Could not read output from git ls-files: %s", + strerror(errno)); + finish_command(&cmd); + + return len; +} + +static int reset_index_file(const unsigned char *sha1, int is_hard_reset) +{ + int i = 0; + const char *args[6]; + + args[i++] = "read-tree"; + args[i++] = "-v"; + args[i++] = "--reset"; + if (is_hard_reset) + args[i++] = "-u"; + args[i++] = sha1_to_hex(sha1); + args[i] = NULL; + + return run_command_v_opt(args, RUN_GIT_CMD); +} + +static void print_new_head_line(struct commit *commit) +{ + const char *hex, *dots = "...", *body; + + hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV); + if (!hex) { + hex = sha1_to_hex(commit->object.sha1); + dots = ""; + } + printf("HEAD is now at %s%s", hex, dots); + body = strstr(commit->buffer, "\n\n"); + if (body) { + const char *eol; + size_t len; + body += 2; + eol = strchr(body, '\n'); + len = eol ? eol - body : strlen(body); + printf(" %.*s\n", (int) len, body); + } + else + printf("\n"); +} + +static int update_index_refresh(void) +{ + const char *argv_update_index[] = {"update-index", "--refresh", NULL}; + return run_command_v_opt(argv_update_index, RUN_GIT_CMD); +} + +static void update_index_from_diff(struct diff_queue_struct *q, + struct diff_options *opt, void *data) +{ + int i; + + /* do_diff_cache() mangled the index */ + discard_cache(); + read_cache(); + + for (i = 0; i < q->nr; i++) { + struct diff_filespec *one = q->queue[i]->one; + if (one->mode) { + struct cache_entry *ce; + ce = make_cache_entry(one->mode, one->sha1, one->path, + 0, 0); + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | + ADD_CACHE_OK_TO_REPLACE); + } else + remove_file_from_cache(one->path); + } +} + +static int read_from_tree(const char *prefix, const char **argv, + unsigned char *tree_sha1) +{ + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int index_fd; + struct diff_options opt; + + memset(&opt, 0, sizeof(opt)); + diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt); + opt.output_format = DIFF_FORMAT_CALLBACK; + opt.format_callback = update_index_from_diff; + + index_fd = hold_locked_index(lock, 1); + read_cache(); + if (do_diff_cache(tree_sha1, &opt)) + return 1; + diffcore_std(&opt); + diff_flush(&opt); + return write_cache(index_fd, active_cache, active_nr) || + close(index_fd) || + commit_locked_index(lock); +} + +static void prepend_reflog_action(const char *action, char *buf, size_t size) +{ + const char *sep = ": "; + const char *rla = getenv("GIT_REFLOG_ACTION"); + if (!rla) + rla = sep = ""; + if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) + warning("Reflog action message too long: %.*s...", 50, buf); +} + +enum reset_type { MIXED, SOFT, HARD, NONE }; +static char *reset_type_names[] = { "mixed", "soft", "hard", NULL }; + +int cmd_reset(int argc, const char **argv, const char *prefix) +{ + int i = 1, reset_type = NONE, update_ref_status = 0; + const char *rev = "HEAD"; + unsigned char sha1[20], *orig = NULL, sha1_orig[20], + *old_orig = NULL, sha1_old_orig[20]; + struct commit *commit; + char *reflog_action, msg[1024]; + + git_config(git_default_config); + + reflog_action = args_to_str(argv); + setenv("GIT_REFLOG_ACTION", reflog_action, 0); + + if (i < argc) { + if (!strcmp(argv[i], "--mixed")) { + reset_type = MIXED; + i++; + } + else if (!strcmp(argv[i], "--soft")) { + reset_type = SOFT; + i++; + } + else if (!strcmp(argv[i], "--hard")) { + reset_type = HARD; + i++; + } + } + + if (i < argc && argv[i][0] != '-') + rev = argv[i++]; + + if (get_sha1(rev, sha1)) + die("Failed to resolve '%s' as a valid ref.", rev); + + commit = lookup_commit_reference(sha1); + if (!commit) + die("Could not parse object '%s'.", rev); + hashcpy(sha1, commit->object.sha1); + + if (i < argc && !strcmp(argv[i], "--")) + i++; + else if (i < argc && argv[i][0] == '-') + usage(builtin_reset_usage); + + /* git reset tree [--] paths... can be used to + * load chosen paths from the tree into the index without + * affecting the working tree nor HEAD. */ + if (i < argc) { + if (reset_type == MIXED) + warning("--mixed option is deprecated with paths."); + else if (reset_type != NONE) + die("Cannot do %s reset with paths.", + reset_type_names[reset_type]); + if (read_from_tree(prefix, argv + i, sha1)) + return 1; + return update_index_refresh() ? 1 : 0; + } + if (reset_type == NONE) + reset_type = MIXED; /* by default */ + + /* Soft reset does not touch the index file nor the working tree + * at all, but requires them in a good order. Other resets reset + * the index file to the tree object we are switching to. */ + if (reset_type == SOFT) { + if (is_merge() || unmerged_files()) + die("Cannot do a soft reset in the middle of a merge."); + } + else if (reset_index_file(sha1, (reset_type == HARD))) + die("Could not reset index file to revision '%s'.", rev); + + /* Any resets update HEAD to the head being switched to, + * saving the previous head in ORIG_HEAD before. */ + if (!get_sha1("ORIG_HEAD", sha1_old_orig)) + old_orig = sha1_old_orig; + if (!get_sha1("HEAD", sha1_orig)) { + orig = sha1_orig; + prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg)); + update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + } + else if (old_orig) + delete_ref("ORIG_HEAD", old_orig); + prepend_reflog_action("updating HEAD", msg, sizeof(msg)); + update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); + + switch (reset_type) { + case HARD: + if (!update_ref_status) + print_new_head_line(commit); + break; + case SOFT: /* Nothing else to do. */ + break; + case MIXED: /* Report what has not been updated. */ + update_index_refresh(); + break; + } + + unlink(git_path("MERGE_HEAD")); + unlink(git_path("rr-cache/MERGE_RR")); + unlink(git_path("MERGE_MSG")); + unlink(git_path("SQUASH_MSG")); + + free(reflog_action); + + return update_ref_status; +} diff --git a/builtin-rev-list.c b/builtin-rev-list.c index ac551d59f..389463399 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -189,7 +189,7 @@ static int count_interesting_parents(struct commit *commit) return count; } -static inline int halfway(struct commit_list *p, int distance, int nr) +static inline int halfway(struct commit_list *p, int nr) { /* * Don't short-cut something we are not going to return! @@ -202,8 +202,7 @@ static inline int halfway(struct commit_list *p, int distance, int nr) * 2 and 3 are halfway of 5. * 3 is halfway of 6 but 2 and 4 are not. */ - distance *= 2; - switch (distance - nr) { + switch (2 * weight(p) - nr) { case -1: case 0: case 1: return 1; default: @@ -255,6 +254,30 @@ static void show_list(const char *debug, int counted, int nr, } #endif /* DEBUG_BISECT */ +static struct commit_list *best_bisection(struct commit_list *list, int nr) +{ + struct commit_list *p, *best; + int best_distance = -1; + + best = list; + for (p = list; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (revs.prune_fn && !(flags & TREECHANGE)) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + if (distance > best_distance) { + best = p; + best_distance = distance; + } + } + + return best; +} + /* * zero or positive weight is the number of interesting commits it can * reach, including itself. Especially, weight = 0 means it does not @@ -268,39 +291,12 @@ static void show_list(const char *debug, int counted, int nr, * unknown. After running count_distance() first, they will get zero * or positive distance. */ - -static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all) +static struct commit_list *do_find_bisection(struct commit_list *list, + int nr, int *weights) { - int n, nr, on_list, counted, distance; - struct commit_list *p, *best, *next, *last; - int *weights; - - show_list("bisection 2 entry", 0, 0, list); - - /* - * Count the number of total and tree-changing items on the - * list, while reversing the list. - */ - for (nr = on_list = 0, last = NULL, p = list; - p; - p = next) { - unsigned flags = p->item->object.flags; - - next = p->next; - if (flags & UNINTERESTING) - continue; - p->next = last; - last = p; - if (!revs.prune_fn || (flags & TREECHANGE)) - nr++; - on_list++; - } - list = last; - show_list("bisection 2 sorted", 0, nr, list); + int n, counted; + struct commit_list *p; - *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); counted = 0; for (n = 0, p = list; p; p = p->next) { @@ -349,20 +345,14 @@ static struct commit_list *find_bisection(struct commit_list *list, for (p = list; p; p = p->next) { if (p->item->object.flags & UNINTERESTING) continue; - n = weight(p); - if (n != -2) + if (weight(p) != -2) continue; - distance = count_distance(p); + weight_set(p, count_distance(p)); clear_distance(list); - weight_set(p, distance); /* Does it happen to be at exactly half-way? */ - if (halfway(p, distance, nr)) { - p->next = NULL; - *reaches = distance; - free(weights); + if (halfway(p, nr)) return p; - } counted++; } @@ -399,38 +389,59 @@ static struct commit_list *find_bisection(struct commit_list *list, weight_set(p, weight(q)); /* Does it happen to be at exactly half-way? */ - distance = weight(p); - if (halfway(p, distance, nr)) { - p->next = NULL; - *reaches = distance; - free(weights); + if (halfway(p, nr)) return p; - } } } show_list("bisection 2 counted all", counted, nr, list); /* Then find the best one */ - counted = -1; - best = list; - for (p = list; p; p = p->next) { + return best_bisection(list, nr); +} + +static struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all) +{ + int nr, on_list; + struct commit_list *p, *best, *next, *last; + int *weights; + + show_list("bisection 2 entry", 0, 0, list); + + /* + * Count the number of total and tree-changing items on the + * list, while reversing the list. + */ + for (nr = on_list = 0, last = NULL, p = list; + p; + p = next) { unsigned flags = p->item->object.flags; - if (revs.prune_fn && !(flags & TREECHANGE)) + next = p->next; + if (flags & UNINTERESTING) continue; - distance = weight(p); - if (nr - distance < distance) - distance = nr - distance; - if (distance > counted) { - best = p; - counted = distance; - *reaches = weight(p); - } + p->next = last; + last = p; + if (!revs.prune_fn || (flags & TREECHANGE)) + nr++; + on_list++; } - if (best) + list = last; + show_list("bisection 2 sorted", 0, nr, list); + + *all = nr; + weights = xcalloc(on_list, sizeof(*weights)); + + /* Do the real work of finding bisection commit. */ + best = do_find_bisection(list, nr, weights); + + if (best) { best->next = NULL; + *reaches = weight(best); + } free(weights); + return best; } diff --git a/builtin-rm.c b/builtin-rm.c index 9a808c1bf..3b0677e44 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -227,7 +227,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (remove_file_from_cache(path)) die("git-rm: unable to remove %s", path); - cache_tree_invalidate_path(active_cache_tree, path); } if (show_only) diff --git a/builtin-update-index.c b/builtin-update-index.c index a7a4574f2..55fb679d6 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -195,11 +195,6 @@ static int process_path(const char *path) int len; struct stat st; - /* We probably want to do this in remove_file_from_cache() and - * add_cache_entry() instead... - */ - cache_tree_invalidate_path(active_cache_tree, path); - /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -239,7 +234,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return error("%s: cannot add to the index - missing --add option?", path); report("add '%s'", path); - cache_tree_invalidate_path(active_cache_tree, path); return 0; } @@ -284,7 +278,6 @@ static void update_one(const char *path, const char *prefix, int prefix_length) die("Unable to mark file %s", path); goto free_return; } - cache_tree_invalidate_path(active_cache_tree, path); if (force_remove) { if (remove_file_from_cache(p)) @@ -367,7 +360,6 @@ static void read_index_info(int line_termination) free(path_name); continue; } - cache_tree_invalidate_path(active_cache_tree, path_name); if (!mode) { /* mode == 0 means there is no such path -- remove */ @@ -474,7 +466,6 @@ static int unresolve_one(const char *path) goto free_return; } - cache_tree_invalidate_path(active_cache_tree, path); remove_file_from_cache(path); if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { error("%s: cannot add our version to the index.", path); diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 8339cf19e..fe1f74c9f 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -8,7 +8,6 @@ static const char git_update_ref_usage[] = int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL; - struct ref_lock *lock; unsigned char sha1[20], oldsha1[20]; int i, delete, ref_flags; @@ -62,10 +61,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, ref_flags); - if (!lock) - die("%s: cannot lock the ref", refname); - if (write_ref_sha1(lock, sha1, msg) < 0) - die("%s: cannot update the ref", refname); - return 0; + return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, + ref_flags, DIE_ON_ERR); } diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c index dfcfcd045..cc4c55d7e 100644 --- a/builtin-verify-tag.c +++ b/builtin-verify-tag.c @@ -35,7 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) /* find the length without signature */ len = 0; - while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) { + while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) { eol = memchr(buf + len, '\n', size - len); len += eol ? eol - (buf + len) + 1 : size - len; } @@ -60,6 +60,7 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); extern int cmd_config(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); +extern int cmd_reset(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); @@ -593,7 +593,6 @@ extern void trace_argv_printf(const char **argv, int count, const char *format, /* convert.c */ extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep); extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep); -extern void *convert_sha1_file(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size); /* diff.c */ extern int diff_auto_refresh_index; @@ -787,8 +787,8 @@ static void fill_person(struct interp *table, const char *msg, int len) interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601)); } -static long format_commit_message(const struct commit *commit, - const char *msg, char **buf_p, unsigned long *space_p) +long format_commit_message(const struct commit *commit, const void *format, + char **buf_p, unsigned long *space_p) { struct interp table[] = { { "%H" }, /* commit hash */ @@ -843,6 +843,7 @@ static long format_commit_message(const struct commit *commit, char parents[1024]; int i; enum { HEADER, SUBJECT, BODY } state; + const char *msg = commit->buffer; if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table)) die("invalid interp table!"); @@ -924,7 +925,7 @@ static long format_commit_message(const struct commit *commit, char *buf = *buf_p; unsigned long space = *space_p; - space = interpolate(buf, space, user_format, + space = interpolate(buf, space, format, table, ARRAY_SIZE(table)); if (!space) break; @@ -1165,7 +1166,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, char *buf; if (fmt == CMIT_FMT_USERFORMAT) - return format_commit_message(commit, msg, buf_p, space_p); + return format_commit_message(commit, user_format, buf_p, space_p); encoding = (git_log_output_encoding ? git_log_output_encoding @@ -61,6 +61,7 @@ enum cmit_fmt { }; extern enum cmit_fmt get_commit_format(const char *arg); +extern long format_commit_message(const struct commit *commit, const void *template, char **buf_p, unsigned long *space_p); extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode); /** Removes the first commit from a list sorted by date, and adds all diff --git a/compat/memmem.c b/compat/memmem.c new file mode 100644 index 000000000..cd0d87736 --- /dev/null +++ b/compat/memmem.c @@ -0,0 +1,29 @@ +#include "../git-compat-util.h" + +void *gitmemmem(const void *haystack, size_t haystack_len, + const void *needle, size_t needle_len) +{ + const char *begin = haystack; + const char *last_possible = begin + haystack_len - needle_len; + + /* + * The first occurrence of the empty string is deemed to occur at + * the beginning of the string. + */ + if (needle_len == 0) + return (void *)begin; + + /* + * Sanity check, otherwise the loop might search through the whole + * memory. + */ + if (haystack_len < needle_len) + return NULL; + + for (; begin <= last_possible; begin++) { + if (!memcmp(begin, needle, needle_len)) + return (void *)begin; + } + + return NULL; +} diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index cad842af4..e76093074 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -299,7 +299,6 @@ __git_commands () check-attr) : plumbing;; check-ref-format) : plumbing;; commit-tree) : plumbing;; - convert-objects) : plumbing;; cvsexportcommit) : export;; cvsimport) : import;; cvsserver) : daemon;; diff --git a/convert-objects.c b/contrib/convert-objects/convert-objects.c index 90e7900e6..90e7900e6 100644 --- a/convert-objects.c +++ b/contrib/convert-objects/convert-objects.c diff --git a/Documentation/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt index 9718abf86..9718abf86 100644 --- a/Documentation/git-convert-objects.txt +++ b/contrib/convert-objects/git-convert-objects.txt diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 280557ecd..2d77fd47e 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -97,6 +97,21 @@ if there is already one that displays the same directory." :group 'git :type 'string) +(defcustom git-show-uptodate nil + "Whether to display up-to-date files." + :group 'git + :type 'boolean) + +(defcustom git-show-ignored nil + "Whether to display ignored files." + :group 'git + :type 'boolean) + +(defcustom git-show-unknown t + "Whether to display unknown files." + :group 'git + :type 'boolean) + (defface git-status-face '((((class color) (background light)) (:foreground "purple")) @@ -479,6 +494,27 @@ and returns the process output as a string." (setf (git-fileinfo->orig-name info) nil) (setf (git-fileinfo->needs-refresh info) t)))) +(defun git-set-filenames-state (status files state) + "Set the state of a list of named files." + (when files + (setq files (sort files #'string-lessp)) + (let ((file (pop files)) + (node (ewoc-nth status 0))) + (while (and file node) + (let ((info (ewoc-data node))) + (cond ((string-lessp (git-fileinfo->name info) file) + (setq node (ewoc-next status node))) + ((string-equal (git-fileinfo->name info) file) + (unless (eq (git-fileinfo->state info) state) + (setf (git-fileinfo->state info) state) + (setf (git-fileinfo->rename-state info) nil) + (setf (git-fileinfo->orig-name info) nil) + (setf (git-fileinfo->needs-refresh info) t)) + (setq file (pop files))) + (t (setq file (pop files))))))) + (unless state ;; delete files whose state has been set to nil + (ewoc-filter status (lambda (info) (git-fileinfo->state info)))))) + (defun git-state-code (code) "Convert from a string to a added/deleted/modified state." (case (string-to-char code) @@ -532,19 +568,36 @@ and returns the process output as a string." " " (git-escape-file-name (git-fileinfo->name info)) (git-rename-as-string info)))) -(defun git-insert-fileinfo (status info &optional refresh) - "Insert INFO in the status buffer, optionally refreshing an existing one." - (let ((node (and refresh - (git-find-status-file status (git-fileinfo->name info))))) - (setf (git-fileinfo->needs-refresh info) t) - (when node ;preserve the marked flag - (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node)))) - (if node (setf (ewoc-data node) info) (ewoc-enter-last status info)))) +(defun git-insert-info-list (status infolist) + "Insert a list of file infos in the status buffer, replacing existing ones if any." + (setq infolist (sort infolist + (lambda (info1 info2) + (string-lessp (git-fileinfo->name info1) + (git-fileinfo->name info2))))) + (let ((info (pop infolist)) + (node (ewoc-nth status 0))) + (while info + (setf (git-fileinfo->needs-refresh info) t) + (cond ((not node) + (ewoc-enter-last status info) + (setq info (pop infolist))) + ((string-lessp (git-fileinfo->name (ewoc-data node)) + (git-fileinfo->name info)) + (setq node (ewoc-next status node))) + ((string-equal (git-fileinfo->name (ewoc-data node)) + (git-fileinfo->name info)) + ;; preserve the marked flag + (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))) + (setf (ewoc-data node) info) + (setq info (pop infolist))) + (t + (ewoc-enter-before status node info) + (setq info (pop infolist))))))) (defun git-run-diff-index (status files) "Run git-diff-index on FILES and parse the results into STATUS. Return the list of files that haven't been handled." - (let ((refresh files)) + (let (infolist) (with-temp-buffer (apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files) (goto-char (point-min)) @@ -558,13 +611,14 @@ Return the list of files that haven't been handled." (new-name (match-string 8))) (if new-name ; copy or rename (if (eq ?C (string-to-char state)) - (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh) - (git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh) - (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh) - (git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh)) + (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist) + (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist) + (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist)) + (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist)) (setq files (delete name files)) - (when new-name (setq files (delete new-name files))))))) - files) + (when new-name (setq files (delete new-name files)))))) + (git-insert-info-list status infolist) + files)) (defun git-find-status-file (status file) "Find a given file in the status ewoc and return its node." @@ -576,16 +630,16 @@ Return the list of files that haven't been handled." (defun git-run-ls-files (status files default-state &rest options) "Run git-ls-files on FILES and parse the results into STATUS. Return the list of files that haven't been handled." - (let ((refresh files)) + (let (infolist) (with-temp-buffer - (apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files)) + (apply #'git-run-command t nil "ls-files" "-z" (append options (list "--") files)) (goto-char (point-min)) - (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1) - (let ((state (match-string 1)) - (name (match-string 2))) - (git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh) - (setq files (delete name files)))))) - files) + (while (re-search-forward "\\([^\0]*\\)\0" nil t 1) + (let ((name (match-string 1))) + (push (git-create-fileinfo default-state name) infolist) + (setq files (delete name files))))) + (git-insert-info-list status infolist) + files)) (defun git-run-ls-unmerged (status files) "Run git-ls-files -u on FILES and parse the results into STATUS." @@ -594,9 +648,8 @@ Return the list of files that haven't been handled." (goto-char (point-min)) (let (unmerged-files) (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t) - (let ((node (git-find-status-file status (match-string 1)))) - (when node (push (ewoc-data node) unmerged-files)))) - (git-set-files-state unmerged-files 'unmerged)))) + (push (match-string 1) unmerged-files)) + (git-set-filenames-state status unmerged-files 'unmerged)))) (defun git-get-exclude-files () "Get the list of exclude files to pass to git-ls-files." @@ -608,34 +661,30 @@ Return the list of files that haven't been handled." (push config files)) files)) +(defun git-run-ls-files-with-excludes (status files default-state &rest options) + "Run git-ls-files on FILES with appropriate --exclude-from options." + (let ((exclude-files (git-get-exclude-files))) + (apply #'git-run-ls-files status files default-state + (concat "--exclude-per-directory=" git-per-dir-ignore-file) + (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) + (defun git-update-status-files (files &optional default-state) "Update the status of FILES from the index." (unless git-status (error "Not in git-status buffer.")) - (let* ((status git-status) - (remaining-files + (unless files + (when git-show-uptodate (git-run-ls-files git-status nil 'uptodate "-c"))) + (let* ((remaining-files (if (git-empty-db-p) ; we need some special handling for an empty db - (git-run-ls-files status files 'added "-c") - (git-run-diff-index status files)))) - (git-run-ls-unmerged status files) - (when (or (not files) remaining-files) - (let ((exclude-files (git-get-exclude-files))) - (setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o" - (concat "--exclude-per-directory=" git-per-dir-ignore-file) - (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) - ; mark remaining files with the default state (or remove them if nil) - (when remaining-files - (if default-state - (ewoc-map (lambda (info) - (when (member (git-fileinfo->name info) remaining-files) - (git-set-files-state (list info) default-state)) - nil) - status) - (ewoc-filter status - (lambda (info files) - (not (member (git-fileinfo->name info) files))) - remaining-files))) + (git-run-ls-files git-status files 'added "-c") + (git-run-diff-index git-status files)))) + (git-run-ls-unmerged git-status files) + (when (or remaining-files (and git-show-unknown (not files))) + (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o"))) + (when (or remaining-files (and git-show-ignored (not files))) + (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i"))) + (git-set-filenames-state git-status remaining-files default-state) (git-refresh-files) - (git-refresh-ewoc-hf status))) + (git-refresh-ewoc-hf git-status))) (defun git-marked-files () "Return a list of all marked files, or if none a list containing just the file at cursor position." @@ -853,7 +902,7 @@ Return the list of files that haven't been handled." (defun git-add-file () "Add marked file(s) to the index cache." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unknown)))) + (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) (apply #'git-run-command nil nil "update-index" "--add" "--" files) @@ -871,7 +920,7 @@ Return the list of files that haven't been handled." (defun git-remove-file () "Remove the marked file(s)." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate)))) + (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored)))) (unless files (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files)) (if (yes-or-no-p @@ -916,11 +965,41 @@ Return the list of files that haven't been handled." (interactive) (ewoc-filter git-status (lambda (info) - (not (or (eq (git-fileinfo->state info) 'ignored) - (eq (git-fileinfo->state info) 'uptodate))))) + (case (git-fileinfo->state info) + ('ignored git-show-ignored) + ('uptodate git-show-uptodate) + ('unknown git-show-unknown) + (t t)))) (unless (ewoc-nth git-status 0) ; refresh header if list is empty (git-refresh-ewoc-hf git-status))) +(defun git-toggle-show-uptodate () + "Toogle the option for showing up-to-date files." + (interactive) + (if (setq git-show-uptodate (not git-show-uptodate)) + (git-refresh-status) + (git-remove-handled))) + +(defun git-toggle-show-ignored () + "Toogle the option for showing ignored files." + (interactive) + (if (setq git-show-ignored (not git-show-ignored)) + (progn + (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i") + (git-refresh-files) + (git-refresh-ewoc-hf git-status)) + (git-remove-handled))) + +(defun git-toggle-show-unknown () + "Toogle the option for showing unknown files." + (interactive) + (if (setq git-show-unknown (not git-show-unknown)) + (progn + (git-run-ls-files-with-excludes git-status nil 'unknown "-o") + (git-refresh-files) + (git-refresh-ewoc-hf git-status)) + (git-remove-handled))) + (defun git-setup-diff-buffer (buffer) "Setup a buffer for displaying a diff." (let ((dir default-directory)) @@ -1146,7 +1225,8 @@ Return the list of files that haven't been handled." (unless git-status-mode-map (let ((map (make-keymap)) - (diff-map (make-sparse-keymap))) + (diff-map (make-sparse-keymap)) + (toggle-map (make-sparse-keymap))) (suppress-keymap map) (define-key map "?" 'git-help) (define-key map "h" 'git-help) @@ -1170,6 +1250,7 @@ Return the list of files that haven't been handled." (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) (define-key map "R" 'git-resolve-file) + (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) (define-key map "U" 'git-revert-file) @@ -1186,6 +1267,11 @@ Return the list of files that haven't been handled." (define-key diff-map "h" 'git-diff-file-merge-head) (define-key diff-map "m" 'git-diff-file-mine) (define-key diff-map "o" 'git-diff-file-other) + ; the toggle submap + (define-key toggle-map "u" 'git-toggle-show-uptodate) + (define-key toggle-map "i" 'git-toggle-show-ignored) + (define-key toggle-map "k" 'git-toggle-show-unknown) + (define-key toggle-map "m" 'git-toggle-all-marks) (setq git-status-mode-map map))) ;; git mode should only run in the *git status* buffer @@ -1207,6 +1293,9 @@ Commands: (let ((status (ewoc-create 'git-fileinfo-prettyprint "" ""))) (set (make-local-variable 'git-status) status)) (set (make-local-variable 'list-buffers-directory) default-directory) + (make-local-variable 'git-show-uptodate) + (make-local-variable 'git-show-ignored) + (make-local-variable 'git-show-unknown) (run-hooks 'git-status-mode-hook))) (defun git-find-status-buffer (dir) diff --git a/git-reset.sh b/contrib/examples/git-reset.sh index bafeb52cd..bafeb52cd 100755 --- a/git-reset.sh +++ b/contrib/examples/git-reset.sh diff --git a/contrib/fast-import/git-import.perl b/contrib/fast-import/git-import.perl new file mode 100755 index 000000000..f9fef6db2 --- /dev/null +++ b/contrib/fast-import/git-import.perl @@ -0,0 +1,64 @@ +#!/usr/bin/perl +# +# Performs an initial import of a directory. This is the equivalent +# of doing 'git init; git add .; git commit'. It's a little slower, +# but is meant to be a simple fast-import example. + +use strict; +use File::Find; + +my $USAGE = 'Usage: git-import branch import-message'; +my $branch = shift or die "$USAGE\n"; +my $message = shift or die "$USAGE\n"; + +chomp(my $username = `git config user.name`); +chomp(my $email = `git config user.email`); +die 'You need to set user name and email' + unless $username && $email; + +system('git init'); +open(my $fi, '|-', qw(git fast-import --date-format=now)) + or die "unable to spawn fast-import: $!"; + +print $fi <<EOF; +commit refs/heads/$branch +committer $username <$email> now +data <<MSGEOF +$message +MSGEOF + +EOF + +find( + sub { + if($File::Find::name eq './.git') { + $File::Find::prune = 1; + return; + } + return unless -f $_; + + my $fn = $File::Find::name; + $fn =~ s#^.\/##; + + open(my $in, '<', $_) + or die "unable to open $fn: $!"; + my @st = stat($in) + or die "unable to stat $fn: $!"; + my $len = $st[7]; + + print $fi "M 644 inline $fn\n"; + print $fi "data $len\n"; + while($len > 0) { + my $r = read($in, my $buf, $len < 4096 ? $len : 4096); + defined($r) or die "read error from $fn: $!"; + $r > 0 or die "premature EOF from $fn: $!"; + print $fi $buf; + $len -= $r; + } + print $fi "\n"; + + }, '.' +); + +close($fi); +exit $?; diff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh new file mode 100755 index 000000000..0ca7718d0 --- /dev/null +++ b/contrib/fast-import/git-import.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Performs an initial import of a directory. This is the equivalent +# of doing 'git init; git add .; git commit'. It's a lot slower, +# but is meant to be a simple fast-import example. + +if [ -z "$1" -o -z "$2" ]; then + echo "Usage: git-import branch import-message" + exit 1 +fi + +USERNAME="$(git config user.name)" +EMAIL="$(git config user.email)" + +if [ -z "$USERNAME" -o -z "$EMAIL" ]; then + echo "You need to set user name and email" + exit 1 +fi + +git init + +( + cat <<EOF +commit refs/heads/$1 +committer $USERNAME <$EMAIL> now +data <<MSGEOF +$2 +MSGEOF + +EOF + find * -type f|while read i;do + echo "M 100644 inline $i" + echo data $(stat -c '%s' "$i") + cat "$i" + echo + done + echo +) | git fast-import --date-format=now diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 65c57ac4d..557649a14 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -289,6 +289,19 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent def originP4BranchesExist(): return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master") +def p4ChangesForPaths(depotPaths, changeRange): + assert depotPaths + output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange) + for p in depotPaths])) + + changes = [] + for line in output: + changeNum = line.split(" ")[1] + changes.append(int(changeNum)) + + changes.sort() + return changes + class Command: def __init__(self): self.usage = "usage: %prog [options]" @@ -672,9 +685,8 @@ class P4Submit(Command): f.close(); os.chdir(self.clientPath) - response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath) - if response == "y" or response == "yes": - system("p4 sync ...") + print "Syncronizing p4 checkout..." + system("p4 sync ...") if self.reset: self.firstTime = True @@ -713,10 +725,14 @@ class P4Submit(Command): else: print "All changes applied!" os.chdir(self.oldWorkingDirectory) - response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ") + + sync = P4Sync() + sync.run([]) + + response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ") if response == "y" or response == "yes": rebase = P4Rebase() - rebase.run([]) + rebase.rebase() os.remove(self.configFile) return True @@ -1110,6 +1126,186 @@ class P4Sync(Command): self.keepRepoPath = (d.has_key('options') and ('keepRepoPath' in d['options'])) + def gitRefForBranch(self, branch): + if branch == "main": + return self.refPrefix + "master" + + if len(branch) <= 0: + return branch + + return self.refPrefix + self.projectName + branch + + def gitCommitByP4Change(self, ref, change): + if self.verbose: + print "looking in ref " + ref + " for change %s using bisect..." % change + + earliestCommit = "" + latestCommit = parseRevision(ref) + + while True: + if self.verbose: + print "trying: earliest %s latest %s" % (earliestCommit, latestCommit) + next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip() + if len(next) == 0: + if self.verbose: + print "argh" + return "" + log = extractLogMessageFromGitCommit(next) + settings = extractSettingsGitLog(log) + currentChange = int(settings['change']) + if self.verbose: + print "current change %s" % currentChange + + if currentChange == change: + if self.verbose: + print "found %s" % next + return next + + if currentChange < change: + earliestCommit = "^%s" % next + else: + latestCommit = "%s" % next + + return "" + + def importNewBranch(self, branch, maxChange): + # make fast-import flush all changes to disk and update the refs using the checkpoint + # command so that we can try to find the branch parent in the git history + self.gitStream.write("checkpoint\n\n"); + self.gitStream.flush(); + branchPrefix = self.depotPaths[0] + branch + "/" + range = "@1,%s" % maxChange + #print "prefix" + branchPrefix + changes = p4ChangesForPaths([branchPrefix], range) + if len(changes) <= 0: + return False + firstChange = changes[0] + #print "first change in branch: %s" % firstChange + sourceBranch = self.knownBranches[branch] + sourceDepotPath = self.depotPaths[0] + sourceBranch + sourceRef = self.gitRefForBranch(sourceBranch) + #print "source " + sourceBranch + + branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"]) + #print "branch parent: %s" % branchParentChange + gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange) + if len(gitParent) > 0: + self.initialParents[self.gitRefForBranch(branch)] = gitParent + #print "parent git commit: %s" % gitParent + + self.importChanges(changes) + return True + + def importChanges(self, changes): + cnt = 1 + for change in changes: + description = p4Cmd("describe %s" % change) + self.updateOptionDict(description) + + if not self.silent: + sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes))) + sys.stdout.flush() + cnt = cnt + 1 + + try: + if self.detectBranches: + branches = self.splitFilesIntoBranches(description) + for branch in branches.keys(): + ## HACK --hwn + branchPrefix = self.depotPaths[0] + branch + "/" + + parent = "" + + filesForCommit = branches[branch] + + if self.verbose: + print "branch is %s" % branch + + self.updatedBranches.add(branch) + + if branch not in self.createdBranches: + self.createdBranches.add(branch) + parent = self.knownBranches[branch] + if parent == branch: + parent = "" + else: + fullBranch = self.projectName + branch + if fullBranch not in self.p4BranchesInGit: + if not self.silent: + print("\n Importing new branch %s" % fullBranch); + if self.importNewBranch(branch, change - 1): + parent = "" + self.p4BranchesInGit.append(fullBranch) + if not self.silent: + print("\n Resuming with change %s" % change); + + if self.verbose: + print "parent determined through known branches: %s" % parent + + branch = self.gitRefForBranch(branch) + parent = self.gitRefForBranch(parent) + + if self.verbose: + print "looking for initial parent for %s; current parent is %s" % (branch, parent) + + if len(parent) == 0 and branch in self.initialParents: + parent = self.initialParents[branch] + del self.initialParents[branch] + + self.commit(description, filesForCommit, branch, [branchPrefix], parent) + else: + files = self.extractFilesFromCommit(description) + self.commit(description, files, self.branch, self.depotPaths, + self.initialParent) + self.initialParent = "" + except IOError: + print self.gitError.read() + sys.exit(1) + + def importHeadRevision(self, revision): + print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch) + + details = { "user" : "git perforce import user", "time" : int(time.time()) } + details["desc"] = ("Initial import of %s from the state at revision %s" + % (' '.join(self.depotPaths), revision)) + details["change"] = revision + newestRevision = 0 + + fileCnt = 0 + for info in p4CmdList("files " + + ' '.join(["%s...%s" + % (p, revision) + for p in self.depotPaths])): + + if info['code'] == 'error': + sys.stderr.write("p4 returned an error: %s\n" + % info['data']) + sys.exit(1) + + + change = int(info["change"]) + if change > newestRevision: + newestRevision = change + + if info["action"] == "delete": + # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! + #fileCnt = fileCnt + 1 + continue + + for prop in ["depotFile", "rev", "action", "type" ]: + details["%s%s" % (prop, fileCnt)] = info[prop] + + fileCnt = fileCnt + 1 + + details["change"] = newestRevision + self.updateOptionDict(details) + try: + self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths) + except IOError: + print "IO error with git fast-import. Is your git version recent enough?" + print self.gitError.read() + + def run(self, args): self.depotPaths = [] self.changeRange = "" @@ -1207,7 +1403,7 @@ class P4Sync(Command): self.depotPaths = sorted(args) - self.revision = "" + revision = "" self.users = {} newPaths = [] @@ -1218,15 +1414,15 @@ class P4Sync(Command): if self.changeRange == "@all": self.changeRange = "" elif ',' not in self.changeRange: - self.revision = self.changeRange + revision = self.changeRange self.changeRange = "" p = p[:atIdx] elif p.find("#") != -1: hashIdx = p.index("#") - self.revision = p[hashIdx:] + revision = p[hashIdx:] p = p[:hashIdx] elif self.previousDepotPaths == []: - self.revision = "#head" + revision = "#head" p = re.sub ("\.\.\.$", "", p) if not p.endswith("/"): @@ -1267,49 +1463,8 @@ class P4Sync(Command): self.gitStream = importProcess.stdin self.gitError = importProcess.stderr - if self.revision: - print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch) - - details = { "user" : "git perforce import user", "time" : int(time.time()) } - details["desc"] = ("Initial import of %s from the state at revision %s" - % (' '.join(self.depotPaths), self.revision)) - details["change"] = self.revision - newestRevision = 0 - - fileCnt = 0 - for info in p4CmdList("files " - + ' '.join(["%s...%s" - % (p, self.revision) - for p in self.depotPaths])): - - if info['code'] == 'error': - sys.stderr.write("p4 returned an error: %s\n" - % info['data']) - sys.exit(1) - - - change = int(info["change"]) - if change > newestRevision: - newestRevision = change - - if info["action"] == "delete": - # don't increase the file cnt, otherwise details["depotFile123"] will have gaps! - #fileCnt = fileCnt + 1 - continue - - for prop in ["depotFile", "rev", "action", "type" ]: - details["%s%s" % (prop, fileCnt)] = info[prop] - - fileCnt = fileCnt + 1 - - details["change"] = newestRevision - self.updateOptionDict(details) - try: - self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths) - except IOError: - print "IO error with git fast-import. Is your git version recent enough?" - print self.gitError.read() - + if revision: + self.importHeadRevision(revision) else: changes = [] @@ -1327,15 +1482,7 @@ class P4Sync(Command): if self.verbose: print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), self.changeRange) - assert self.depotPaths - output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange) - for p in self.depotPaths])) - - for line in output: - changeNum = line.split(" ")[1] - changes.append(int(changeNum)) - - changes.sort() + changes = p4ChangesForPaths(self.depotPaths, self.changeRange) if len(self.maxChanges) > 0: changes = changes[:min(int(self.maxChanges), len(changes))] @@ -1350,74 +1497,7 @@ class P4Sync(Command): self.updatedBranches = set() - cnt = 1 - for change in changes: - description = p4Cmd("describe %s" % change) - self.updateOptionDict(description) - - if not self.silent: - sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes))) - sys.stdout.flush() - cnt = cnt + 1 - - try: - if self.detectBranches: - branches = self.splitFilesIntoBranches(description) - for branch in branches.keys(): - ## HACK --hwn - branchPrefix = self.depotPaths[0] + branch + "/" - - parent = "" - - filesForCommit = branches[branch] - - if self.verbose: - print "branch is %s" % branch - - self.updatedBranches.add(branch) - - if branch not in self.createdBranches: - self.createdBranches.add(branch) - parent = self.knownBranches[branch] - if parent == branch: - parent = "" - elif self.verbose: - print "parent determined through known branches: %s" % parent - - # main branch? use master - if branch == "main": - branch = "master" - else: - - ## FIXME - branch = self.projectName + branch - - if parent == "main": - parent = "master" - elif len(parent) > 0: - ## FIXME - parent = self.projectName + parent - - branch = self.refPrefix + branch - if len(parent) > 0: - parent = self.refPrefix + parent - - if self.verbose: - print "looking for initial parent for %s; current parent is %s" % (branch, parent) - - if len(parent) == 0 and branch in self.initialParents: - parent = self.initialParents[branch] - del self.initialParents[branch] - - self.commit(description, filesForCommit, branch, [branchPrefix], parent) - else: - files = self.extractFilesFromCommit(description) - self.commit(description, files, self.branch, self.depotPaths, - self.initialParent) - self.initialParent = "" - except IOError: - print self.gitError.read() - sys.exit(1) + self.importChanges(changes) if not self.silent: print "" @@ -1427,7 +1507,6 @@ class P4Sync(Command): sys.stdout.write("%s " % b) sys.stdout.write("\n") - self.gitStream.close() if importProcess.wait() != 0: die("fast-import failed: %s" % self.gitError.read()) @@ -1448,6 +1527,9 @@ class P4Rebase(Command): sync = P4Sync() sync.run([]) + return self.rebase() + + def rebase(self): [upstream, settings] = findUpstreamBranchPoint() if len(upstream) == 0: die("Cannot find upstream branchpoint for rebase") diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl new file mode 100644 index 000000000..5e3b89def --- /dev/null +++ b/contrib/hooks/setgitperms.perl @@ -0,0 +1,213 @@ +#!/usr/bin/perl +# +# Copyright (c) 2006 Josh England +# +# This script can be used to save/restore full permissions and ownership data +# within a git working tree. +# +# To save permissions/ownership data, place this script in your .git/hooks +# directory and enable a `pre-commit` hook with the following lines: +# #!/bin/sh +# . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -r +# +# To restore permissions/ownership data, place this script in your .git/hooks +# directory and enable a `post-merge` hook with the following lines: +# #!/bin/sh +# . git-sh-setup +# $GIT_DIR/hooks/setgitperms.perl -w +# +use strict; +use Getopt::Long; +use File::Find; +use File::Basename; + +my $usage = +"Usage: setgitperms.perl [OPTION]... <--read|--write> +This program uses a file `.gitmeta` to store/restore permissions and uid/gid +info for all files/dirs tracked by git in the repository. + +---------------------------------Read Mode------------------------------------- +-r, --read Reads perms/etc from working dir into a .gitmeta file +-s, --stdout Output to stdout instead of .gitmeta +-d, --diff Show unified diff of perms file (XOR with --stdout) + +---------------------------------Write Mode------------------------------------ +-w, --write Modify perms/etc in working dir to match the .gitmeta file +-v, --verbose Be verbose + +\n"; + +my ($stdout, $showdiff, $verbose, $read_mode, $write_mode); + +if ((@ARGV < 0) || !GetOptions( + "stdout", \$stdout, + "diff", \$showdiff, + "read", \$read_mode, + "write", \$write_mode, + "verbose", \$verbose, + )) { die $usage; } +die $usage unless ($read_mode xor $write_mode); + +my $topdir = `git-rev-parse --show-cdup` or die "\n"; chomp $topdir; +my $gitdir = $topdir . '.git'; +my $gitmeta = $topdir . '.gitmeta'; + +if ($write_mode) { + # Update the working dir permissions/ownership based on data from .gitmeta + open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n"; + while (defined ($_ = <IN>)) { + chomp; + if (/^(.*) mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) { + # Compare recorded perms to actual perms in the working dir + my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4); + my $fullpath = $topdir . $path; + my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath); + $wmode = sprintf "%04o", $wmode & 07777; + if ($mode ne $wmode) { + $verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n"; + chmod oct($mode), $fullpath; + } + if ($uid != $wuid || $gid != $wgid) { + if ($verbose) { + # Print out user/group names instead of uid/gid + my $pwname = getpwuid($uid); + my $grpname = getgrgid($gid); + my $wpwname = getpwuid($wuid); + my $wgrpname = getgrgid($wgid); + $pwname = $uid if !defined $pwname; + $grpname = $gid if !defined $grpname; + $wpwname = $wuid if !defined $wpwname; + $wgrpname = $wgid if !defined $wgrpname; + + print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n"; + } + chown $uid, $gid, $fullpath; + } + } + else { + warn "Invalid input format in $gitmeta:\n\t$_\n"; + } + } + close IN; +} +elsif ($read_mode) { + # Handle merge conflicts in the .gitperms file + if (-e "$gitdir/MERGE_MSG") { + if (`grep ====== $gitmeta`) { + # Conflict not resolved -- abort the commit + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Resolve the conflict in the $gitmeta file and then run\n"; + print " `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + elsif (`grep $gitmeta $gitdir/MERGE_MSG`) { + # A conflict in .gitmeta has been manually resolved. Verify that + # the working dir perms matches the current .gitmeta perms for + # each file/dir that conflicted. + # This is here because a `setgitperms.perl --write` was not + # performed due to a merge conflict, so permissions/ownership + # may not be consistent with the manually merged .gitmeta file. + my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`; + my @conflict_files; + my $metadiff = 0; + + # Build a list of files that conflicted from the .gitmeta diff + foreach my $line (@conflict_diff) { + if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) { + $metadiff = 1; + } + elsif ($line =~ /^diff --git/) { + $metadiff = 0; + } + elsif ($metadiff && $line =~ /^\+(.*) mode=/) { + push @conflict_files, $1; + } + } + + # Verify that each conflict file now has permissions consistent + # with the .gitmeta file + foreach my $file (@conflict_files) { + my $absfile = $topdir . $file; + my $gm_entry = `grep "^$file mode=" $gitmeta`; + if ($gm_entry =~ /mode=(\d+) uid=(\d+) gid=(\d+)/) { + my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3); + my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile"); + $mode = sprintf("%04o", $mode & 07777); + if (($gm_mode ne $mode) || ($gm_uid != $uid) + || ($gm_gid != $gid)) { + print "PERMISSIONS/OWNERSHIP CONFLICT\n"; + print " Mismatch found for file: $file\n"; + print " Run `.git/hooks/setgitperms.perl --write` to reconcile.\n"; + exit 1; + } + } + else { + print "Warning! Permissions/ownership no longer being tracked for file: $file\n"; + } + } + } + } + + # No merge conflicts -- write out perms/ownership data to .gitmeta file + unless ($stdout) { + open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n"; + } + + my @files = `git-ls-files`; + my %dirs; + + foreach my $path (@files) { + chomp $path; + # We have to manually add stats for parent directories + my $parent = dirname($path); + while (!exists $dirs{$parent}) { + $dirs{$parent} = 1; + next if $parent eq '.'; + printstats($parent); + $parent = dirname($parent); + } + # Now the git-tracked file + printstats($path); + } + + # diff the temporary metadata file to see if anything has changed + # If no metadata has changed, don't overwrite the real file + # This is just so `git commit -a` doesn't try to commit a bogus update + unless ($stdout) { + if (! -e $gitmeta) { + rename "$gitmeta.tmp", $gitmeta; + } + else { + my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`; + if ($diff ne '') { + rename "$gitmeta.tmp", $gitmeta; + } + else { + unlink "$gitmeta.tmp"; + } + if ($showdiff) { + print $diff; + } + } + close OUT; + } + # Make sure the .gitmeta file is tracked + system("git add $gitmeta"); +} + + +sub printstats { + my $path = $_[0]; + $path =~ s/@/\@/g; + my (undef,undef,$mode,undef,$uid,$gid) = lstat($path); + $path =~ s/%/\%/g; + if ($stdout) { + print $path; + printf " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } + else { + print OUT $path; + printf OUT " mode=%04o uid=$uid gid=$gid\n", $mode & 07777; + } +} @@ -687,18 +687,3 @@ char *convert_to_working_tree(const char *path, const char *src, unsigned long * return buf; } - -void *convert_sha1_file(const char *path, const unsigned char *sha1, - unsigned int mode, enum object_type *type, - unsigned long *size) -{ - void *buffer = read_sha1_file(sha1, type, size); - if (S_ISREG(mode) && buffer) { - void *converted = convert_to_working_tree(path, buffer, size); - if (converted) { - free(buffer); - buffer = converted; - } - } - return buffer; -} diff --git a/diff-delta.c b/diff-delta.c index 0dde2f2dc..9e440a929 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -115,7 +115,11 @@ static const unsigned int U[256] = { struct index_entry { const unsigned char *ptr; unsigned int val; - struct index_entry *next; +}; + +struct unpacked_index_entry { + struct index_entry entry; + struct unpacked_index_entry *next; }; struct delta_index { @@ -131,7 +135,8 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) unsigned int i, hsize, hmask, entries, prev_val, *hash_count; const unsigned char *data, *buffer = buf; struct delta_index *index; - struct index_entry *entry, **hash; + struct unpacked_index_entry *entry, **hash; + struct index_entry *packed_entry, **packed_hash; void *mem; unsigned long memsize; @@ -148,28 +153,21 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) hmask = hsize - 1; /* allocate lookup index */ - memsize = sizeof(*index) + - sizeof(*hash) * hsize + + memsize = sizeof(*hash) * hsize + sizeof(*entry) * entries; mem = malloc(memsize); if (!mem) return NULL; - index = mem; - mem = index + 1; hash = mem; mem = hash + hsize; entry = mem; - index->memsize = memsize; - index->src_buf = buf; - index->src_size = bufsize; - index->hash_mask = hmask; memset(hash, 0, hsize * sizeof(*hash)); /* allocate an array to count hash entries */ hash_count = calloc(hsize, sizeof(*hash_count)); if (!hash_count) { - free(index); + free(hash); return NULL; } @@ -183,12 +181,13 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT]; if (val == prev_val) { /* keep the lowest of consecutive identical blocks */ - entry[-1].ptr = data + RABIN_WINDOW; + entry[-1].entry.ptr = data + RABIN_WINDOW; + --entries; } else { prev_val = val; i = val & hmask; - entry->ptr = data + RABIN_WINDOW; - entry->val = val; + entry->entry.ptr = data + RABIN_WINDOW; + entry->entry.val = val; entry->next = hash[i]; hash[i] = entry++; hash_count[i]++; @@ -208,20 +207,84 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) * the reference buffer. */ for (i = 0; i < hsize; i++) { - if (hash_count[i] < HASH_LIMIT) + int acc; + + if (hash_count[i] <= HASH_LIMIT) continue; + + entries -= hash_count[i] - HASH_LIMIT; + /* We leave exactly HASH_LIMIT entries in the bucket */ + entry = hash[i]; + acc = 0; do { - struct index_entry *keep = entry; - int skip = hash_count[i] / HASH_LIMIT; - do { - entry = entry->next; - } while(--skip && entry); - keep->next = entry; - } while(entry); + acc += hash_count[i] - HASH_LIMIT; + if (acc > 0) { + struct unpacked_index_entry *keep = entry; + do { + entry = entry->next; + acc -= HASH_LIMIT; + } while (acc > 0); + keep->next = entry->next; + } + entry = entry->next; + } while (entry); + + /* Assume that this loop is gone through exactly + * HASH_LIMIT times and is entered and left with + * acc==0. So the first statement in the loop + * contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT + * to the accumulator, and the inner loop consequently + * is run (hash_count[i]-HASH_LIMIT) times, removing + * one element from the list each time. Since acc + * balances out to 0 at the final run, the inner loop + * body can't be left with entry==NULL. So we indeed + * encounter entry==NULL in the outer loop only. + */ } free(hash_count); + /* Now create the packed index in array form rather than + * linked lists */ + + memsize = sizeof(*index) + + sizeof(*packed_hash) * (hsize+1) + + sizeof(*packed_entry) * entries; + + mem = malloc(memsize); + + if (!mem) { + free(hash); + return NULL; + } + + index = mem; + index->memsize = memsize; + index->src_buf = buf; + index->src_size = bufsize; + index->hash_mask = hmask; + + mem = index + 1; + packed_hash = mem; + mem = packed_hash + (hsize+1); + packed_entry = mem; + + /* Coalesce all entries belonging to one linked list into + * consecutive array entries */ + + for (i = 0; i < hsize; i++) { + packed_hash[i] = packed_entry; + for (entry = hash[i]; entry; entry = entry->next) + *packed_entry++ = entry->entry; + } + + /* Sentinel value to indicate the length of the last hash + * bucket */ + + packed_hash[hsize] = packed_entry; + assert(packed_entry - (struct index_entry *)mem == entries); + free(hash); + return index; } @@ -302,7 +365,7 @@ create_delta(const struct delta_index *index, val ^= U[data[-RABIN_WINDOW]]; val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; i = val & index->hash_mask; - for (entry = index->hash[i]; entry; entry = entry->next) { + for (entry = index->hash[i]; entry < index->hash[i+1]; entry++) { const unsigned char *ref = entry->ptr; const unsigned char *src = data; unsigned int ref_size = ref_top - ref; diff --git a/git-commit.sh b/git-commit.sh index 7a7a2cb4b..cb14f0621 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -98,101 +98,71 @@ do no_edit=t log_given=t$log_given logfile="$1" - shift ;; -F*|-f*) no_edit=t log_given=t$log_given - logfile=`expr "z$1" : 'z-[Ff]\(.*\)'` - shift + logfile="${1#-[Ff]}" ;; --F=*|--f=*|--fi=*|--fil=*|--file=*) no_edit=t log_given=t$log_given - logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift + logfile="${1#*=}" ;; -a|--a|--al|--all) all=t - shift ;; --au=*|--aut=*|--auth=*|--autho=*|--author=*) - force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'` - shift + force_author="${1#*=}" ;; --au|--aut|--auth|--autho|--author) case "$#" in 1) usage ;; esac shift force_author="$1" - shift ;; -e|--e|--ed|--edi|--edit) edit_flag=t - shift ;; -i|--i|--in|--inc|--incl|--inclu|--includ|--include) also=t - shift ;; --int|--inte|--inter|--intera|--interac|--interact|--interacti|\ --interactiv|--interactive) interactive=t - shift ;; -o|--o|--on|--onl|--only) only=t - shift ;; -m|--m|--me|--mes|--mess|--messa|--messag|--message) case "$#" in 1) usage ;; esac shift log_given=m$log_given - if test "$log_message" = '' - then - log_message="$1" - else - log_message="$log_message + log_message="${log_message:+${log_message} -$1" - fi +}$1" no_edit=t - shift ;; -m*) log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-m\(.*\)'` - else - log_message="$log_message + log_message="${log_message:+${log_message} -`expr "z$1" : 'z-m\(.*\)'`" - fi +}${1#-m}" no_edit=t - shift ;; --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) log_given=m$log_given - if test "$log_message" = '' - then - log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'` - else - log_message="$log_message + log_message="${log_message:+${log_message} -`expr "z$1" : 'zq-[^=]*=\(.*\)'`" - fi +}${1#*=}" no_edit=t - shift ;; -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\ --no-verify) verify= - shift ;; --a|--am|--ame|--amen|--amend) amend=t use_commit=HEAD - shift ;; -c) case "$#" in 1) usage ;; esac @@ -200,15 +170,13 @@ $1" log_given=t$log_given use_commit="$1" no_edit= - shift ;; --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\ --reedit-messag=*|--reedit-message=*) log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + use_commit="${1#*=}" no_edit= - shift ;; --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\ @@ -218,7 +186,6 @@ $1" log_given=t$log_given use_commit="$1" no_edit= - shift ;; -C) case "$#" in 1) usage ;; esac @@ -226,15 +193,13 @@ $1" log_given=t$log_given use_commit="$1" no_edit=t - shift ;; --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\ --reuse-message=*) log_given=t$log_given - use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'` + use_commit="${1#*=}" no_edit=t - shift ;; --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message) @@ -243,32 +208,26 @@ $1" log_given=t$log_given use_commit="$1" no_edit=t - shift ;; -s|--s|--si|--sig|--sign|--signo|--signof|--signoff) signoff=t - shift ;; -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template) case "$#" in 1) usage ;; esac shift templatefile="$1" no_edit= - shift ;; -q|--q|--qu|--qui|--quie|--quiet) quiet=t - shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) verbose=t - shift ;; -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\ --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\ --untracked-file|--untracked-files) untracked_files=t - shift ;; --) shift @@ -281,6 +240,7 @@ $1" break ;; esac + shift done case "$edit_flag" in t) no_edit= ;; esac @@ -441,12 +401,8 @@ esac if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit then - if test "$TMP_INDEX" - then - GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit - else - GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit - fi || exit + GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \ + || exit fi if test "$log_message" != '' diff --git a/git-compat-util.h b/git-compat-util.h index ca0a597a2..1bfbdeb94 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -172,6 +172,12 @@ extern uintmax_t gitstrtoumax(const char *, char **, int); extern const char *githstrerror(int herror); #endif +#ifdef NO_MEMMEM +#define memmem gitmemmem +void *gitmemmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +#endif + extern void release_pack_memory(size_t, int); static inline char* xstrdup(const char *str) diff --git a/git-merge.sh b/git-merge.sh index cde09d4d6..6c513dcbd 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -97,6 +97,19 @@ finish () { fi ;; esac + + # Run a post-merge hook + if test -x "$GIT_DIR"/hooks/post-merge + then + case "$squash" in + t) + "$GIT_DIR"/hooks/post-merge 1 + ;; + '') + "$GIT_DIR"/hooks/post-merge 0 + ;; + esac + fi } merge_name () { diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 2fa53fdae..268a629c4 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -36,14 +36,14 @@ warn () { output () { case "$VERBOSE" in '') - "$@" > "$DOTEST"/output 2>&1 + output=$("$@" 2>&1 ) status=$? - test $status != 0 && - cat "$DOTEST"/output + test $status != 0 && printf "%s\n" "$output" return $status - ;; + ;; *) "$@" + ;; esac } @@ -63,6 +63,7 @@ comment_for_reflog () { ''|rebase*) GIT_REFLOG_ACTION="rebase -i ($1)" export GIT_REFLOG_ACTION + ;; esac } @@ -70,22 +71,23 @@ mark_action_done () { sed -e 1q < "$TODO" >> "$DONE" sed -e 1d < "$TODO" >> "$TODO".new mv -f "$TODO".new "$TODO" - count=$(($(wc -l < "$DONE"))) - total=$(($count+$(wc -l < "$TODO"))) + count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l))) + total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l))) printf "Rebasing (%d/%d)\r" $count $total test -z "$VERBOSE" || echo } make_patch () { - parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null) + parent_sha1=$(git rev-parse --verify "$1"^) || + die "Cannot get patch for $1^" git diff "$parent_sha1".."$1" > "$DOTEST"/patch + test -f "$DOTEST"/message || + git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message + test -f "$DOTEST"/author-script || + get_author_ident_from_commit "$1" > "$DOTEST"/author-script } die_with_patch () { - test -f "$DOTEST"/message || - git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message - test -f "$DOTEST"/author-script || - get_author_ident_from_commit $sha1 > "$DOTEST"/author-script make_patch "$1" die "$2" } @@ -95,13 +97,18 @@ die_abort () { die "$1" } +has_action () { + grep -vqe '^$' -e '^#' "$1" +} + pick_one () { no_ff= case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return - parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null) + parent_sha1=$(git rev-parse --verify $sha1^) || + die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) if test $no_ff$current_sha1 = $parent_sha1; then output git reset --hard $sha1 @@ -129,7 +136,7 @@ pick_one_preserving_merges () { fast_forward=t preserve=t new_parents= - for p in $(git rev-list --parents -1 $sha1 | cut -d\ -f2-) + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) do if test -f "$REWRITTEN"/$p then @@ -141,41 +148,47 @@ pick_one_preserving_merges () { ;; # do nothing; that parent is already there *) new_parents="$new_parents $new_p" + ;; esac fi done case $fast_forward in t) output warn "Fast forward to $sha1" - test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1 + test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1 ;; f) test "a$1" = a-n && die "Refusing to squash a merge: $sha1" - first_parent=$(expr "$new_parents" : " \([^ ]*\)") + first_parent=$(expr "$new_parents" : ' \([^ ]*\)') # detach HEAD to current parent output git checkout $first_parent 2> /dev/null || die "Cannot move HEAD to $first_parent" echo $sha1 > "$DOTEST"/current-commit case "$new_parents" in - \ *\ *) + ' '*' '*) # redo merge author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" - msg="$(git cat-file commit $sha1 | \ - sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")" + msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" # NEEDSWORK: give rerere a chance - if ! output git merge $STRATEGY -m "$msg" $new_parents + if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ + output git merge $STRATEGY -m "$msg" \ + $new_parents then - echo "$msg" > "$GIT_DIR"/MERGE_MSG + printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG die Error redoing merge $sha1 fi ;; *) output git cherry-pick $STRATEGY "$@" || die_with_patch $sha1 "Could not pick $sha1" + ;; esac + ;; esac } @@ -212,11 +225,11 @@ peek_next_command () { } do_next () { - test -f "$DOTEST"/message && rm "$DOTEST"/message - test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script + rm -f "$DOTEST"/message "$DOTEST"/author-script \ + "$DOTEST"/amend || exit read command sha1 rest < "$TODO" case "$command" in - \#|'') + '#'*|'') mark_action_done ;; pick) @@ -233,6 +246,7 @@ do_next () { pick_one $sha1 || die_with_patch $sha1 "Could not apply $sha1... $rest" make_patch $sha1 + : > "$DOTEST"/amend warn warn "You can amend the commit now, with" warn @@ -243,7 +257,7 @@ do_next () { squash) comment_for_reflog squash - test -z "$(grep -ve '^$' -e '^#' < $DONE)" && + has_action "$DONE" || die "Cannot 'squash' without a previous commit" mark_action_done @@ -253,11 +267,12 @@ do_next () { EDIT_COMMIT= USE_OUTPUT=output cp "$MSG" "$SQUASH_MSG" - ;; + ;; *) EDIT_COMMIT=-e USE_OUTPUT= - test -f "$SQUASH_MSG" && rm "$SQUASH_MSG" + rm -f "$SQUASH_MSG" || exit + ;; esac failed=f @@ -269,7 +284,9 @@ do_next () { f) # This is like --amend, but with a different message eval "$author_script" - export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE + GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ + GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ + GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT ;; t) @@ -277,11 +294,13 @@ do_next () { warn warn "Could not apply $sha1... $rest" die_with_patch $sha1 "" + ;; esac ;; *) warn "Unknown command: $command $sha1 $rest" die_with_patch $sha1 "Please fix this in the file $TODO." + ;; esac test -s "$TODO" && return @@ -330,7 +349,9 @@ do git update-index --refresh && git diff-files --quiet && ! git diff-index --cached --quiet HEAD && - . "$DOTEST"/author-script && + . "$DOTEST"/author-script && { + test ! -f "$DOTEST"/amend || git reset --soft HEAD^ + } && export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE && git commit -F "$DOTEST"/message -e @@ -406,7 +427,6 @@ do require_clean_work_tree - mkdir "$DOTEST" || die "Could not create temporary $DOTEST" if test ! -z "$2" then output git show-ref --verify --quiet "refs/heads/$2" || @@ -418,6 +438,8 @@ do HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + mkdir "$DOTEST" || die "Could not create temporary $DOTEST" + test -z "$ONTO" && ONTO=$UPSTREAM : > "$DOTEST"/interactive || die "Could not mark as interactive" @@ -468,17 +490,18 @@ EOF $UPSTREAM...$HEAD | \ sed -n "s/^>/pick /p" >> "$TODO" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" cp "$TODO" "$TODO".backup git_editor "$TODO" || die "Could not execute editor" - test -z "$(grep -ve '^$' -e '^#' < $TODO)" && + has_action "$TODO" || die_abort "Nothing to do" output git checkout $ONTO && do_rest + ;; esac shift done diff --git a/git-rebase.sh b/git-rebase.sh index 058fcacb7..1583402a0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -215,9 +215,11 @@ do -v|--verbose) verbose=t ;; + --whitespace=*) + git_am_opt="$git_am_opt $1" + ;; -C*) - git_am_opt=$1 - shift + git_am_opt="$git_am_opt $1" ;; -*) usage diff --git a/git-remote.perl b/git-remote.perl index 01cf48022..f6f283ea4 100755 --- a/git-remote.perl +++ b/git-remote.perl @@ -278,7 +278,9 @@ sub add_remote { for (@$track) { $git->command('config', '--add', "remote.$name.fetch", - "+refs/heads/$_:refs/remotes/$name/$_"); + $opts->{'mirror'} ? + "+refs/$_:refs/$_" : + "+refs/heads/$_:refs/remotes/$name/$_"); } if ($opts->{'fetch'}) { $git->command('fetch', $name); @@ -409,6 +411,10 @@ elsif ($ARGV[0] eq 'add') { shift @ARGV; next; } + if ($opt eq '--mirror') { + $opts{'mirror'} = 1; + next; + } add_usage(); } if (@ARGV != 3) { diff --git a/git-send-email.perl b/git-send-email.perl index 9547cc37a..4031e86b8 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -73,9 +73,18 @@ Options: --signed-off-cc Automatically add email addresses that appear in Signed-off-by: or Cc: lines to the cc: list. Defaults to on. + --identity The configuration identity, a subsection to prioritise over + the default section. + --smtp-server If set, specifies the outgoing SMTP server to use. Defaults to localhost. + --smtp-user The username for SMTP-AUTH. + + --smtp-pass The password for SMTP-AUTH. + + --smtp-ssl If set, connects to the SMTP server using SSL. + --suppress-from Suppress sending emails to yourself if your address appears in a From: line. Defaults to off. @@ -145,7 +154,6 @@ my $compose_filename = ".msg.$$"; my (@to,@cc,@initial_cc,@bcclist,@xh, $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time); -my $smtp_server; my $envelope_sender; # Example reply to: @@ -164,24 +172,26 @@ my ($quiet, $dry_run) = (0, 0); # Variables with corresponding config settings my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd); +my ($smtp_server, $smtp_authuser, $smtp_authpass, $smtp_ssl); +my ($identity, $aliasfiletype, @alias_files); -my %config_settings = ( +my %config_bool_settings = ( "thread" => [\$thread, 1], "chainreplyto" => [\$chain_reply_to, 1], "suppressfrom" => [\$suppress_from, 0], "signedoffcc" => [\$signed_off_cc, 1], - "cccmd" => [\$cc_cmd, ""], + "smtpssl" => [\$smtp_ssl, 0], ); -foreach my $setting (keys %config_settings) { - my $config = $repo->config_bool("sendemail.$setting"); - ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1]; -} - -@bcclist = $repo->config('sendemail.bcc'); -if (!@bcclist or !$bcclist[0]) { - @bcclist = (); -} +my %config_settings = ( + "smtpserver" => \$smtp_server, + "smtpuser" => \$smtp_authuser, + "smtppass" => \$smtp_authpass, + "cccmd" => \$cc_cmd, + "aliasfiletype" => \$aliasfiletype, + "bcc" => \@bcclist, + "aliasesfile" => \@alias_files, +); # Begin by accumulating all the variables (defined above), that we will end up # needing, first, from the command line: @@ -194,6 +204,10 @@ my $rc = GetOptions("sender|from=s" => \$sender, "bcc=s" => \@bcclist, "chain-reply-to!" => \$chain_reply_to, "smtp-server=s" => \$smtp_server, + "smtp-user=s" => \$smtp_authuser, + "smtp-pass=s" => \$smtp_authpass, + "smtp-ssl!" => \$smtp_ssl, + "identity=s" => \$identity, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, @@ -208,6 +222,43 @@ unless ($rc) { usage(); } +# Now, let's fill any that aren't set in with defaults: + +sub read_config { + my ($prefix) = @_; + + foreach my $setting (keys %config_bool_settings) { + my $target = $config_bool_settings{$setting}->[0]; + $$target = $repo->config_bool("$prefix.$setting") unless (defined $$target); + } + + foreach my $setting (keys %config_settings) { + my $target = $config_settings{$setting}; + if (ref($target) eq "ARRAY") { + unless (@$target) { + my @values = $repo->config("$prefix.$setting"); + @$target = @values if (@values && defined $values[0]); + } + } + else { + $$target = $repo->config("$prefix.$setting") unless (defined $$target); + } + } +} + +# read configuration from [sendemail "$identity"], fall back on [sendemail] +$identity = $repo->config("sendemail.identity") unless (defined $identity); +read_config("sendemail.$identity") if (defined $identity); +read_config("sendemail"); + +# fall back on builtin bool defaults +foreach my $setting (values %config_bool_settings) { + ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]})); +} + +my ($repoauthor) = $repo->ident_person('author'); +my ($repocommitter) = $repo->ident_person('committer'); + # Verify the user input foreach my $entry (@to) { @@ -222,14 +273,7 @@ foreach my $entry (@bcclist) { die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/; } -# Now, let's fill any that aren't set in with defaults: - -my ($repoauthor) = $repo->ident_person('author'); -my ($repocommitter) = $repo->ident_person('committer'); - my %aliases; -my @alias_files = $repo->config('sendemail.aliasesfile'); -my $aliasfiletype = $repo->config('sendemail.aliasfiletype'); my %parse_alias = ( # multiline formats can be supported in the future mutt => sub { my $fh = shift; while (<$fh>) { @@ -321,10 +365,7 @@ if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to =~ s/>?\s+$/>/; } -if (!$smtp_server) { - $smtp_server = $repo->config('sendemail.smtpserver'); -} -if (!$smtp_server) { +if (!defined $smtp_server) { foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) { if (-x $_) { $smtp_server = $_; @@ -561,8 +602,16 @@ X-Mailer: git-send-email $gitversion print $sm "$header\n$message"; close $sm or die $?; } else { - require Net::SMTP; - $smtp ||= Net::SMTP->new( $smtp_server ); + if ($smtp_ssl) { + require Net::SMTP::SSL; + $smtp ||= Net::SMTP::SSL->new( $smtp_server, Port => 465 ); + } + else { + require Net::SMTP; + $smtp ||= Net::SMTP->new( $smtp_server ); + } + $smtp->auth( $smtp_authuser, $smtp_authpass ) + or die $smtp->message if (defined $smtp_authuser); $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; $smtp->data or die $smtp->message; @@ -669,7 +718,7 @@ foreach my $t (@files) { } close F; - if ($cc_cmd ne "") { + if (defined $cc_cmd) { open(F, "$cc_cmd $t |") or die "(cc-cmd) Could not execute '$cc_cmd'"; while(<F>) { diff --git a/git-svn.perl b/git-svn.perl index c015ea858..484b0576a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -124,7 +124,8 @@ my %cmd = ( "Set an SVN repository to a git tree-ish", { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], 'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings", - { 'revision|r=i' => \$_revision } ], + { 'revision|r=i' => \$_revision + } ], 'multi-fetch' => [ \&cmd_multi_fetch, "Deprecated alias for $0 fetch --all", { 'revision|r=s' => \$_revision, %fc_opts } ], @@ -144,10 +145,10 @@ my %cmd = ( 'non-recursive' => \$Git::SVN::Log::non_recursive, 'authors-file|A=s' => \$_authors, 'color' => \$Git::SVN::Log::color, - 'pager=s' => \$Git::SVN::Log::pager, + 'pager=s' => \$Git::SVN::Log::pager } ], 'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish", - { } ], + {} ], 'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory", { 'merge|m|M' => \$_merge, 'verbose|v' => \$_verbose, @@ -811,7 +812,8 @@ sub cmt_metadata { sub working_head_info { my ($head, $refs) = @_; - my ($fh, $ctx) = command_output_pipe('log', '--no-color', $head); + my @args = ('log', '--no-color', '--first-parent'); + my ($fh, $ctx) = command_output_pipe(@args, $head); my $hash; my %max; while (<$fh>) { @@ -364,6 +364,7 @@ static void handle_internal_command(int argc, const char **argv) { "reflog", cmd_reflog, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, + { "reset", cmd_reset, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, { "rev-parse", cmd_rev_parse, RUN_SETUP }, { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, diff --git a/merge-recursive.c b/merge-recursive.c index 19d5f3b28..97dcf9bf0 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -96,6 +96,7 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; +static int rename_limit = -1; static int buffer_output = 1; static struct output_buffer *output_list, *output_end; @@ -372,6 +373,7 @@ static struct path_list *get_renames(struct tree *tree, diff_setup(&opts); opts.recursive = 1; opts.detect_rename = DIFF_DETECT_RENAME; + opts.rename_limit = rename_limit; opts.output_format = DIFF_FORMAT_NO_OUTPUT; if (diff_setup_done(&opts) < 0) die("diff setup failed"); @@ -1693,6 +1695,10 @@ static int merge_config(const char *var, const char *value) verbosity = git_config_int(var, value); return 0; } + if (!strcasecmp(var, "diff.renamelimit")) { + rename_limit = git_config_int(var, value); + return 0; + } return git_default_config(var, value); } diff --git a/read-cache.c b/read-cache.c index 536f4d087..2e40a3442 100644 --- a/read-cache.c +++ b/read-cache.c @@ -346,6 +346,7 @@ int remove_file_from_index(struct index_state *istate, const char *path) int pos = index_name_pos(istate, path, strlen(path)); if (pos < 0) pos = -pos-1; + cache_tree_invalidate_path(istate->cache_tree, path); while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path)) remove_index_entry_at(istate, pos); return 0; @@ -430,7 +431,6 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose) die("unable to add %s to index",path); if (verbose) printf("add '%s'\n", path); - cache_tree_invalidate_path(istate->cache_tree, path); return 0; } @@ -698,6 +698,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE; int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; + cache_tree_invalidate_path(istate->cache_tree, ce->name); pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags)); /* existing match? Just replace it. */ @@ -1455,3 +1455,30 @@ int for_each_reflog(each_ref_fn fn, void *cb_data) { return do_for_each_reflog("", fn, cb_data); } + +int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr) +{ + static struct ref_lock *lock; + lock = lock_any_ref_for_update(refname, oldval, flags); + if (!lock) { + const char *str = "Cannot lock the ref '%s'."; + switch (onerr) { + case MSG_ON_ERR: error(str, refname); break; + case DIE_ON_ERR: die(str, refname); break; + case QUIET_ON_ERR: break; + } + return 1; + } + if (write_ref_sha1(lock, sha1, action) < 0) { + const char *str = "Cannot update the ref '%s'."; + switch (onerr) { + case MSG_ON_ERR: error(str, refname); break; + case DIE_ON_ERR: die(str, refname); break; + case QUIET_ON_ERR: break; + } + return 1; + } + return 0; +} @@ -64,4 +64,10 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg /** resolve ref in nested "gitlink" repository */ extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result); +/** lock a ref and then write its file */ +enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR }; +int update_ref(const char *action, const char *refname, + const unsigned char *sha1, const unsigned char *oldval, + int flags, enum action_on_err onerr); + #endif /* REFS_H */ diff --git a/send-pack.c b/send-pack.c index 9fc8a812f..f74e66a8b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -307,20 +307,14 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha rs.src = ref->name; rs.dst = NULL; if (!remote_find_tracking(remote, &rs)) { - struct ref_lock *lock; fprintf(stderr, " Also local %s\n", rs.dst); if (will_delete_ref) { if (delete_ref(rs.dst, NULL)) { error("Failed to delete"); } - } else { - lock = lock_any_ref_for_update(rs.dst, NULL, 0); - if (!lock) - error("Failed to lock"); - else - write_ref_sha1(lock, ref->new_sha1, - "update by push"); - } + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); free(rs.dst); } } diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 718c9c1fa..1af73a47c 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -80,7 +80,7 @@ cat "$1".tmp action=pick for line in $FAKE_LINES; do case $line in - squash) + squash|edit) action="$line";; *) echo sed -n "${line}s/^pick/$action/p" @@ -297,4 +297,16 @@ test_expect_success 'ignore patch if in upstream' ' test $HEAD = $(git rev-parse HEAD^) ' +test_expect_success '--continue tries to commit, even for "edit"' ' + parent=$(git rev-parse HEAD^) && + test_tick && + FAKE_LINES="edit 1" git rebase -i HEAD^ && + echo edited > file7 && + git add file7 && + FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue && + test edited = $(git show HEAD:file7) && + git show HEAD | grep chouette && + test $parent = $(git rev-parse HEAD^) +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 1a4c53a03..42e28ab75 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -28,12 +28,15 @@ commit id embedding: TAR=${TAR:-tar} UNZIP=${UNZIP:-unzip} +SUBSTFORMAT=%H%n + test_expect_success \ 'populate workdir' \ 'mkdir a b c && echo simple textfile >a/a && mkdir a/bin && cp /bin/sh a/bin && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile && ln -s a a/l1 && (p=long_path_to_a_file && cd a && for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && @@ -105,6 +108,22 @@ test_expect_success \ 'diff -r a c/prefix/a' test_expect_success \ + 'create an archive with a substfile' \ + 'echo substfile export-subst >a/.gitattributes && + git archive HEAD >f.tar && + rm a/.gitattributes' + +test_expect_success \ + 'extract substfile' \ + '(mkdir f && cd f && $TAR xf -) <f.tar' + +test_expect_success \ + 'validate substfile contents' \ + 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ + >f/a/substfile.expected && + diff f/a/substfile.expected f/a/substfile' + +test_expect_success \ 'git archive --format=zip' \ 'git archive --format=zip HEAD >d.zip' diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh new file mode 100755 index 000000000..1c4b0b32a --- /dev/null +++ b/t/t5402-post-merge-hook.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# +# Copyright (c) 2006 Josh England +# + +test_description='Test the post-merge hook.' +. ./test-lib.sh + +test_expect_success setup ' + echo Data for commit0. >a && + git update-index --add a && + tree0=$(git write-tree) && + commit0=$(echo setup | git commit-tree $tree0) && + echo Changed data for commit1. >a && + git update-index a && + tree1=$(git write-tree) && + commit1=$(echo modify | git commit-tree $tree1 -p $commit0) && + git update-ref refs/heads/master $commit0 && + git-clone ./. clone1 && + GIT_DIR=clone1/.git git update-index --add a && + git-clone ./. clone2 && + GIT_DIR=clone2/.git git update-index --add a +' + +for clone in 1 2; do + cat >clone${clone}/.git/hooks/post-merge <<'EOF' +#!/bin/sh +echo $@ >> $GIT_DIR/post-merge.args +EOF + chmod u+x clone${clone}/.git/hooks/post-merge +done + +test_expect_failure 'post-merge does not run for up-to-date ' ' + GIT_DIR=clone1/.git git merge $commit0 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge runs as expected ' ' + GIT_DIR=clone1/.git git merge $commit1 && + test -e clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from normal merge receives the right argument ' ' + grep 0 clone1/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge runs as expected ' ' + GIT_DIR=clone2/.git git merge --squash $commit1 && + test -e clone2/.git/post-merge.args +' + +test_expect_success 'post-merge from squash merge receives the right argument ' ' + grep 1 clone2/.git/post-merge.args +' + +test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh new file mode 100755 index 000000000..f64b1cbf7 --- /dev/null +++ b/t/t7102-reset.sh @@ -0,0 +1,405 @@ +#!/bin/sh +# +# Copyright (c) 2007 Carlos Rica +# + +test_description='git-reset + +Documented tests for git-reset' + +. ./test-lib.sh + +test_expect_success 'creating initial files and commits' ' + test_tick && + echo "1st file" >first && + git add first && + git commit -m "create 1st file" && + + echo "2nd file" >second && + git add second && + git commit -m "create 2nd file" && + + echo "2nd line 1st file" >>first && + git commit -a -m "modify 1st file" && + + git rm first && + git mv second secondfile && + git commit -a -m "remove 1st and rename 2nd" && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git commit -a -m "modify 2nd file" +' +# git log --pretty=oneline # to see those SHA1 involved + +check_changes () { + test "$(git rev-parse HEAD)" = "$1" && + git diff | git diff .diff_expect - && + git diff --cached | git diff .cached_expect - && + for FILE in * + do + echo $FILE':' + cat $FILE || return + done | git diff .cat_expect - +} + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF + +test_expect_success 'giving a non existing revision should fail' ' + ! git reset aaaaaa && + ! git reset --mixed aaaaaa && + ! git reset --soft aaaaaa && + ! git reset --hard aaaaaa && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'giving paths with options different than --mixed should fail' ' + ! git reset --soft -- first && + ! git reset --hard -- first && + ! git reset --soft HEAD^ -- first && + ! git reset --hard HEAD^ -- first && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success 'giving unrecognized options should fail' ' + ! git reset --other && + ! git reset -o && + ! git reset --mixed --other && + ! git reset --mixed -o && + ! git reset --soft --other && + ! git reset --soft -o && + ! git reset --hard --other && + ! git reset --hard -o && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'trying to do reset --soft with pending merge should fail' ' + git branch branch1 && + git branch branch2 && + + git checkout branch1 && + echo "3rd line in branch1" >>secondfile && + git commit -a -m "change in branch1" && + + git checkout branch2 && + echo "3rd line in branch2" >>secondfile && + git commit -a -m "change in branch2" && + + ! git merge branch1 && + ! git reset --soft && + + printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile && + git commit -a -m "the change in branch2" && + + git checkout master && + git branch -D branch1 branch2 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'trying to do reset --soft with pending checkout merge should fail' ' + git branch branch3 && + git branch branch4 && + + git checkout branch3 && + echo "3rd line in branch3" >>secondfile && + git commit -a -m "line in branch3" && + + git checkout branch4 && + echo "3rd line in branch4" >>secondfile && + + git checkout -m branch3 && + ! git reset --soft && + + printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile && + git commit -a -m "the line in branch3" && + + git checkout master && + git branch -D branch3 branch4 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +test_expect_success \ + 'resetting to HEAD with no changes should succeed and do nothing' ' + git reset --hard && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --hard HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --soft && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --soft HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --mixed && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset --mixed HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc + git reset HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +cat >.cached_expect <<EOF +diff --git a/secondfile b/secondfile +index 1bbba79..44c5b58 100644 +--- a/secondfile ++++ b/secondfile +@@ -1 +1,2 @@ +-2nd file ++1st line 2nd file ++2nd line 2nd file +EOF +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success '--soft reset only should show changes in diff --cached' ' + git reset --soft HEAD^ && + check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 && + test "$(git rev-parse ORIG_HEAD)" = \ + 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +3rd line 2nd file +EOF +test_expect_success \ + 'changing files and redo the last commit should succeed' ' + echo "3rd line 2nd file" >>secondfile && + git commit -a -C ORIG_HEAD && + check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d && + test "$(git rev-parse ORIG_HEAD)" = \ + 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +first: +1st file +2nd line 1st file +second: +2nd file +EOF +test_expect_success \ + '--hard reset should change the files and undo commits permanently' ' + git reset --hard HEAD~2 && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + test "$(git rev-parse ORIG_HEAD)" = \ + 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d +' + +>.diff_expect +cat >.cached_expect <<EOF +diff --git a/first b/first +deleted file mode 100644 +index 8206c22..0000000 +--- a/first ++++ /dev/null +@@ -1,2 +0,0 @@ +-1st file +-2nd line 1st file +diff --git a/second b/second +deleted file mode 100644 +index 1bbba79..0000000 +--- a/second ++++ /dev/null +@@ -1 +0,0 @@ +-2nd file +diff --git a/secondfile b/secondfile +new file mode 100644 +index 0000000..44c5b58 +--- /dev/null ++++ b/secondfile +@@ -0,0 +1,2 @@ ++1st line 2nd file ++2nd line 2nd file +EOF +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success \ + 'redoing changes adding them without commit them should succeed' ' + git rm first && + git mv second secondfile && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git add secondfile && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e +' + +cat >.diff_expect <<EOF +diff --git a/first b/first +deleted file mode 100644 +index 8206c22..0000000 +--- a/first ++++ /dev/null +@@ -1,2 +0,0 @@ +-1st file +-2nd line 1st file +diff --git a/second b/second +deleted file mode 100644 +index 1bbba79..0000000 +--- a/second ++++ /dev/null +@@ -1 +0,0 @@ +-2nd file +EOF +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success '--mixed reset to HEAD should unadd the files' ' + git reset && + check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + test "$(git rev-parse ORIG_HEAD)" = \ + ddaefe00f1da16864591c61fdc7adb5d7cd6b74e +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success 'redoing the last two commits should succeed' ' + git add secondfile && + git reset --hard ddaefe00f1da16864591c61fdc7adb5d7cd6b74e && + + git rm first && + git mv second secondfile && + git commit -a -m "remove 1st and rename 2nd" && + + echo "1st line 2nd file" >secondfile && + echo "2nd line 2nd file" >>secondfile && + git commit -a -m "modify 2nd file" && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +3rd line in branch2 +EOF +test_expect_success '--hard reset to HEAD should clear a failed merge' ' + git branch branch1 && + git branch branch2 && + + git checkout branch1 && + echo "3rd line in branch1" >>secondfile && + git commit -a -m "change in branch1" && + + git checkout branch2 && + echo "3rd line in branch2" >>secondfile && + git commit -a -m "change in branch2" && + + ! git pull . branch1 && + git reset --hard && + check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb +' + +>.diff_expect +>.cached_expect +cat >.cat_expect <<EOF +secondfile: +1st line 2nd file +2nd line 2nd file +EOF +test_expect_success \ + '--hard reset to ORIG_HEAD should clear a fast-forward merge' ' + git reset --hard HEAD^ && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && + + git pull . branch1 && + git reset --hard ORIG_HEAD && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc && + + git checkout master && + git branch -D branch1 branch2 && + check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc +' + +cat > expect << EOF +diff --git a/file1 b/file1 +index d00491f..7ed6ff8 100644 +--- a/file1 ++++ b/file1 +@@ -1 +1 @@ +-1 ++5 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 0cfbf08..0000000 +--- a/file2 ++++ /dev/null +@@ -1 +0,0 @@ +-2 +EOF +cat > cached_expect << EOF +diff --git a/file4 b/file4 +new file mode 100644 +index 0000000..b8626c4 +--- /dev/null ++++ b/file4 +@@ -0,0 +1 @@ ++4 +EOF +test_expect_success 'test --mixed <paths>' ' + echo 1 > file1 && + echo 2 > file2 && + git add file1 file2 && + test_tick && + git commit -m files && + git rm file2 && + echo 3 > file3 && + echo 4 > file4 && + echo 5 > file1 && + git add file1 file3 file4 && + ! git reset HEAD -- file1 file2 file3 && + git diff > output && + git diff output expect && + git diff --cached > output && + git diff output cached_expect +' + +test_expect_success 'test resetting the index at give paths' ' + + mkdir sub && + >sub/file1 && + >sub/file2 && + git update-index --add sub/file1 sub/file2 && + T=$(git write-tree) && + ! git reset HEAD sub/file2 && + U=$(git write-tree) && + echo "$T" && + echo "$U" && + ! git diff-index --cached --exit-code "$T" && + test "$T" != "$U" + +' + +test_done |