diff options
77 files changed, 2016 insertions, 1233 deletions
diff --git a/.gitignore b/.gitignore index 249b451b87..c8c13f5503 100644 --- a/.gitignore +++ b/.gitignore @@ -128,7 +128,6 @@ git-status git-stripspace git-submodule git-svn -git-svnimport git-symbolic-ref git-tag git-tar-tree diff --git a/Documentation/RelNotes-1.5.3.5.txt b/Documentation/RelNotes-1.5.3.5.txt index e28d92f618..4e46d2c2a2 100644 --- a/Documentation/RelNotes-1.5.3.5.txt +++ b/Documentation/RelNotes-1.5.3.5.txt @@ -87,8 +87,8 @@ Fixes since v1.5.3.4 * A few workarounds to squelch false warnings from recent gcc have been added. --- -exec >/var/tmp/1 -O=v1.5.3.4-55-gf120ae2 -echo O=`git describe refs/heads/maint` -git shortlog --no-merges $O..refs/heads/maint + * "git-send-pack $remote frotz" segfaulted when there is nothing + named 'frotz' on the local end. + + * "git-rebase -interactive" did not handle its "--strategy" option + properly. diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 1061fd8bcd..8d21d423e5 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -185,7 +185,6 @@ git-status mainporcelain git-stripspace purehelpers git-submodule mainporcelain git-svn foreignscminterface -git-svnimport foreignscminterface git-symbolic-ref plumbingmanipulators git-tag mainporcelain git-tar-tree plumbinginterrogators diff --git a/Documentation/core-tutorial.txt b/Documentation/core-tutorial.txt index d8e78ac8f1..5df97a1f9d 100644 --- a/Documentation/core-tutorial.txt +++ b/Documentation/core-tutorial.txt @@ -878,7 +878,7 @@ script called `git merge`, which wants to know which branches you want to resolve and what the merge is all about: ------------ -$ git merge "Merge work in mybranch" HEAD mybranch +$ git merge -m "Merge work in mybranch" mybranch ------------ where the first argument is going to be used as the commit message if @@ -965,7 +965,7 @@ to the `master` branch. Let's go back to `mybranch`, and run ------------ $ git checkout mybranch -$ git merge "Merge upstream changes." HEAD master +$ git merge -m "Merge upstream changes." master ------------ This outputs something like this (the actual commit object names @@ -1607,8 +1607,8 @@ in both of them. You could merge in 'diff-fix' first and then 'commit-fix' next, like this: ------------ -$ git merge 'Merge fix in diff-fix' master diff-fix -$ git merge 'Merge fix in commit-fix' master commit-fix +$ git merge -m 'Merge fix in diff-fix' diff-fix +$ git merge -m 'Merge fix in commit-fix' commit-fix ------------ Which would result in: diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 001503205b..9709c35c98 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -1,5 +1,5 @@ -The output format from "git-diff-index", "git-diff-tree" and -"git-diff-files" are very similar. +The output format from "git-diff-index", "git-diff-tree", +"git-diff-files" and "git diff --raw" are very similar. These commands all compare two sets of things; what is compared differs: @@ -62,7 +62,8 @@ respectively. diff format for merges ---------------------- -"git-diff-tree" and "git-diff-files" can take '-c' or '--cc' option +"git-diff-tree", "git-diff-files" and "git-diff --raw" +can take '-c' or '--cc' option to generate diff output also for merge commits. The output differs from the format described above in the following way: @@ -86,10 +87,10 @@ Generating patches with -p -------------------------- When "git-diff-index", "git-diff-tree", or "git-diff-files" are run -with a '-p' option, they do not produce the output described above; -instead they produce a patch file. You can customize the creation -of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS -environment variables. +with a '-p' option, or "git diff" without the '--raw' option, they +do not produce the output described above; instead they produce a +patch file. You can customize the creation of such patches via the +GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS environment variables. What the -p option produces is slightly different from the traditional diff format. @@ -137,8 +138,8 @@ file made it into the new one. combined diff format -------------------- -git-diff-tree and git-diff-files can take '-c' or '--cc' option -to produce 'combined diff', which looks like this: +"git-diff-tree", "git-diff-files" and "git-diff" can take '-c' or +'--cc' option to produce 'combined diff', which looks like this: ------------ diff --combined describe.c diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 1072fb87d1..4795349c10 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -16,8 +16,9 @@ The command takes various subcommands, and different options depending on the subcommand: git bisect start [<bad> [<good>...]] [--] [<paths>...] - git bisect bad <rev> - git bisect good <rev> + git bisect bad [<rev>] + git bisect good [<rev>...] + git bisect skip [<rev>...] git bisect reset [<branch>] git bisect visualize git bisect replay <logfile> @@ -134,6 +135,20 @@ $ git reset --hard HEAD~3 # try 3 revs before what Then compile and test the one you chose to try. After that, tell bisect what the result was as usual. +Bisect skip +~~~~~~~~~~~~ + +Instead of choosing by yourself a nearby commit, you may just want git +to do it for you using: + +------------ +$ git bisect skip # Current version cannot be tested +------------ + +But computing the commit to test may be slower afterwards and git may +eventually not be able to tell the first bad among a bad and one or +more "skip"ped commits. + Cutting down bisection by giving more parameters to bisect start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -167,14 +182,18 @@ $ git bisect run my_script ------------ Note that the "run" script (`my_script` in the above example) should -exit with code 0 in case the current source code is good and with a -code between 1 and 127 (included) in case the current source code is -bad. +exit with code 0 in case the current source code is good. Exit with a +code between 1 and 127 (inclusive), except 125, if the current +source code is bad. Any other exit code will abort the automatic bisect process. (A program that does "exit(-1)" leaves $? = 255, see exit(3) manual page, the value is chopped with "& 0377".) +The special exit code 125 should be used when the current source code +cannot be tested. If the "run" script exits with this code, the current +revision will be skipped, see `git bisect skip` above. + You may often find that during bisect you want to have near-constant tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or "revision that does not have this commit needs this patch applied to diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 253f4f03c5..cca14d6b5d 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -111,11 +111,11 @@ OPTIONS --depth <depth>:: Create a 'shallow' clone with a history truncated to the - specified number of revs. A shallow repository has + specified number of revisions. A shallow repository has a number of limitations (you cannot clone or fetch from it, nor push from nor into it), but is adequate if you - want to only look at near the tip of a large project - with a long history, and would want to send in a fixes + are only interested in the recent history of a large project + with a long history, and would want to send in fixes as patches. <repository>:: diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 4c8d1e6386..c3922f9238 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -73,7 +73,7 @@ Merge one patch into CVS:: $ export GIT_DIR=~/project/.git $ cd ~/project_cvs_checkout $ git-cvsexportcommit -v <commit-sha1> -$ cvs commit -F .mgs <files> +$ cvs commit -F .msg <files> ------------ Merge pending patches into CVS automatically -- only if you really know what you are doing:: diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index ce0f502468..11c4216c4a 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -82,6 +82,9 @@ include::diff-options.txt[] the diff to the named paths (you can give directory names and get diff for all files under them). +Output format +------------- +include::diff-format.txt[] EXAMPLES -------- diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index ba9b4fbca7..385ecc900f 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -219,7 +219,7 @@ git filter-branch --commit-filter ' fi' HEAD ------------------------------------------------------------------------------ -The function 'skip_commits' is defined as follows: +The function 'skip_commit' is defined as follows: -------------------------- skip_commit() diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index c9857a2d62..f0617efa0a 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -168,7 +168,7 @@ git-format-patch origin:: is created in the current directory. git-format-patch \--root origin:: - Extract all commits which that leads to 'origin' since the + Extract all commits that lead to 'origin' since the inception of the project. git-format-patch -M -B origin:: diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index bca4212e56..eabd7ef33f 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -11,26 +11,27 @@ SYNOPSIS [verse] 'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]... [-m <msg>] <remote> <remote>... +'git-merge' <msg> HEAD <remote>... DESCRIPTION ----------- This is the top-level interface to the merge machinery which drives multiple merge strategy scripts. +The second syntax (<msg> `HEAD` <remote>) is supported for +historical reasons. Do not use it from the command line or in +new scripts. It is the same as `git merge -m <msg> <remote>`. + OPTIONS ------- include::merge-options.txt[] -<msg>:: +-m <msg>:: The commit message to be used for the merge commit (in case it is created). The `git-fmt-merge-msg` script can be used to give a good default for automated `git-merge` invocations. -<head>:: - Our branch head commit. This has to be `HEAD`, so new - syntax does not require it - <remote>:: Other branch head merged into our branch. You need at least one <remote>. Specifying more than one <remote> diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 6c32c6d18e..a26c260162 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -25,12 +25,18 @@ OPTIONS -t or --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff + kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff + If a merge resolution program is not specified, 'git mergetool' will use the configuration variable merge.tool. If the configuration variable merge.tool is not set, 'git mergetool' will pick a suitable default. ++ +You can explicitly provide a full path to the tool by setting the +configuration variable mergetool.<tool>.path. For example, you +can configure the absolute path to kdiff3 by setting +mergetool.kdiff3.path. Otherwise, 'git mergetool' assumes the tool +is available in PATH. Author ------ diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 7cd0e8913e..485280423e 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -34,6 +34,7 @@ SYNOPSIS [ \--pretty | \--header ] [ \--bisect ] [ \--bisect-vars ] + [ \--bisect-all ] [ \--merge ] [ \--reverse ] [ \--walk-reflogs ] @@ -354,6 +355,21 @@ the expected number of commits to be tested if `bisect_rev` turns out to be bad to `bisect_bad`, and the number of commits we are bisecting right now to `bisect_all`. +--bisect-all:: + +This outputs all the commit objects between the included and excluded +commits, ordered by their distance to the included and excluded +commits. The farthest from them is displayed first. (This is the only +one displayed by `--bisect`.) + +This is useful because it makes it easy to choose a good commit to +test when you want to avoid to test some of them for some reason (they +may not compile for example). + +This option can be used along with `--bisect-vars`, in this case, +after all the sorted commit objects, there will be the same text as if +`--bisect-vars` had been used alone. + -- Commit Ordering diff --git a/Documentation/git.txt b/Documentation/git.txt index c4d87ac201..6db7ae1ea7 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.5.txt[1.5.3.5], link:RelNotes-1.5.3.4.txt[1.5.3.4], link:RelNotes-1.5.3.3.txt[1.5.3.3], link:RelNotes-1.5.3.2.txt[1.5.3.2], @@ -225,8 +225,7 @@ SCRIPT_SH = \ SCRIPT_PERL = \ git-add--interactive.perl \ git-archimport.perl git-cvsimport.perl git-relink.perl \ - git-cvsserver.perl git-remote.perl \ - git-svnimport.perl git-cvsexportcommit.perl \ + git-cvsserver.perl git-remote.perl git-cvsexportcommit.perl \ git-send-email.perl git-svn.perl SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -290,7 +289,7 @@ LIB_H = \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \ - mailmap.h remote.h parse-options.h transport.h + mailmap.h remote.h parse-options.h transport.h diffcore.h hash.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -300,7 +299,7 @@ DIFF_OBJS = \ LIB_OBJS = \ blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o \ - interpolate.o \ + interpolate.o hash.o \ lockfile.o \ patch-ids.o \ object.o pack-check.o pack-write.o patch-delta.o path.o pkt-line.o \ @@ -917,7 +916,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) -$(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) diff --git a/builtin-add.c b/builtin-add.c index f61681c3ae..45b14e8a61 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -111,7 +111,7 @@ static void update_callback(struct diff_queue_struct *q, } } -static void update(int verbose, const char *prefix, const char **files) +void add_files_to_cache(int verbose, const char *prefix, const char **files) { struct rev_info rev; init_revisions(&rev, prefix); @@ -120,8 +120,6 @@ static void update(int verbose, const char *prefix, const char **files) rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; rev.diffopt.format_callback_data = &verbose; - if (read_cache() < 0) - die("index file corrupt"); run_diff_files(&rev, 0); } @@ -155,6 +153,13 @@ static int git_add_config(const char *var, const char *value) return git_default_config(var, value); } +int interactive_add(void) +{ + const char *argv[2] = { "add--interactive", NULL }; + + return run_command_v_opt(argv, RUN_GIT_CMD); +} + static struct lock_file lock_file; static const char ignore_error[] = @@ -183,12 +188,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, builtin_add_options, builtin_add_usage, 0); if (add_interactive) { - const char *args[] = { "add--interactive", NULL }; - if (add_interactive != 1 || orig_argc != 2) die("add --interactive does not take any parameters"); - execv_git_cmd(args); - exit(1); + exit(interactive_add()); } git_config(git_add_config); @@ -196,7 +198,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) newfd = hold_locked_index(&lock_file, 1); if (take_worktree_changes) { - update(verbose, prefix, argv); + if (read_cache() < 0) + die("index file corrupt"); + add_files_to_cache(verbose, prefix, argv); goto finish; } diff --git a/builtin-archive.c b/builtin-archive.c index 6f29c2f40a..14a1b3077c 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -30,7 +30,7 @@ static int run_remote_archiver(const char *remote, int argc, { char *url, buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; - pid_t pid; + struct child_process *conn; const char *exec = "git-upload-archive"; int exec_at = 0; @@ -46,9 +46,7 @@ static int run_remote_archiver(const char *remote, int argc, } url = xstrdup(remote); - pid = git_connect(fd, url, exec, 0); - if (pid < 0) - return pid; + conn = git_connect(fd, url, exec, 0); for (i = 1; i < argc; i++) { if (i == exec_at) @@ -76,7 +74,7 @@ static int run_remote_archiver(const char *remote, int argc, rv = recv_sideband("archive", fd[0], 1, 2); close(fd[0]); close(fd[1]); - rv |= finish_connect(pid); + rv |= finish_connect(conn); return !!rv; } diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 8753840a82..862652be92 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -7,6 +7,7 @@ #include "pack.h" #include "sideband.h" #include "fetch-pack.h" +#include "run-command.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -457,53 +458,49 @@ static int everything_local(struct ref **refs, int nr_match, char **match) return retval; } -static pid_t setup_sideband(int fd[2], int xd[2]) +static int sideband_demux(int fd, void *data) { - pid_t side_pid; + int *xd = data; + close(xd[1]); + return recv_sideband("fetch-pack", xd[0], fd, 2); +} + +static void setup_sideband(int fd[2], int xd[2], struct async *demux) +{ if (!use_sideband) { fd[0] = xd[0]; fd[1] = xd[1]; - return 0; + return; } /* xd[] is talking with upload-pack; subprocess reads from * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through our fd[0]. + * through demux->out. */ - if (pipe(fd) < 0) - die("fetch-pack: unable to set up pipe"); - side_pid = fork(); - if (side_pid < 0) + demux->proc = sideband_demux; + demux->data = xd; + if (start_async(demux)) die("fetch-pack: unable to fork off sideband demultiplexer"); - if (!side_pid) { - /* subprocess */ - close(fd[0]); - if (xd[0] != xd[1]) - close(xd[1]); - if (recv_sideband("fetch-pack", xd[0], fd[1], 2)) - exit(1); - exit(0); - } close(xd[0]); - close(fd[1]); + fd[0] = demux->out; fd[1] = xd[1]; - return side_pid; } static int get_pack(int xd[2], char **pack_lockfile) { - int status; - pid_t pid, side_pid; + struct async demux; int fd[2]; const char *argv[20]; char keep_arg[256]; char hdr_arg[256]; const char **av; int do_keep = args.keep_pack; - int keep_pipe[2]; + struct child_process cmd; - side_pid = setup_sideband(fd, xd); + setup_sideband(fd, xd, &demux); + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = argv; av = argv; *hdr_arg = 0; if (!args.keep_pack && unpack_limit) { @@ -520,8 +517,8 @@ static int get_pack(int xd[2], char **pack_lockfile) } if (do_keep) { - if (pack_lockfile && pipe(keep_pipe)) - die("fetch-pack: pipe setup failure: %s", strerror(errno)); + if (pack_lockfile) + cmd.out = -1; *av++ = "index-pack"; *av++ = "--stdin"; if (!args.quiet && !args.no_progress) @@ -545,43 +542,19 @@ static int get_pack(int xd[2], char **pack_lockfile) *av++ = hdr_arg; *av++ = NULL; - pid = fork(); - if (pid < 0) + cmd.in = fd[0]; + cmd.git_cmd = 1; + if (start_command(&cmd)) die("fetch-pack: unable to fork off %s", argv[0]); - if (!pid) { - dup2(fd[0], 0); - if (do_keep && pack_lockfile) { - dup2(keep_pipe[1], 1); - close(keep_pipe[0]); - close(keep_pipe[1]); - } - close(fd[0]); - close(fd[1]); - execv_git_cmd(argv); - die("%s exec failed", argv[0]); - } - close(fd[0]); close(fd[1]); - if (do_keep && pack_lockfile) { - close(keep_pipe[1]); - *pack_lockfile = index_pack_lockfile(keep_pipe[0]); - close(keep_pipe[0]); - } - while (waitpid(pid, &status, 0) < 0) { - if (errno != EINTR) - die("waiting for %s: %s", argv[0], strerror(errno)); - } - if (WIFEXITED(status)) { - int code = WEXITSTATUS(status); - if (code) - die("%s died with error code %d", argv[0], code); - return 0; - } - if (WIFSIGNALED(status)) { - int sig = WTERMSIG(status); - die("%s died of signal %d", argv[0], sig); - } - die("%s died of unnatural causes %d", argv[0], status); + if (do_keep && pack_lockfile) + *pack_lockfile = index_pack_lockfile(cmd.out); + + if (finish_command(&cmd)) + die("%s failed", argv[0]); + if (use_sideband && finish_async(&demux)) + die("error in sideband demultiplexer"); + return 0; } static struct ref *do_fetch_pack(int fd[2], @@ -763,7 +736,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, { int i, ret; int fd[2]; - pid_t pid; + struct child_process *conn; struct ref *ref; struct stat st; @@ -774,16 +747,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, st.st_mtime = 0; } - pid = git_connect(fd, (char *)dest, args.uploadpack, + conn = git_connect(fd, (char *)dest, args.uploadpack, args.verbose ? CONNECT_VERBOSE : 0); - if (pid < 0) - return NULL; if (heads && nr_heads) nr_heads = remove_duplicates(nr_heads, heads); ref = do_fetch_pack(fd, nr_heads, heads, pack_lockfile); close(fd[0]); close(fd[1]); - ret = finish_connect(pid); + ret = finish_connect(conn); if (!ret && nr_heads) { /* If the heads to pull were given, we should have diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 0be539ed7f..25ec65d0f0 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -65,8 +65,6 @@ static int no_reuse_delta, no_reuse_object, keep_unreachable; static int local; static int incremental; static int allow_ofs_delta; -static const char *pack_tmp_name, *idx_tmp_name; -static char tmpname[PATH_MAX]; static const char *base_name; static int progress = 1; static int window = 10; @@ -75,7 +73,7 @@ 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; +static struct progress *progress_state; static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; @@ -587,12 +585,6 @@ static off_t write_one(struct sha1file *f, return offset + size; } -static int open_object_dir_tmp(const char *path) -{ - snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path); - return xmkstemp(tmpname); -} - /* forward declaration for write_pack_file */ static int adjust_perm(const char *path, mode_t mode); @@ -606,16 +598,21 @@ static void write_pack_file(void) uint32_t nr_remaining = nr_result; if (do_progress) - start_progress(&progress_state, "Writing %u objects...", "", nr_result); + progress_state = start_progress("Writing objects", nr_result); written_list = xmalloc(nr_objects * sizeof(struct object_entry *)); do { unsigned char sha1[20]; + char *pack_tmp_name = NULL; if (pack_to_stdout) { - f = sha1fd(1, "<stdout>"); + f = sha1fd_throughput(1, "<stdout>", progress_state); } else { - int fd = open_object_dir_tmp("tmp_pack_XXXXXX"); + char tmpname[PATH_MAX]; + int fd; + snprintf(tmpname, sizeof(tmpname), + "%s/tmp_pack_XXXXXX", get_object_directory()); + fd = xmkstemp(tmpname); pack_tmp_name = xstrdup(tmpname); f = sha1fd(fd, pack_tmp_name); } @@ -632,8 +629,7 @@ static void write_pack_file(void) if (!offset_one) break; offset = offset_one; - if (do_progress) - display_progress(&progress_state, written); + display_progress(progress_state, written); } /* @@ -643,19 +639,21 @@ static void write_pack_file(void) if (pack_to_stdout || nr_written == nr_remaining) { sha1close(f, sha1, 1); } else { - sha1close(f, sha1, 0); - fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written); - close(f->fd); + int fd = sha1close(f, NULL, 0); + fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written); + close(fd); } if (!pack_to_stdout) { mode_t mode = umask(0); + char *idx_tmp_name, tmpname[PATH_MAX]; umask(mode); mode = 0444 & ~mode; idx_tmp_name = write_idx_file(NULL, - (struct pack_idx_entry **) written_list, nr_written, sha1); + (struct pack_idx_entry **) written_list, + nr_written, sha1); snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); if (adjust_perm(pack_tmp_name, mode)) @@ -672,6 +670,8 @@ static void write_pack_file(void) if (rename(idx_tmp_name, tmpname)) die("unable to rename temporary index file: %s", strerror(errno)); + free(idx_tmp_name); + free(pack_tmp_name); puts(sha1_to_hex(sha1)); } @@ -683,8 +683,7 @@ static void write_pack_file(void) } while (nr_remaining && i < nr_objects); free(written_list); - if (do_progress) - stop_progress(&progress_state); + stop_progress(&progress_state); if (written != nr_result) die("wrote %u objects while expecting %u", written, nr_result); /* @@ -852,8 +851,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type, else object_ix[-1 - ix] = nr_objects; - if (progress) - display_progress(&progress_state, nr_objects); + display_progress(progress_state, nr_objects); if (name && no_try_delta(name)) entry->no_try_delta = 1; @@ -1516,8 +1514,7 @@ static void find_deltas(struct object_entry **list, unsigned list_size, progress_lock(); (*processed)++; - if (progress) - display_progress(&progress_state, *processed); + display_progress(progress_state, *processed); progress_unlock(); /* @@ -1714,16 +1711,14 @@ static void prepare_pack(int window, int depth) delta_list[n++] = entry; } - if (nr_deltas) { + if (nr_deltas && n > 1) { unsigned nr_done = 0; if (progress) - start_progress(&progress_state, - "Deltifying %u objects...", "", - nr_deltas); + progress_state = start_progress("Compressing 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); + stop_progress(&progress_state); if (nr_done != nr_deltas) die("inconsistency with delta count"); } @@ -2135,23 +2130,17 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) prepare_packed_git(); if (progress) - start_progress(&progress_state, "Generating pack...", - "Counting objects: ", 0); + progress_state = start_progress("Counting objects", 0); if (!use_internal_rev_list) read_object_list_from_stdin(); else { rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } - if (progress) { - stop_progress(&progress_state); - fprintf(stderr, "Done counting %u objects.\n", nr_objects); - } + stop_progress(&progress_state); if (non_empty && !nr_result) return 0; - if (progress && (nr_objects != nr_result)) - fprintf(stderr, "Result has %u objects.\n", nr_result); if (nr_result) prepare_pack(window, depth); write_pack_file(); diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index 977730064b..23faf3129f 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "progress.h" static const char prune_packed_usage[] = "git-prune-packed [-n] [-q]"; @@ -7,6 +8,8 @@ static const char prune_packed_usage[] = #define DRY_RUN 01 #define VERBOSE 02 +static struct progress *progress; + static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) { struct dirent *de; @@ -27,6 +30,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) printf("rm -f %s\n", pathname); else if (unlink(pathname) < 0) error("unable to unlink %s", pathname); + display_progress(progress, i + 1); } pathname[len] = 0; rmdir(pathname); @@ -39,6 +43,10 @@ void prune_packed_objects(int opts) const char *dir = get_object_directory(); int len = strlen(dir); + if (opts == VERBOSE) + progress = start_progress_delay("Removing duplicate objects", + 256, 95, 2); + if (len > PATH_MAX - 42) die("impossible object directory"); memcpy(pathname, dir, len); @@ -49,16 +57,12 @@ void prune_packed_objects(int opts) sprintf(pathname + len, "%02x/", i); d = opendir(pathname); - if (opts == VERBOSE && (d || i == 255)) - fprintf(stderr, "Removing unused objects %d%%...\015", - ((i+1) * 100) / 256); if (!d) continue; prune_dir(i, d, pathname, len + 3, opts); closedir(d); } - if (opts == VERBOSE) - fprintf(stderr, "\nDone.\n"); + stop_progress(&progress); } int cmd_prune_packed(int argc, const char **argv, const char *prefix) diff --git a/builtin-rerere.c b/builtin-rerere.c index b8206744c1..74493237c9 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -389,18 +389,39 @@ static int is_rerere_enabled(void) return 1; } -int cmd_rerere(int argc, const char **argv, const char *prefix) +static int setup_rerere(struct path_list *merge_rr) { - struct path_list merge_rr = { NULL, 0, 0, 1 }; - int i, fd = -1; + int fd; git_config(git_rerere_config); if (!is_rerere_enabled()) - return 0; + return -1; merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR")); fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1); - read_rr(&merge_rr); + read_rr(merge_rr); + return fd; +} + +int rerere(void) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; + return do_plain_rerere(&merge_rr, fd); +} + +int cmd_rerere(int argc, const char **argv, const char *prefix) +{ + struct path_list merge_rr = { NULL, 0, 0, 1 }; + int i, fd; + + fd = setup_rerere(&merge_rr); + if (fd < 0) + return 0; if (argc < 2) return do_plain_rerere(&merge_rr, fd); diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 33726b8d84..44393320e8 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -9,6 +9,7 @@ #include "revision.h" #include "list-objects.h" #include "builtin.h" +#include "log-tree.h" /* bits #0-15 in revision.h */ @@ -38,7 +39,8 @@ static const char rev_list_usage[] = " --left-right\n" " special purpose:\n" " --bisect\n" -" --bisect-vars" +" --bisect-vars\n" +" --bisect-all" ; static struct rev_info revs; @@ -74,6 +76,7 @@ static void show_commit(struct commit *commit) parents = parents->next; } } + show_decorations(commit); if (revs.commit_format == CMIT_FMT_ONELINE) putchar(' '); else @@ -278,6 +281,57 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) return best; } +struct commit_dist { + struct commit *commit; + int distance; +}; + +static int compare_commit_dist(const void *a_, const void *b_) +{ + struct commit_dist *a, *b; + + a = (struct commit_dist *)a_; + b = (struct commit_dist *)b_; + if (a->distance != b->distance) + return b->distance - a->distance; /* desc sort */ + return hashcmp(a->commit->object.sha1, b->commit->object.sha1); +} + +static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +{ + struct commit_list *p; + struct commit_dist *array = xcalloc(nr, sizeof(*array)); + int cnt, i; + + for (p = list, cnt = 0; 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; + array[cnt].commit = p->item; + array[cnt].distance = distance; + cnt++; + } + qsort(array, cnt, sizeof(*array), compare_commit_dist); + for (p = list, i = 0; i < cnt; i++) { + struct name_decoration *r = xmalloc(sizeof(*r) + 100); + struct object *obj = &(array[i].commit->object); + + sprintf(r->name, "dist=%d", array[i].distance); + r->next = add_decoration(&name_decoration, obj, r); + p->item = array[i].commit; + p = p->next; + } + if (p) + p->next = NULL; + free(array); + return list; +} + /* * zero or positive weight is the number of interesting commits it can * reach, including itself. Especially, weight = 0 means it does not @@ -292,7 +346,8 @@ static struct commit_list *best_bisection(struct commit_list *list, int nr) * or positive distance. */ static struct commit_list *do_find_bisection(struct commit_list *list, - int nr, int *weights) + int nr, int *weights, + int find_all) { int n, counted; struct commit_list *p; @@ -351,7 +406,7 @@ static struct commit_list *do_find_bisection(struct commit_list *list, clear_distance(list); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; counted++; } @@ -389,19 +444,22 @@ static struct commit_list *do_find_bisection(struct commit_list *list, weight_set(p, weight(q)); /* Does it happen to be at exactly half-way? */ - if (halfway(p, nr)) + if (!find_all && halfway(p, nr)) return p; } } show_list("bisection 2 counted all", counted, nr, list); - /* Then find the best one */ - return best_bisection(list, nr); + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); } static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all) + int *reaches, int *all, + int find_all) { int nr, on_list; struct commit_list *p, *best, *next, *last; @@ -434,14 +492,13 @@ static struct commit_list *find_bisection(struct commit_list *list, weights = xcalloc(on_list, sizeof(*weights)); /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights); - + best = do_find_bisection(list, nr, weights, find_all); if (best) { - best->next = NULL; + if (!find_all) + best->next = NULL; *reaches = weight(best); } free(weights); - return best; } @@ -468,6 +525,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int i; int read_from_stdin = 0; int bisect_show_vars = 0; + int bisect_find_all = 0; git_config(git_default_config); init_revisions(&revs, prefix); @@ -490,6 +548,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) bisect_list = 1; continue; } + if (!strcmp(arg, "--bisect-all")) { + bisect_list = 1; + bisect_find_all = 1; + continue; + } if (!strcmp(arg, "--bisect-vars")) { bisect_list = 1; bisect_show_vars = 1; @@ -536,9 +599,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) { int reaches = reaches, all = all; - revs.commits = find_bisection(revs.commits, &reaches, &all); + revs.commits = find_bisection(revs.commits, &reaches, &all, + bisect_find_all); if (bisect_show_vars) { int cnt; + char hex[41]; if (!revs.commits) return 1; /* @@ -550,15 +615,22 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) * A bisect set of size N has (N-1) commits further * to test, as we already know one bad one. */ - cnt = all-reaches; + cnt = all - reaches; if (cnt < reaches) cnt = reaches; + strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); + + if (bisect_find_all) { + traverse_commit_list(&revs, show_commit, show_object); + printf("------\n"); + } + printf("bisect_rev=%s\n" "bisect_nr=%d\n" "bisect_good=%d\n" "bisect_bad=%d\n" "bisect_all=%d\n", - sha1_to_hex(revs.commits->item->object.sha1), + hex, cnt - 1, all - reaches - 1, reaches - 1, diff --git a/builtin-revert.c b/builtin-revert.c index 2ea766b5bf..a9347cf9c5 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -341,7 +341,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed. " "After resolving the conflicts,\n" - "mark the corrected paths with 'git-add <paths>'\n" + "mark the corrected paths with 'git add <paths>' " "and commit the result.\n", me); if (action == CHERRY_PICK) { fprintf(stderr, "When commiting, use the option " diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index a6ff62fd8c..1e51865c52 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -311,7 +311,7 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress progress; + struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); unsigned nr_objects = ntohl(hdr->hdr_entries); @@ -322,15 +322,13 @@ static void unpack_all(void) use(sizeof(struct pack_header)); if (!quiet) - start_progress(&progress, "Unpacking %u objects...", "", nr_objects); + progress = start_progress("Unpacking objects", nr_objects); obj_list = xmalloc(nr_objects * sizeof(*obj_list)); for (i = 0; i < nr_objects; i++) { unpack_one(i); - if (!quiet) - display_progress(&progress, i + 1); + display_progress(progress, i + 1); } - if (!quiet) - stop_progress(&progress); + stop_progress(&progress); if (delta_list) die("unresolved deltas left after unpacking"); @@ -6,6 +6,7 @@ extern const char git_version_string[]; extern const char git_usage_string[]; +extern void list_common_cmds_help(void); extern void help_unknown_cmd(const char *cmd); extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); extern void prune_packed_objects(int); @@ -503,8 +503,8 @@ struct ref { #define REF_TAGS (1u << 2) #define CONNECT_VERBOSE (1u << 0) -extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags); -extern int finish_connect(pid_t pid); +extern struct child_process *git_connect(int fd[2], char *url, const char *prog, int flags); +extern int finish_connect(struct child_process *conn); extern int path_match(const char *path, int nr, char **match); extern int get_ack(int fd, unsigned char *result_sha1); extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); @@ -135,39 +135,39 @@ int git_config_colorbool(const char *var, const char *value) return git_config_bool(var, value); } -static int color_vprintf(const char *color, const char *fmt, +static int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args, const char *trail) { int r = 0; if (*color) - r += printf("%s", color); - r += vprintf(fmt, args); + r += fprintf(fp, "%s", color); + r += vfprintf(fp, fmt, args); if (*color) - r += printf("%s", COLOR_RESET); + r += fprintf(fp, "%s", COLOR_RESET); if (trail) - r += printf("%s", trail); + r += fprintf(fp, "%s", trail); return r; } -int color_printf(const char *color, const char *fmt, ...) +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, NULL); + r = color_vfprintf(fp, color, fmt, args, NULL); va_end(args); return r; } -int color_printf_ln(const char *color, const char *fmt, ...) +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); - r = color_vprintf(color, fmt, args, "\n"); + r = color_vfprintf(fp, color, fmt, args, "\n"); va_end(args); return r; } @@ -6,7 +6,7 @@ int git_config_colorbool(const char *var, const char *value); void color_parse(const char *var, const char *value, char *dst); -int color_printf(const char *color, const char *fmt, ...); -int color_printf_ln(const char *color, const char *fmt, ...); +int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); +int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); #endif /* COLOR_H */ @@ -128,4 +128,9 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); int in_merge_bases(struct commit *, struct commit **, int); + +extern int interactive_add(void); +extern void add_files_to_cache(int verbose, const char *prefix, const char **files); +extern int rerere(void); + #endif /* COMMIT_H */ @@ -468,24 +468,26 @@ char *get_port(char *host) } /* - * This returns 0 if the transport protocol does not need fork(2), - * or a process id if it does. Once done, finish the connection + * This returns NULL if the transport protocol does not need fork(2), or a + * struct child_process object if it does. Once done, finish the connection * with finish_connect() with the value returned from this function - * (it is safe to call finish_connect() with 0 to support the former + * (it is safe to call finish_connect() with NULL to support the former * case). * - * Does not return a negative value on error; it just dies. + * If it returns, the connect is successful; it just dies on errors. */ -pid_t git_connect(int fd[2], char *url, const char *prog, int flags) +struct child_process *git_connect(int fd[2], char *url, + const char *prog, int flags) { char *host, *path = url; char *end; int c; - int pipefd[2][2]; - pid_t pid; + struct child_process *conn; enum protocol protocol = PROTO_LOCAL; int free_path = 0; char *port = NULL; + const char **arg; + struct strbuf cmd; /* Without this we cannot rely on waitpid() to tell * what happened to our children. @@ -568,74 +570,68 @@ pid_t git_connect(int fd[2], char *url, const char *prog, int flags) free(target_host); if (free_path) free(path); - return 0; + return NULL; } - if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0) - die("unable to create pipe pair for communication"); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - struct strbuf cmd; - - strbuf_init(&cmd, MAX_CMD_LEN); - strbuf_addstr(&cmd, prog); - strbuf_addch(&cmd, ' '); - sq_quote_buf(&cmd, path); - if (cmd.len >= MAX_CMD_LEN) - die("command line too long"); - - dup2(pipefd[1][0], 0); - dup2(pipefd[0][1], 1); - close(pipefd[0][0]); - close(pipefd[0][1]); - close(pipefd[1][0]); - close(pipefd[1][1]); - if (protocol == PROTO_SSH) { - const char *ssh, *ssh_basename; - ssh = getenv("GIT_SSH"); - if (!ssh) ssh = "ssh"; - ssh_basename = strrchr(ssh, '/'); - if (!ssh_basename) - ssh_basename = ssh; - else - ssh_basename++; - - if (!port) - execlp(ssh, ssh_basename, host, cmd.buf, NULL); - else - execlp(ssh, ssh_basename, "-p", port, host, - cmd.buf, NULL); - } - else { - unsetenv(ALTERNATE_DB_ENVIRONMENT); - unsetenv(DB_ENVIRONMENT); - unsetenv(GIT_DIR_ENVIRONMENT); - unsetenv(GIT_WORK_TREE_ENVIRONMENT); - unsetenv(GRAFT_ENVIRONMENT); - unsetenv(INDEX_ENVIRONMENT); - execlp("sh", "sh", "-c", cmd.buf, NULL); + conn = xcalloc(1, sizeof(*conn)); + + strbuf_init(&cmd, MAX_CMD_LEN); + strbuf_addstr(&cmd, prog); + strbuf_addch(&cmd, ' '); + sq_quote_buf(&cmd, path); + if (cmd.len >= MAX_CMD_LEN) + die("command line too long"); + + conn->in = conn->out = -1; + conn->argv = arg = xcalloc(6, sizeof(*arg)); + if (protocol == PROTO_SSH) { + const char *ssh = getenv("GIT_SSH"); + if (!ssh) ssh = "ssh"; + + *arg++ = ssh; + if (port) { + *arg++ = "-p"; + *arg++ = port; } - die("exec failed"); + *arg++ = host; } - fd[0] = pipefd[0][0]; - fd[1] = pipefd[1][1]; - close(pipefd[0][1]); - close(pipefd[1][0]); + else { + /* remove these from the environment */ + const char *env[] = { + ALTERNATE_DB_ENVIRONMENT, + DB_ENVIRONMENT, + GIT_DIR_ENVIRONMENT, + GIT_WORK_TREE_ENVIRONMENT, + GRAFT_ENVIRONMENT, + INDEX_ENVIRONMENT, + NULL + }; + conn->env = env; + *arg++ = "sh"; + *arg++ = "-c"; + } + *arg++ = cmd.buf; + *arg = NULL; + + if (start_command(conn)) + die("unable to fork"); + + fd[0] = conn->out; /* read from child's stdout */ + fd[1] = conn->in; /* write to child's stdin */ + strbuf_release(&cmd); if (free_path) free(path); - return pid; + return conn; } -int finish_connect(pid_t pid) +int finish_connect(struct child_process *conn) { - if (pid == 0) + int code; + if (!conn) return 0; - while (waitpid(pid, NULL, 0) < 0) { - if (errno != EINTR) - return -1; - } - return 0; + code = finish_command(conn); + free(conn->argv); + free(conn); + return code; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e760930740..599b2fc571 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -346,7 +346,6 @@ __git_commands () ssh-*) : transport;; stripspace) : plumbing;; svn) : import export;; - svnimport) : import;; symbolic-ref) : plumbing;; tar-tree) : deprecated;; unpack-file) : plumbing;; diff --git a/git-svnimport.perl b/contrib/examples/git-svnimport.perl index ea8c1b2f60..ea8c1b2f60 100755 --- a/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl diff --git a/Documentation/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 71aad8b45b..71aad8b45b 100644 --- a/Documentation/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt @@ -192,48 +192,39 @@ static int crlf_to_worktree(const char *path, const char *src, size_t len, return 1; } -static int filter_buffer(const char *path, const char *src, - unsigned long size, const char *cmd) +struct filter_params { + const char *src; + unsigned long size; + const char *cmd; +}; + +static int filter_buffer(int fd, void *data) { /* * Spawn cmd and feed the buffer contents through its stdin. */ struct child_process child_process; - int pipe_feed[2]; + struct filter_params *params = (struct filter_params *)data; int write_err, status; + const char *argv[] = { "sh", "-c", params->cmd, NULL }; memset(&child_process, 0, sizeof(child_process)); + child_process.argv = argv; + child_process.in = -1; + child_process.out = fd; - if (pipe(pipe_feed) < 0) { - error("cannot create pipe to run external filter %s", cmd); - return 1; - } - - child_process.pid = fork(); - if (child_process.pid < 0) { - error("cannot fork to run external filter %s", cmd); - close(pipe_feed[0]); - close(pipe_feed[1]); - return 1; - } - if (!child_process.pid) { - dup2(pipe_feed[0], 0); - close(pipe_feed[0]); - close(pipe_feed[1]); - execlp("sh", "sh", "-c", cmd, NULL); - return 1; - } - close(pipe_feed[0]); + if (start_command(&child_process)) + return error("cannot fork to run external filter %s", params->cmd); - write_err = (write_in_full(pipe_feed[1], src, size) < 0); - if (close(pipe_feed[1])) + write_err = (write_in_full(child_process.in, params->src, params->size) < 0); + if (close(child_process.in)) write_err = 1; if (write_err) - error("cannot feed the input to external filter %s", cmd); + error("cannot feed the input to external filter %s", params->cmd); status = finish_command(&child_process); if (status) - error("external filter %s failed %d", cmd, -status); + error("external filter %s failed %d", params->cmd, -status); return (write_err || status); } @@ -246,49 +237,36 @@ static int apply_filter(const char *path, const char *src, size_t len, * * (child --> cmd) --> us */ - int pipe_feed[2]; - int status, ret = 1; - struct child_process child_process; + int ret = 1; struct strbuf nbuf; + struct async async; + struct filter_params params; if (!cmd) return 0; - memset(&child_process, 0, sizeof(child_process)); - - if (pipe(pipe_feed) < 0) { - error("cannot create pipe to run external filter %s", cmd); - return 0; - } + memset(&async, 0, sizeof(async)); + async.proc = filter_buffer; + async.data = ¶ms; + params.src = src; + params.size = len; + params.cmd = cmd; fflush(NULL); - child_process.pid = fork(); - if (child_process.pid < 0) { - error("cannot fork to run external filter %s", cmd); - close(pipe_feed[0]); - close(pipe_feed[1]); - return 0; - } - if (!child_process.pid) { - dup2(pipe_feed[1], 1); - close(pipe_feed[0]); - close(pipe_feed[1]); - exit(filter_buffer(path, src, len, cmd)); - } - close(pipe_feed[1]); + if (start_async(&async)) + return 0; /* error was already reported */ strbuf_init(&nbuf, 0); - if (strbuf_read(&nbuf, pipe_feed[0], len) < 0) { + if (strbuf_read(&nbuf, async.out, len) < 0) { error("read from external filter %s failed", cmd); ret = 0; } - if (close(pipe_feed[0])) { + if (close(async.out)) { error("read from external filter %s failed", cmd); ret = 0; } - status = finish_command(&child_process); - if (status) { - error("external filter %s failed %d", cmd, -status); + if (finish_async(&async)) { + error("external filter %s failed", cmd); ret = 0; } diff --git a/csum-file.c b/csum-file.c index 9ab997120d..3729e73e19 100644 --- a/csum-file.c +++ b/csum-file.c @@ -8,6 +8,7 @@ * able to verify hasn't been messed with afterwards. */ #include "cache.h" +#include "progress.h" #include "csum-file.h" static void sha1flush(struct sha1file *f, unsigned int count) @@ -17,6 +18,7 @@ static void sha1flush(struct sha1file *f, unsigned int count) for (;;) { int ret = xwrite(f->fd, buf, count); if (ret > 0) { + display_throughput(f->tp, ret); buf = (char *) buf + ret; count -= ret; if (count) @@ -31,22 +33,27 @@ static void sha1flush(struct sha1file *f, unsigned int count) int sha1close(struct sha1file *f, unsigned char *result, int final) { + int fd; unsigned offset = f->offset; if (offset) { SHA1_Update(&f->ctx, f->buffer, offset); sha1flush(f, offset); f->offset = 0; } - if (!final) - return 0; /* only want to flush (no checksum write, no close) */ - SHA1_Final(f->buffer, &f->ctx); - if (result) - hashcpy(result, f->buffer); - sha1flush(f, 20); - if (close(f->fd)) - die("%s: sha1 file error on close (%s)", f->name, strerror(errno)); + if (final) { + /* write checksum and close fd */ + SHA1_Final(f->buffer, &f->ctx); + if (result) + hashcpy(result, f->buffer); + sha1flush(f, 20); + if (close(f->fd)) + die("%s: sha1 file error on close (%s)", + f->name, strerror(errno)); + fd = 0; + } else + fd = f->fd; free(f); - return 0; + return fd; } int sha1write(struct sha1file *f, void *buf, unsigned int count) @@ -75,6 +82,11 @@ int sha1write(struct sha1file *f, void *buf, unsigned int count) struct sha1file *sha1fd(int fd, const char *name) { + return sha1fd_throughput(fd, name, NULL); +} + +struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp) +{ struct sha1file *f; unsigned len; @@ -89,6 +101,7 @@ struct sha1file *sha1fd(int fd, const char *name) f->fd = fd; f->error = 0; f->offset = 0; + f->tp = tp; f->do_crc = 0; SHA1_Init(&f->ctx); return f; diff --git a/csum-file.h b/csum-file.h index c3c792f1b5..4d1b231292 100644 --- a/csum-file.h +++ b/csum-file.h @@ -1,11 +1,14 @@ #ifndef CSUM_FILE_H #define CSUM_FILE_H +struct progress; + /* A SHA1-protected file */ struct sha1file { int fd, error; unsigned int offset, namelen; SHA_CTX ctx; + struct progress *tp; char name[PATH_MAX]; int do_crc; uint32_t crc32; @@ -13,6 +16,7 @@ struct sha1file { }; extern struct sha1file *sha1fd(int fd, const char *name); +extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp); extern int sha1close(struct sha1file *, unsigned char *, int); extern int sha1write(struct sha1file *, void *, unsigned int); extern void crc32_begin(struct sha1file *); @@ -9,6 +9,7 @@ #include "xdiff-interface.h" #include "color.h" #include "attr.h" +#include "run-command.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -1440,9 +1441,18 @@ struct diff_filespec *alloc_filespec(const char *path) memset(spec, 0, sizeof(*spec)); spec->path = (char *)(spec + 1); memcpy(spec->path, path, namelen+1); + spec->count = 1; return spec; } +void free_filespec(struct diff_filespec *spec) +{ + if (!--spec->count) { + diff_free_filespec_data(spec); + free(spec); + } +} + void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, unsigned short mode) { @@ -1752,40 +1762,6 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } -static int spawn_prog(const char *pgm, const char **arg) -{ - pid_t pid; - int status; - - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - execvp(pgm, (char *const*) arg); - exit(255); - } - - while (waitpid(pid, &status, 0) < 0) { - if (errno == EINTR) - continue; - return -1; - } - - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. - */ - if (WIFEXITED(status) && !WEXITSTATUS(status)) - return 0; - return -1; -} - /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -1838,7 +1814,8 @@ static void run_external_diff(const char *pgm, *arg++ = name; } *arg = NULL; - retval = spawn_prog(pgm, spawn_arg); + fflush(NULL); + retval = run_command_v_opt(spawn_arg, 0); remove_tempfile(); if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); @@ -2435,10 +2412,8 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *queue, void diff_free_filepair(struct diff_filepair *p) { - diff_free_filespec_data(p->one); - diff_free_filespec_data(p->two); - free(p->one); - free(p->two); + free_filespec(p->one); + free_filespec(p->two); free(p); } @@ -2590,9 +2565,9 @@ void diff_debug_filepair(const struct diff_filepair *p, int i) { diff_debug_filespec(p->one, i, "one"); diff_debug_filespec(p->two, i, "two"); - fprintf(stderr, "score %d, status %c stays %d broken %d\n", + fprintf(stderr, "score %d, status %c rename_used %d broken %d\n", p->score, p->status ? p->status : '?', - p->source_stays, p->broken_pair); + p->one->rename_used, p->broken_pair); } void diff_debug_queue(const char *msg, struct diff_queue_struct *q) @@ -2610,8 +2585,8 @@ void diff_debug_queue(const char *msg, struct diff_queue_struct *q) static void diff_resolve_rename_copy(void) { - int i, j; - struct diff_filepair *p, *pp; + int i; + struct diff_filepair *p; struct diff_queue_struct *q = &diff_queued_diff; diff_debug_queue("resolve-rename-copy", q); @@ -2633,27 +2608,21 @@ static void diff_resolve_rename_copy(void) * either in-place edit or rename/copy edit. */ else if (DIFF_PAIR_RENAME(p)) { - if (p->source_stays) { - p->status = DIFF_STATUS_COPIED; - continue; - } - /* See if there is some other filepair that - * copies from the same source as us. If so - * we are a copy. Otherwise we are either a - * copy if the path stays, or a rename if it - * does not, but we already handled "stays" case. + /* + * A rename might have re-connected a broken + * pair up, causing the pathnames to be the + * same again. If so, that's not a rename at + * all, just a modification.. + * + * Otherwise, see if this source was used for + * multiple renames, in which case we decrement + * the count, and call it a copy. */ - for (j = i + 1; j < q->nr; j++) { - pp = q->queue[j]; - if (strcmp(pp->one->path, p->one->path)) - continue; /* not us */ - if (!DIFF_PAIR_RENAME(pp)) - continue; /* not a rename/copy */ - /* pp is a rename/copy from the same source */ + if (!strcmp(p->one->path, p->two->path)) + p->status = DIFF_STATUS_MODIFIED; + else if (--p->one->rename_used > 0) p->status = DIFF_STATUS_COPIED; - break; - } - if (!p->status) + else p->status = DIFF_STATUS_RENAMED; } else if (hashcmp(p->one->sha1, p->two->sha1) || diff --git a/diffcore-rename.c b/diffcore-rename.c index 142e5376dd..f9ebea5640 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,6 +4,7 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "hash.h" /* Table of rename/copy destinations */ @@ -55,12 +56,10 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, static struct diff_rename_src { struct diff_filespec *one; unsigned short score; /* to remember the break score */ - unsigned src_path_left : 1; } *rename_src; static int rename_src_nr, rename_src_alloc; static struct diff_rename_src *register_rename_src(struct diff_filespec *one, - int src_path_left, unsigned short score) { int first, last; @@ -92,33 +91,9 @@ static struct diff_rename_src *register_rename_src(struct diff_filespec *one, (rename_src_nr - first - 1) * sizeof(*rename_src)); rename_src[first].one = one; rename_src[first].score = score; - rename_src[first].src_path_left = src_path_left; return &(rename_src[first]); } -static int is_exact_match(struct diff_filespec *src, - struct diff_filespec *dst, - int contents_too) -{ - if (src->sha1_valid && dst->sha1_valid && - !hashcmp(src->sha1, dst->sha1)) - return 1; - if (!contents_too) - return 0; - if (diff_populate_filespec(src, 1) || diff_populate_filespec(dst, 1)) - return 0; - if (src->size != dst->size) - return 0; - if (src->sha1_valid && dst->sha1_valid) - return !hashcmp(src->sha1, dst->sha1); - if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) - return 0; - if (src->size == dst->size && - !memcmp(src->data, dst->data, src->size)) - return 1; - return 0; -} - static int basename_same(struct diff_filespec *src, struct diff_filespec *dst) { int src_len = strlen(src->path), dst_len = strlen(dst->path); @@ -169,6 +144,20 @@ static int estimate_similarity(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; + /* + * Need to check that source and destination sizes are + * filled in before comparing them. + * + * If we already have "cnt_data" filled in, we know it's + * all good (avoid checking the size for zero, as that + * is a possible size - we really should have a flag to + * say whether the size is valid or not!) + */ + if (!src->cnt_data && diff_populate_filespec(src, 0)) + return 0; + if (!dst->cnt_data && diff_populate_filespec(dst, 0)) + return 0; + max_size = ((src->size > dst->size) ? src->size : dst->size); base_size = ((src->size < dst->size) ? src->size : dst->size); delta_size = max_size - base_size; @@ -184,11 +173,6 @@ static int estimate_similarity(struct diff_filespec *src, if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) return 0; - if ((!src->cnt_data && diff_populate_filespec(src, 0)) - || (!dst->cnt_data && diff_populate_filespec(dst, 0))) - return 0; /* error but caught downstream */ - - delta_limit = (unsigned long) (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE); if (diffcore_count_changes(src, dst, @@ -209,27 +193,25 @@ static int estimate_similarity(struct diff_filespec *src, static void record_rename_pair(int dst_index, int src_index, int score) { - struct diff_filespec *one, *two, *src, *dst; + struct diff_filespec *src, *dst; struct diff_filepair *dp; if (rename_dst[dst_index].pair) die("internal error: dst already matched."); src = rename_src[src_index].one; - one = alloc_filespec(src->path); - fill_filespec(one, src->sha1, src->mode); + src->rename_used++; + src->count++; dst = rename_dst[dst_index].two; - two = alloc_filespec(dst->path); - fill_filespec(two, dst->sha1, dst->mode); + dst->count++; - dp = diff_queue(NULL, one, two); + dp = diff_queue(NULL, src, dst); dp->renamed_pair = 1; if (!strcmp(src->path, dst->path)) dp->score = rename_src[src_index].score; else dp->score = score; - dp->source_stays = rename_src[src_index].src_path_left; rename_dst[dst_index].pair = dp; } @@ -247,19 +229,155 @@ static int score_compare(const void *a_, const void *b_) return b->score - a->score; } -static int compute_stays(struct diff_queue_struct *q, - struct diff_filespec *one) +struct file_similarity { + int src_dst, index; + struct diff_filespec *filespec; + struct file_similarity *next; +}; + +static int find_identical_files(struct file_similarity *src, + struct file_similarity *dst) { - int i; - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (strcmp(one->path, p->two->path)) - continue; - if (DIFF_PAIR_RENAME(p)) { - return 0; /* something else is renamed into this */ + int renames = 0; + + /* + * Walk over all the destinations ... + */ + do { + struct diff_filespec *one = dst->filespec; + struct file_similarity *p, *best; + int i = 100; + + /* + * .. to find the best source match + */ + best = NULL; + for (p = src; p; p = p->next) { + struct diff_filespec *two = p->filespec; + + /* False hash collission? */ + if (hashcmp(one->sha1, two->sha1)) + continue; + /* Non-regular files? If so, the modes must match! */ + if (!S_ISREG(one->mode) || !S_ISREG(two->mode)) { + if (one->mode != two->mode) + continue; + } + best = p; + if (basename_same(one, two)) + break; + + /* Too many identical alternatives? Pick one */ + if (!--i) + break; + } + if (best) { + record_rename_pair(dst->index, best->index, MAX_SCORE); + renames++; } + } while ((dst = dst->next) != NULL); + return renames; +} + +static void free_similarity_list(struct file_similarity *p) +{ + while (p) { + struct file_similarity *entry = p; + p = p->next; + free(entry); } - return 1; +} + +static int find_same_files(void *ptr) +{ + int ret; + struct file_similarity *p = ptr; + struct file_similarity *src = NULL, *dst = NULL; + + /* Split the hash list up into sources and destinations */ + do { + struct file_similarity *entry = p; + p = p->next; + if (entry->src_dst < 0) { + entry->next = src; + src = entry; + } else { + entry->next = dst; + dst = entry; + } + } while (p); + + /* + * If we have both sources *and* destinations, see if + * we can match them up + */ + ret = (src && dst) ? find_identical_files(src, dst) : 0; + + /* Free the hashes and return the number of renames found */ + free_similarity_list(src); + free_similarity_list(dst); + return ret; +} + +static unsigned int hash_filespec(struct diff_filespec *filespec) +{ + unsigned int hash; + if (!filespec->sha1_valid) { + if (diff_populate_filespec(filespec, 0)) + return 0; + hash_sha1_file(filespec->data, filespec->size, "blob", filespec->sha1); + } + memcpy(&hash, filespec->sha1, sizeof(hash)); + return hash; +} + +static void insert_file_table(struct hash_table *table, int src_dst, int index, struct diff_filespec *filespec) +{ + void **pos; + unsigned int hash; + struct file_similarity *entry = xmalloc(sizeof(*entry)); + + entry->src_dst = src_dst; + entry->index = index; + entry->filespec = filespec; + entry->next = NULL; + + hash = hash_filespec(filespec); + pos = insert_hash(hash, entry, table); + + /* We already had an entry there? */ + if (pos) { + entry->next = *pos; + *pos = entry; + } +} + +/* + * Find exact renames first. + * + * The first round matches up the up-to-date entries, + * and then during the second round we try to match + * cache-dirty entries as well. + */ +static int find_exact_renames(void) +{ + int i; + struct hash_table file_table; + + init_hash(&file_table); + for (i = 0; i < rename_src_nr; i++) + insert_file_table(&file_table, -1, i, rename_src[i].one); + + for (i = 0; i < rename_dst_nr; i++) + insert_file_table(&file_table, 1, i, rename_dst[i].two); + + /* Find the renames */ + i = for_each_hash(&file_table, find_same_files); + + /* .. and free the hash data structure */ + free_hash(&file_table); + + return i; } void diffcore_rename(struct diff_options *options) @@ -270,12 +388,11 @@ void diffcore_rename(struct diff_options *options) struct diff_queue_struct *q = &diff_queued_diff; struct diff_queue_struct outq; struct diff_score *mx; - int i, j, rename_count, contents_too; + int i, j, rename_count; int num_create, num_src, dst_cnt; if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; - rename_count = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -289,81 +406,66 @@ void diffcore_rename(struct diff_options *options) locate_rename_dst(p->two, 1); } else if (!DIFF_FILE_VALID(p->two)) { - /* If the source is a broken "delete", and + /* + * If the source is a broken "delete", and * they did not really want to get broken, * that means the source actually stays. + * So we increment the "rename_used" score + * by one, to indicate ourselves as a user + */ + if (p->broken_pair && !p->score) + p->one->rename_used++; + register_rename_src(p->one, p->score); + } + else if (detect_rename == DIFF_DETECT_COPY) { + /* + * Increment the "rename_used" score by + * one, to indicate ourselves as a user. */ - int stays = (p->broken_pair && !p->score); - register_rename_src(p->one, stays, p->score); + p->one->rename_used++; + register_rename_src(p->one, p->score); } - else if (detect_rename == DIFF_DETECT_COPY) - register_rename_src(p->one, 1, p->score); } if (rename_dst_nr == 0 || rename_src_nr == 0) goto cleanup; /* nothing to do */ /* + * We really want to cull the candidates list early + * with cheap tests in order to avoid doing deltas. + */ + rename_count = find_exact_renames(); + + /* Did we only want exact renames? */ + if (minimum_score == MAX_SCORE) + goto cleanup; + + /* + * Calculate how many renames are left (but all the source + * files still remain as options for rename/copies!) + */ + num_create = (rename_dst_nr - rename_count); + num_src = rename_src_nr; + + /* All done? */ + if (!num_create) + goto cleanup; + + /* * This basically does a test for the rename matrix not * growing larger than a "rename_limit" square matrix, ie: * - * rename_dst_nr * rename_src_nr > rename_limit * rename_limit + * num_create * num_src > rename_limit * rename_limit * * but handles the potential overflow case specially (and we * assume at least 32-bit integers) */ if (rename_limit <= 0 || rename_limit > 32767) rename_limit = 32767; - if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit) + if (num_create > rename_limit && num_src > rename_limit) goto cleanup; - if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit) + if (num_create * num_src > rename_limit * rename_limit) goto cleanup; - /* We really want to cull the candidates list early - * with cheap tests in order to avoid doing deltas. - * The first round matches up the up-to-date entries, - * and then during the second round we try to match - * cache-dirty entries as well. - */ - for (contents_too = 0; contents_too < 2; contents_too++) { - for (i = 0; i < rename_dst_nr; i++) { - struct diff_filespec *two = rename_dst[i].two; - if (rename_dst[i].pair) - continue; /* dealt with an earlier round */ - for (j = 0; j < rename_src_nr; j++) { - int k; - struct diff_filespec *one = rename_src[j].one; - if (!is_exact_match(one, two, contents_too)) - continue; - - /* see if there is a basename match, too */ - for (k = j; k < rename_src_nr; k++) { - one = rename_src[k].one; - if (basename_same(one, two) && - is_exact_match(one, two, - contents_too)) { - j = k; - break; - } - } - - record_rename_pair(i, j, (int)MAX_SCORE); - rename_count++; - break; /* we are done with this entry */ - } - } - } - - /* Have we run out the created file pool? If so we can avoid - * doing the delta matrix altogether. - */ - if (rename_count == rename_dst_nr) - goto cleanup; - - if (minimum_score == MAX_SCORE) - goto cleanup; - - num_create = (rename_dst_nr - rename_count); - num_src = rename_src_nr; mx = xmalloc(sizeof(*mx) * num_create * num_src); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { int base = dst_cnt * num_src; @@ -452,16 +554,7 @@ void diffcore_rename(struct diff_options *options) pair_to_free = p; } else { - for (j = 0; j < rename_dst_nr; j++) { - if (!rename_dst[j].pair) - continue; - if (strcmp(rename_dst[j].pair-> - one->path, - p->one->path)) - continue; - break; - } - if (j < rename_dst_nr) + if (p->one->rename_used) /* this path remains */ pair_to_free = p; } @@ -487,27 +580,8 @@ void diffcore_rename(struct diff_options *options) *q = outq; diff_debug_queue("done collapsing", q); - /* We need to see which rename source really stays here; - * earlier we only checked if the path is left in the result, - * but even if a path remains in the result, if that is coming - * from copying something else on top of it, then the original - * source is lost and does not stay. - */ - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - if (DIFF_PAIR_RENAME(p) && p->source_stays) { - /* If one appears as the target of a rename-copy, - * then mark p->source_stays = 0; otherwise - * leave it as is. - */ - p->source_stays = compute_stays(q, p->one); - } - } - - for (i = 0; i < rename_dst_nr; i++) { - diff_free_filespec_data(rename_dst[i].two); - free(rename_dst[i].two); - } + for (i = 0; i < rename_dst_nr; i++) + free_filespec(rename_dst[i].two); free(rename_dst); rename_dst = NULL; diff --git a/diffcore.h b/diffcore.h index eb618b1ec0..cc96c20734 100644 --- a/diffcore.h +++ b/diffcore.h @@ -29,7 +29,9 @@ struct diff_filespec { void *cnt_data; const char *funcname_pattern_ident; unsigned long size; + int count; /* Reference count */ int xfrm_flags; /* for use by the xfrm */ + int rename_used; /* Count of rename users */ unsigned short mode; /* file mode */ unsigned sha1_valid : 1; /* if true, use sha1 and trust mode; * if false, use the name and read from @@ -43,6 +45,7 @@ struct diff_filespec { }; extern struct diff_filespec *alloc_filespec(const char *); +extern void free_filespec(struct diff_filespec *); extern void fill_filespec(struct diff_filespec *, const unsigned char *, unsigned short); @@ -56,7 +59,6 @@ struct diff_filepair { struct diff_filespec *two; unsigned short int score; char status; /* M C R N D U (see Documentation/diff-format.txt) */ - unsigned source_stays : 1; /* all of R/C are copies */ unsigned broken_pair : 1; unsigned renamed_pair : 1; unsigned is_unmerged : 1; diff --git a/exec_cmd.c b/exec_cmd.c index 9b74ed2f42..2d0a758512 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,11 +5,11 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path; +static const char *argv_exec_path; -void git_set_exec_path(const char *exec_path) +void git_set_argv_exec_path(const char *exec_path) { - current_exec_path = exec_path; + argv_exec_path = exec_path; } @@ -18,8 +18,8 @@ const char *git_exec_path(void) { const char *env; - if (current_exec_path) - return current_exec_path; + if (argv_exec_path) + return argv_exec_path; env = getenv(EXEC_PATH_ENVIRONMENT); if (env && *env) { @@ -29,85 +29,69 @@ const char *git_exec_path(void) return builtin_exec_path; } +static void add_path(struct strbuf *out, const char *path) +{ + if (path && *path) { + if (is_absolute_path(path)) + strbuf_addstr(out, path); + else + strbuf_addstr(out, make_absolute_path(path)); + + strbuf_addch(out, ':'); + } +} + +void setup_path(const char *cmd_path) +{ + const char *old_path = getenv("PATH"); + struct strbuf new_path; + + strbuf_init(&new_path, 0); + + add_path(&new_path, argv_exec_path); + add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); + add_path(&new_path, builtin_exec_path); + add_path(&new_path, cmd_path); + + if (old_path) + strbuf_addstr(&new_path, old_path); + else + strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin"); + + setenv("PATH", new_path.buf, 1); + + strbuf_release(&new_path); +} int execv_git_cmd(const char **argv) { - char git_command[PATH_MAX + 1]; - int i; - const char *paths[] = { current_exec_path, - getenv(EXEC_PATH_ENVIRONMENT), - builtin_exec_path }; - - for (i = 0; i < ARRAY_SIZE(paths); ++i) { - size_t len; - int rc; - const char *exec_dir = paths[i]; - const char *tmp; - - if (!exec_dir || !*exec_dir) continue; - - if (*exec_dir != '/') { - if (!getcwd(git_command, sizeof(git_command))) { - fprintf(stderr, "git: cannot determine " - "current directory: %s\n", - strerror(errno)); - break; - } - len = strlen(git_command); - - /* Trivial cleanup */ - while (!prefixcmp(exec_dir, "./")) { - exec_dir += 2; - while (*exec_dir == '/') - exec_dir++; - } - - rc = snprintf(git_command + len, - sizeof(git_command) - len, "/%s", - exec_dir); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - } else { - if (strlen(exec_dir) + 1 > sizeof(git_command)) { - fprintf(stderr, "git: command name given " - "is too long.\n"); - break; - } - strcpy(git_command, exec_dir); - } - - len = strlen(git_command); - rc = snprintf(git_command + len, sizeof(git_command) - len, - "/git-%s", argv[0]); - if (rc < 0 || rc >= sizeof(git_command) - len) { - fprintf(stderr, - "git: command name given is too long.\n"); - break; - } + struct strbuf cmd; + const char *tmp; - /* argv[0] must be the git command, but the argv array - * belongs to the caller, and my be reused in - * subsequent loop iterations. Save argv[0] and - * restore it on error. - */ + strbuf_init(&cmd, 0); + strbuf_addf(&cmd, "git-%s", argv[0]); - tmp = argv[0]; - argv[0] = git_command; + /* + * argv[0] must be the git command, but the argv array + * belongs to the caller, and may be reused in + * subsequent loop iterations. Save argv[0] and + * restore it on error. + */ + tmp = argv[0]; + argv[0] = cmd.buf; - trace_argv_printf(argv, -1, "trace: exec:"); + trace_argv_printf(argv, -1, "trace: exec:"); - /* execve() can only ever return if it fails */ - execve(git_command, (char **)argv, environ); + /* execvp() can only ever return if it fails */ + execvp(cmd.buf, (char **)argv); - trace_printf("trace: exec failed: %s\n", strerror(errno)); + trace_printf("trace: exec failed: %s\n", strerror(errno)); - argv[0] = tmp; - } - return -1; + argv[0] = tmp; + strbuf_release(&cmd); + + return -1; } diff --git a/exec_cmd.h b/exec_cmd.h index 849a8395a0..a892355c82 100644 --- a/exec_cmd.h +++ b/exec_cmd.h @@ -1,8 +1,9 @@ #ifndef GIT_EXEC_CMD_H #define GIT_EXEC_CMD_H -extern void git_set_exec_path(const char *exec_path); +extern void git_set_argv_exec_path(const char *exec_path); extern const char* git_exec_path(void); +extern void setup_path(const char *); extern int execv_git_cmd(const char **argv); /* NULL terminated */ extern int execl_git_cmd(const char *cmd, ...); diff --git a/git-bisect.sh b/git-bisect.sh index 388887a556..b74f44df60 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -1,12 +1,14 @@ #!/bin/sh -USAGE='[start|bad|good|next|reset|visualize|replay|log|run]' +USAGE='[start|bad|good|skip|next|reset|visualize|replay|log|run]' LONG_USAGE='git bisect start [<bad> [<good>...]] [--] [<pathspec>...] reset bisect state and start bisection. git bisect bad [<rev>] mark <rev> a known-bad revision. git bisect good [<rev>...] mark <rev>... known-good revisions. +git bisect skip [<rev>...] + mark <rev>... untestable revisions. git bisect next find next bisection to test and check it out. git bisect reset [<branch>] @@ -64,7 +66,7 @@ bisect_start() { branch=`cat "$GIT_DIR/head-name"` else branch=master - fi + fi git checkout $branch || exit ;; refs/heads/*) @@ -95,75 +97,74 @@ bisect_start() { arg="$1" case "$arg" in --) - shift + shift break ;; *) - rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { + rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || { test $has_double_dash -eq 1 && die "'$arg' does not appear to be a valid revision" break } - if [ $bad_seen -eq 0 ]; then - bad_seen=1 - bisect_write_bad "$rev" - else - bisect_write_good "$rev" - fi - shift + case $bad_seen in + 0) state='bad' ; bad_seen=1 ;; + *) state='good' ;; + esac + bisect_write "$state" "$rev" 'nolog' + shift ;; esac - done + done sq "$@" >"$GIT_DIR/BISECT_NAMES" echo "git-bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" bisect_auto_next } -bisect_bad() { - bisect_autostart - case "$#" in - 0) - rev=$(git rev-parse --verify HEAD) ;; - 1) - rev=$(git rev-parse --verify "$1^{commit}") ;; - *) - usage ;; - esac || exit - bisect_write_bad "$rev" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - bisect_auto_next -} - -bisect_write_bad() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" +bisect_write() { + state="$1" + rev="$2" + nolog="$3" + case "$state" in + bad) tag="$state" ;; + good|skip) tag="$state"-"$rev" ;; + *) die "Bad bisect_write argument: $state" ;; + esac + echo "$rev" >"$GIT_DIR/refs/bisect/$tag" + echo "# $state: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" + test -z "$nolog" && echo "git-bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } -bisect_good() { +bisect_state() { bisect_autostart - case "$#" in - 0) revs=$(git rev-parse --verify HEAD) || exit ;; - *) revs=$(git rev-parse --revs-only --no-flags "$@") && - test '' != "$revs" || die "Bad rev input: $@" ;; + state=$1 + case "$#,$state" in + 0,*) + die "Please call 'bisect_state' with at least one argument." ;; + 1,bad|1,good|1,skip) + rev=$(git rev-parse --verify HEAD) || + die "Bad rev input: HEAD" + bisect_write "$state" "$rev" ;; + 2,bad) + rev=$(git rev-parse --verify "$2^{commit}") || + die "Bad rev input: $2" + bisect_write "$state" "$rev" ;; + *,good|*,skip) + shift + revs=$(git rev-parse --revs-only --no-flags "$@") && + test '' != "$revs" || die "Bad rev input: $@" + for rev in $revs + do + rev=$(git rev-parse --verify "$rev^{commit}") || + die "Bad rev commit: $rev^{commit}" + bisect_write "$state" "$rev" + done ;; + *) + usage ;; esac - for rev in $revs - do - rev=$(git rev-parse --verify "$rev^{commit}") || exit - bisect_write_good "$rev" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - - done bisect_auto_next } -bisect_write_good() { - rev="$1" - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" -} - bisect_next_check() { missing_good= missing_bad= git show-ref -q --verify refs/bisect/bad || missing_bad=t @@ -206,17 +207,97 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } +filter_skipped() { + _eval="$1" + _skip="$2" + + if [ -z "$_skip" ]; then + eval $_eval + return + fi + + # Let's parse the output of: + # "git rev-list --bisect-vars --bisect-all ..." + eval $_eval | while read hash line + do + case "$VARS,$FOUND,$TRIED,$hash" in + # We display some vars. + 1,*,*,*) echo "$hash $line" ;; + + # Split line. + ,*,*,---*) ;; + + # We had nothing to search. + ,,,bisect_rev*) + echo "bisect_rev=" + VARS=1 + ;; + + # We did not find a good bisect rev. + # This should happen only if the "bad" + # commit is also a "skip" commit. + ,,*,bisect_rev*) + echo "bisect_rev=$TRIED" + VARS=1 + ;; + + # We are searching. + ,,*,*) + TRIED="${TRIED:+$TRIED|}$hash" + case "$_skip" in + *$hash*) ;; + *) + echo "bisect_rev=$hash" + echo "bisect_tried=\"$TRIED\"" + FOUND=1 + ;; + esac + ;; + + # We have already found a rev to be tested. + ,1,*,bisect_rev*) VARS=1 ;; + ,1,*,*) ;; + + # ??? + *) die "filter_skipped error " \ + "VARS: '$VARS' " \ + "FOUND: '$FOUND' " \ + "TRIED: '$TRIED' " \ + "hash: '$hash' " \ + "line: '$line'" + ;; + esac + done +} + +exit_if_skipped_commits () { + _tried=$1 + if expr "$_tried" : ".*[|].*" > /dev/null ; then + echo "There are only 'skip'ped commit left to test." + echo "The first bad commit could be any of:" + echo "$_tried" | sed -e 's/[|]/\n/g' + echo "We cannot bisect more!" + exit 2 + fi +} + bisect_next() { - case "$#" in 0) ;; *) usage ;; esac + case "$#" in 0) ;; *) usage ;; esac bisect_autostart bisect_next_check good + skip=$(git for-each-ref --format='%(objectname)' \ + "refs/bisect/skip-*" | tr '[\012]' ' ') || exit + + BISECT_OPT='' + test -n "$skip" && BISECT_OPT='--bisect-all' + bad=$(git rev-parse --verify refs/bisect/bad) && good=$(git for-each-ref --format='^%(objectname)' \ "refs/bisect/good-*" | tr '[\012]' ' ') && - eval="git rev-list --bisect-vars $good $bad --" && + eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" && eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && - eval=$(eval "$eval") && + eval=$(filter_skipped "$eval" "$skip") && eval "$eval" || exit if [ -z "$bisect_rev" ]; then @@ -224,11 +305,16 @@ bisect_next() { exit 1 fi if [ "$bisect_rev" = "$bad" ]; then + exit_if_skipped_commits "$bisect_tried" echo "$bisect_rev is first bad commit" git diff-tree --pretty $bisect_rev exit 0 fi + # We should exit here only if the "bad" + # commit is also a "skip" commit (see above). + exit_if_skipped_commits "$bisect_rev" + echo "Bisecting: $bisect_nr revisions left to test after this" echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect" git checkout -q new-bisect || exit @@ -250,12 +336,10 @@ bisect_reset() { else branch=master fi ;; - 1) git show-ref --verify --quiet -- "refs/heads/$1" || { - echo >&2 "$1 does not seem to be a valid branch" - exit 1 - } + 1) git show-ref --verify --quiet -- "refs/heads/$1" || + die "$1 does not seem to be a valid branch" branch="$1" ;; - *) + *) usage ;; esac if git checkout "$branch"; then @@ -273,10 +357,7 @@ bisect_clean_state() { } bisect_replay () { - test -r "$1" || { - echo >&2 "cannot read $1 for replaying" - exit 1 - } + test -r "$1" || die "cannot read $1 for replaying" bisect_reset while read bisect command rev do @@ -284,21 +365,11 @@ bisect_replay () { case "$command" in start) cmd="bisect_start $rev" - eval "$cmd" - ;; - good) - echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev" - echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG" - ;; - bad) - echo "$rev" >"$GIT_DIR/refs/bisect/bad" - echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG" - echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG" - ;; + eval "$cmd" ;; + good|bad|skip) + bisect_write "$command" "$rev" ;; *) - echo >&2 "?? what are you talking about?" - exit 1 ;; + die "?? what are you talking about?" ;; esac done <"$1" bisect_auto_next @@ -320,24 +391,31 @@ bisect_run () { exit $res fi - # Use "bisect_good" or "bisect_bad" - # depending on run success or failure. - if [ $res -gt 0 ]; then - next_bisect='bisect_bad' + # Find current state depending on run success or failure. + # A special exit code of 125 means cannot test. + if [ $res -eq 125 ]; then + state='skip' + elif [ $res -gt 0 ]; then + state='bad' else - next_bisect='bisect_good' + state='good' fi - # We have to use a subshell because bisect_good or - # bisect_bad functions can exit. - ( $next_bisect > "$GIT_DIR/BISECT_RUN" ) + # We have to use a subshell because "bisect_state" can exit. + ( bisect_state $state > "$GIT_DIR/BISECT_RUN" ) res=$? cat "$GIT_DIR/BISECT_RUN" + if grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ + > /dev/null; then + echo >&2 "bisect run cannot continue any more" + exit $res + fi + if [ $res -ne 0 ]; then echo >&2 "bisect run failed:" - echo >&2 "$next_bisect exited with error code $res" + echo >&2 "'bisect_state $state' exited with error code $res" exit $res fi @@ -359,10 +437,8 @@ case "$#" in case "$cmd" in start) bisect_start "$@" ;; - bad) - bisect_bad "$@" ;; - good) - bisect_good "$@" ;; + bad|good|skip) + bisect_state "$cmd" "$@" ;; next) # Not sure we want "next" at the UI level anymore. bisect_next "$@" ;; diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index f284c88a46..26844af439 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -82,6 +82,7 @@ foreach my $line (@commit) { } } +my $noparent = "0000000000000000000000000000000000000000"; if ($parent) { my $found; # double check that it's a valid parent @@ -95,8 +96,10 @@ if ($parent) { } else { # we don't have a parent from the cmdline... if (@parents == 1) { # it's safe to get it from the commit $parent = $parents[0]; - } else { # or perhaps not! - die "This commit has more than one parent -- please name the parent you want to use explicitly"; + } elsif (@parents == 0) { # there is no parent + $parent = $noparent; + } else { # cannot choose automatically from multiple parents + die "This commit has more than one parent -- please name the parent you want to use explicitly"; } } @@ -116,7 +119,11 @@ if ($opt_a) { } close MSG; -`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; +if ($parent eq $noparent) { + `git-diff-tree --binary -p --root $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; +} else { + `git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff"; +} ## apply non-binary changes diff --git a/git-mergetool.sh b/git-mergetool.sh index 9f4f3134b6..a68b40386b 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -192,10 +192,10 @@ merge_file () { case "$merge_tool" in kdiff3) if base_present ; then - (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \ -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) else - (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ + ("$merge_tool_path" --auto --L1 "$path (Local)" --L2 "$path (Remote)" \ -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) fi status=$? @@ -203,35 +203,35 @@ merge_file () { ;; tkdiff) if base_present ; then - tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" else - tkdiff -o "$path" -- "$LOCAL" "$REMOTE" + "$merge_tool_path" -o "$path" -- "$LOCAL" "$REMOTE" fi status=$? save_backup ;; meld|vimdiff) touch "$BACKUP" - $merge_tool -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; gvimdiff) touch "$BACKUP" - gvimdiff -f -- "$LOCAL" "$path" "$REMOTE" + "$merge_tool_path" -f -- "$LOCAL" "$path" "$REMOTE" check_unchanged save_backup ;; xxdiff) touch "$BACKUP" if base_present ; then - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ --merged-file "$path" -- "$LOCAL" "$BASE" "$REMOTE" else - xxdiff -X --show-merged-pane \ + "$merge_tool_path" -X --show-merged-pane \ -R 'Accel.SaveAsMerged: "Ctrl-S"' \ -R 'Accel.Search: "Ctrl+F"' \ -R 'Accel.SearchForward: "Ctrl-G"' \ @@ -243,18 +243,28 @@ merge_file () { opendiff) touch "$BACKUP" if base_present; then - opendiff "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$path" | cat else - opendiff "$LOCAL" "$REMOTE" -merge "$path" | cat + "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$path" | cat + fi + check_unchanged + save_backup + ;; + ecmerge) + touch "$BACKUP" + if base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --mode=merge3 --to="$path" + else + "$merge_tool_path" "$LOCAL" "$REMOTE" --mode=merge2 --to="$path" fi check_unchanged save_backup ;; emerge) if base_present ; then - emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")" else - emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" + "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")" fi status=$? save_backup @@ -297,17 +307,38 @@ do shift done +valid_tool() { + case "$1" in + kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + ;; # happy + *) + return 1 + ;; + esac +} + +init_merge_tool_path() { + merge_tool_path=`git config mergetool.$1.path` + if test -z "$merge_tool_path" ; then + case "$1" in + emerge) + merge_tool_path=emacs + ;; + *) + merge_tool_path=$1 + ;; + esac + fi +} + + if test -z "$merge_tool"; then merge_tool=`git config merge.tool` - case "$merge_tool" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "") - ;; # happy - *) + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" echo >&2 "Resetting to default..." unset merge_tool - ;; - esac + fi fi if test -z "$merge_tool" ; then @@ -329,40 +360,30 @@ if test -z "$merge_tool" ; then merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" echo "merge tool candidates: $merge_tool_candidates" for i in $merge_tool_candidates; do - if test $i = emerge ; then - cmd=emacs - else - cmd=$i - fi - if type $cmd > /dev/null 2>&1; then + init_merge_tool_path $i + if type "$merge_tool_path" > /dev/null 2>&1; then merge_tool=$i break fi done if test -z "$merge_tool" ; then - echo "No available merge resolution programs available." + echo "No known merge resolution program available." exit 1 fi +else + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge_tool $merge_tool" + exit 1 + fi + + init_merge_tool_path "$merge_tool" + + if ! type "$merge_tool_path" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available as '$merge_tool_path'" + exit 1 + fi fi -case "$merge_tool" in - kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff) - if ! type "$merge_tool" > /dev/null 2>&1; then - echo "The merge tool $merge_tool is not available" - exit 1 - fi - ;; - emerge) - if ! type "emacs" > /dev/null 2>&1; then - echo "Emacs is not available" - exit 1 - fi - ;; - *) - echo "Unknown merge tool: $merge_tool" - exit 1 - ;; -esac if test $# -eq 0 ; then files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 0dd77b4005..76dc679e62 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -116,7 +116,7 @@ pick_one () { sha1=$(git rev-parse --short $sha1) output warn Fast forward to $sha1 else - output git cherry-pick $STRATEGY "$@" + output git cherry-pick "$@" fi } @@ -172,6 +172,8 @@ pick_one_preserving_merges () { author_script=$(get_author_ident_from_commit $sha1) eval "$author_script" msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')" + # No point in merging the first parent, that's HEAD + new_parents=${new_parents# $first_parent} # NEEDSWORK: give rerere a chance if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ @@ -184,7 +186,7 @@ pick_one_preserving_merges () { fi ;; *) - output git cherry-pick $STRATEGY "$@" || + output git cherry-pick "$@" || die_with_patch $sha1 "Could not pick $sha1" ;; esac @@ -387,7 +389,6 @@ do output git reset --hard && do_rest ;; -s|--strategy) - shift case "$#,$1" in *,*=*) STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;; diff --git a/git-rebase.sh b/git-rebase.sh index 1583402a06..224cca98ee 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -59,7 +59,7 @@ continue_merge () { die "$RESOLVEMSG" fi - cmt=`cat $dotest/current` + cmt=`cat "$dotest/current"` if ! git diff-index --quiet HEAD then if ! git-commit -C "$cmt" @@ -84,14 +84,14 @@ continue_merge () { } call_merge () { - cmt="$(cat $dotest/cmt.$1)" + cmt="$(cat "$dotest/cmt.$1")" echo "$cmt" > "$dotest/current" hd=$(git rev-parse --verify HEAD) cmt_name=$(git symbolic-ref HEAD) - msgnum=$(cat $dotest/msgnum) - end=$(cat $dotest/end) + msgnum=$(cat "$dotest/msgnum") + end=$(cat "$dotest/end") eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' - eval GITHEAD_$hd='"$(cat $dotest/onto_name)"' + eval GITHEAD_$hd='$(cat "$dotest/onto_name")' export GITHEAD_$cmt GITHEAD_$hd git-merge-$strategy "$cmt^" -- "$hd" "$cmt" rv=$? @@ -140,10 +140,10 @@ do } if test -d "$dotest" then - prev_head="`cat $dotest/prev_head`" - end="`cat $dotest/end`" - msgnum="`cat $dotest/msgnum`" - onto="`cat $dotest/onto`" + prev_head=$(cat "$dotest/prev_head") + end=$(cat "$dotest/end") + msgnum=$(cat "$dotest/msgnum") + onto=$(cat "$dotest/onto") continue_merge while test "$msgnum" -le "$end" do @@ -160,11 +160,11 @@ do if test -d "$dotest" then git rerere clear - prev_head="`cat $dotest/prev_head`" - end="`cat $dotest/end`" - msgnum="`cat $dotest/msgnum`" + prev_head=$(cat "$dotest/prev_head") + end=$(cat "$dotest/end") + msgnum=$(cat "$dotest/msgnum") msgnum=$(($msgnum + 1)) - onto="`cat $dotest/onto`" + onto=$(cat "$dotest/onto") while test "$msgnum" -le "$end" do call_merge "$msgnum" diff --git a/git-repack.sh b/git-repack.sh index e72adc4d91..7220635c96 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -83,9 +83,6 @@ for name in $names ; do fullbases="$fullbases pack-$name" chmod a-w "$PACKTMP-$name.pack" chmod a-w "$PACKTMP-$name.idx" - if test "$quiet" != '-q'; then - echo "Pack pack-$name created." - fi mkdir -p "$PACKDIR" || exit for sfx in pack idx diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 3c325fd133..86d7d4c4e7 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -110,7 +110,7 @@ esac if [ -z "$SUBDIRECTORY_OK" ] then : ${GIT_DIR=.git} - GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || { + test -z "$(git rev-parse --show-cdup)" || { exit=$? echo >&2 "You need to run this command from the toplevel of the working tree." exit $exit @@ -6,28 +6,6 @@ const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; -static void prepend_to_path(const char *dir, int len) -{ - const char *old_path = getenv("PATH"); - char *path; - int path_len = len; - - if (!old_path) - old_path = "/usr/local/bin:/usr/bin:/bin"; - - path_len = len + strlen(old_path) + 1; - - path = xmalloc(path_len + 1); - - memcpy(path, dir, len); - path[len] = ':'; - memcpy(path + len + 1, old_path, path_len - len); - - setenv("PATH", path, 1); - - free(path); -} - static int handle_options(const char*** argv, int* argc, int* envchanged) { int handled = 0; @@ -51,7 +29,7 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) if (!prefixcmp(cmd, "--exec-path")) { cmd += 11; if (*cmd == '=') - git_set_exec_path(cmd + 1); + git_set_argv_exec_path(cmd + 1); else { puts(git_exec_path()); exit(0); @@ -408,7 +386,7 @@ int main(int argc, const char **argv) { const char *cmd = argv[0] ? argv[0] : "git-help"; char *slash = strrchr(cmd, '/'); - const char *exec_path = NULL; + const char *cmd_path = NULL; int done_alias = 0; /* @@ -418,10 +396,7 @@ int main(int argc, const char **argv) */ if (slash) { *slash++ = 0; - if (*cmd == '/') - exec_path = cmd; - else - exec_path = xstrdup(make_absolute_path(cmd)); + cmd_path = cmd; cmd = slash; } @@ -450,23 +425,20 @@ int main(int argc, const char **argv) if (!prefixcmp(argv[0], "--")) argv[0] += 2; } else { - /* Default command: "help" */ - argv[0] = "help"; - argc = 1; + /* The user didn't specify a command; give them help */ + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + exit(1); } cmd = argv[0]; /* - * We execute external git command via execv_git_cmd(), - * which looks at "--exec-path" option, GIT_EXEC_PATH - * environment, and $(gitexecdir) in Makefile while built, - * in this order. For scripted commands, we prepend - * the value of the exec_path variable to the PATH. + * We use PATH to find git commands, but we prepend some higher + * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH + * environment, and the $(gitexecdir) from the Makefile at build + * time. */ - if (exec_path) - prepend_to_path(exec_path, strlen(exec_path)); - exec_path = git_exec_path(); - prepend_to_path(exec_path, strlen(exec_path)); + setup_path(cmd_path); while (1) { /* See if it's an internal command */ diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 6328e26f56..9cd5b0a2b1 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -116,7 +116,7 @@ GITWEB_CONFIG file: $feature{'pickaxe'}{'default'} = [1]; $feature{'pickaxe'}{'override'} = 1; - $feature{'snapshot'}{'default'} = ['x-gzip', 'gz', 'gzip']; + $feature{'snapshot'}{'default'} = ['zip', 'tgz']; $feature{'snapshot'}{'override'} = 1; diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 48e21dad6c..2e00756276 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -846,6 +846,23 @@ sub chop_str { return "$body$tail"; } +# takes the same arguments as chop_str, but also wraps a <span> around the +# result with a title attribute if it does get chopped. Additionally, the +# string is HTML-escaped. +sub chop_and_escape_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + my $chopped = chop_str($str, $len, $add_len); + if ($chopped eq $str) { + return esc_html($chopped); + } else { + return qq{<span title="} . esc_html($str) . qq{">} . + esc_html($chopped) . qq{</span>}; + } +} + ## ---------------------------------------------------------------------- ## functions returning short strings @@ -2000,6 +2017,19 @@ sub parse_difftree_raw_line { return wantarray ? %res : \%res; } +# wrapper: return parsed line of git-diff-tree "raw" output +# (the argument might be raw line, or parsed info) +sub parsed_difftree_line { + my $line_or_ref = shift; + + if (ref($line_or_ref) eq "HASH") { + # pre-parsed (or generated by hand) + return $line_or_ref; + } else { + return parse_difftree_raw_line($line_or_ref); + } +} + # parse line of git-ls-tree output sub parse_ls_tree_line ($;%) { my $line = shift; @@ -2043,6 +2073,7 @@ sub parse_from_to_diffinfo { } } } else { + # ordinary (not combined) diff $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'}; if ($diffinfo->{'status'} ne "A") { # not new (added) file $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent, @@ -2766,6 +2797,7 @@ sub git_print_tree_entry { ## ...................................................................... ## functions printing large fragments of HTML +# get pre-image filenames for merge (combined) diff sub fill_from_file_info { my ($diff, @parents) = @_; @@ -2782,28 +2814,25 @@ sub fill_from_file_info { return $diff; } -# parameters can be strings, or references to arrays of strings -sub from_ids_eq { - my ($a, $b) = @_; - - if (ref($a) eq "ARRAY" && ref($b) eq "ARRAY" && @$a == @$b) { - for (my $i = 0; $i < @$a; ++$i) { - return 0 unless ($a->[$i] eq $b->[$i]); - } - return 1; - } elsif (!ref($a) && !ref($b)) { - return $a eq $b; - } else { - return 0; - } -} - +# is current raw difftree line of file deletion sub is_deleted { my $diffinfo = shift; return $diffinfo->{'to_id'} eq ('0' x 40); } +# does patch correspond to [previous] difftree raw line +# $diffinfo - hashref of parsed raw diff format +# $patchinfo - hashref of parsed patch diff format +# (the same keys as in $diffinfo) +sub is_patch_split { + my ($diffinfo, $patchinfo) = @_; + + return defined $diffinfo && defined $patchinfo + && ($diffinfo->{'to_file'} || $diffinfo->{'file'}) eq $patchinfo->{'to_file'}; +} + + sub git_difftree_body { my ($difftree, $hash, @parents) = @_; my ($parent) = $parents[0]; @@ -2840,13 +2869,7 @@ sub git_difftree_body { my $alternate = 1; my $patchno = 0; foreach my $line (@{$difftree}) { - my $diff; - if (ref($line) eq "HASH") { - # pre-parsed (or generated by hand) - $diff = $line; - } else { - $diff = parse_difftree_raw_line($line); - } + my $diff = parsed_difftree_line($line); if ($alternate) { print "<tr class=\"dark\">\n"; @@ -3117,10 +3140,12 @@ sub git_patchset_body { my ($fd, $difftree, $hash, @hash_parents) = @_; my ($hash_parent) = $hash_parents[0]; + my $is_combined = (@hash_parents > 1); my $patch_idx = 0; my $patch_number = 0; my $patch_line; my $diffinfo; + my $to_name; my (%from, %to); print "<div class=\"patchset\">\n"; @@ -3134,73 +3159,46 @@ sub git_patchset_body { PATCH: while ($patch_line) { - my @diff_header; - my ($from_id, $to_id); - - # git diff header - #assert($patch_line =~ m/^diff /) if DEBUG; - #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed - $patch_number++; - push @diff_header, $patch_line; - - # extended diff header - EXTENDED_HEADER: - while ($patch_line = <$fd>) { - chomp $patch_line; - - last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); - - if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = $1; - $to_id = $2; - } elsif ($patch_line =~ m/^index ((?:[0-9a-fA-F]{40},)+[0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { - $from_id = [ split(',', $1) ]; - $to_id = $2; - } - push @diff_header, $patch_line; + # parse "git diff" header line + if ($patch_line =~ m/^diff --git (\"(?:[^\\\"]*(?:\\.[^\\\"]*)*)\"|[^ "]*) (.*)$/) { + # $1 is from_name, which we do not use + $to_name = unquote($2); + $to_name =~ s!^b/!!; + } elsif ($patch_line =~ m/^diff --(cc|combined) ("?.*"?)$/) { + # $1 is 'cc' or 'combined', which we do not use + $to_name = unquote($2); + } else { + $to_name = undef; } - my $last_patch_line = $patch_line; # check if current patch belong to current raw line # and parse raw git-diff line if needed - if (defined $diffinfo && - defined $from_id && defined $to_id && - from_ids_eq($diffinfo->{'from_id'}, $from_id) && - $diffinfo->{'to_id'} eq $to_id) { + if (is_patch_split($diffinfo, { 'to_file' => $to_name })) { # this is continuation of a split patch print "<div class=\"patch cont\">\n"; } else { # advance raw git-diff output if needed $patch_idx++ if defined $diffinfo; - # compact combined diff output can have some patches skipped - # find which patch (using pathname of result) we are at now - my $to_name; - if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) { - $to_name = $1; - } - - do { - # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + # read and prepare patch information + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); - # check if current raw line has no patch (it got simplified) - if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) { + # compact combined diff output can have some patches skipped + # find which patch (using pathname of result) we are at now; + if ($is_combined) { + while ($to_name ne $diffinfo->{'to_file'}) { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . format_diff_cc_simplified($diffinfo, @hash_parents) . "</div>\n"; # class="patch" $patch_idx++; $patch_number++; + + last if $patch_idx > $#$difftree; + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); } - } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} || - $patch_idx > $#$difftree); + } # modifies %from, %to hashes parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents); @@ -3210,30 +3208,36 @@ sub git_patchset_body { print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n"; } + # git diff header + #assert($patch_line =~ m/^diff /) if DEBUG; + #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed + $patch_number++; # print "git diff" header - $patch_line = shift @diff_header; print format_git_diff_header_line($patch_line, $diffinfo, \%from, \%to); # print extended diff header - print "<div class=\"diff extended_header\">\n" if (@diff_header > 0); + print "<div class=\"diff extended_header\">\n"; EXTENDED_HEADER: - foreach $patch_line (@diff_header) { + while ($patch_line = <$fd>) { + chomp $patch_line; + + last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /); + print format_extended_diff_header_line($patch_line, $diffinfo, \%from, \%to); } - print "</div>\n" if (@diff_header > 0); # class="diff extended_header" + print "</div>\n"; # class="diff extended_header" # from-file/to-file diff header - $patch_line = $last_patch_line; if (! $patch_line) { print "</div>\n"; # class="patch" last PATCH; } next PATCH if ($patch_line =~ m/^diff /); #assert($patch_line =~ m/^---/) if DEBUG; - #assert($patch_line eq $last_patch_line) if DEBUG; + my $last_patch_line = $patch_line; $patch_line = <$fd>; chomp $patch_line; #assert($patch_line =~ m/^\+\+\+/) if DEBUG; @@ -3258,16 +3262,11 @@ sub git_patchset_body { # for compact combined (--cc) format, with chunk and patch simpliciaction # patchset might be empty, but there might be unprocessed raw lines - for ($patch_idx++ if $patch_number > 0; + for (++$patch_idx if $patch_number > 0; $patch_idx < @$difftree; - $patch_idx++) { + ++$patch_idx) { # read and prepare patch information - if (ref($difftree->[$patch_idx]) eq "HASH") { - # pre-parsed (or generated by hand) - $diffinfo = $difftree->[$patch_idx]; - } else { - $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]); - } + $diffinfo = parsed_difftree_line($difftree->[$patch_idx]); # generate anchor for "patch" links in difftree / whatchanged part print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" . @@ -3395,7 +3394,7 @@ sub git_project_list_body { "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"), -class => "list", -title => $pr->{'descr_long'}}, esc_html($pr->{'descr'})) . "</td>\n" . - "<td><i>" . esc_html(chop_str($pr->{'owner'}, 15)) . "</i></td>\n"; + "<td><i>" . chop_and_escape_str($pr->{'owner'}, 15) . "</i></td>\n"; print "<td class=\"". age_class($pr->{'age'}) . "\">" . (defined $pr->{'age_string'} ? $pr->{'age_string'} : "No commits") . "</td>\n" . "<td class=\"link\">" . @@ -3437,9 +3436,10 @@ sub git_shortlog_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 10); # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); @@ -3487,9 +3487,10 @@ sub git_history_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + # shortlog uses chop_str($co{'author_name'}, 10) + my $author = chop_and_escape_str($co{'author_name'}, 15, 3); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - # shortlog uses chop_str($co{'author_name'}, 10) - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>"; # originally git_history used chop_str($co{'title'}, 50) print format_subject_html($co{'title'}, $co{'title_short'}, @@ -3643,11 +3644,12 @@ sub git_search_grep_body { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); my $comment = $co{'comment'}; foreach my $line (@$comment) { if ($line =~ m/^(.*)($search_regexp)(.*)$/i) { @@ -5157,12 +5159,13 @@ sub git_search { print "<tr class=\"light\">\n"; } $alternate ^= 1; + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" . + "<td><i>" . $author . "</i></td>\n" . "<td>" . $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"}, - esc_html(chop_str($co{'title'}, 50)) . "<br/>"); + chop_and_escape_str($co{'title'}, 50) . "<br/>"); while (my $setref = shift @files) { my %set = %$setref; print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, diff --git a/hash.c b/hash.c new file mode 100644 index 0000000000..7b492d4fc0 --- /dev/null +++ b/hash.c @@ -0,0 +1,110 @@ +/* + * Some generic hashing helpers. + */ +#include "cache.h" +#include "hash.h" + +/* + * Look up a hash entry in the hash table. Return the pointer to + * the existing entry, or the empty slot if none existed. The caller + * can then look at the (*ptr) to see whether it existed or not. + */ +static struct hash_table_entry *lookup_hash_entry(unsigned int hash, struct hash_table *table) +{ + unsigned int size = table->size, nr = hash % size; + struct hash_table_entry *array = table->array; + + while (array[nr].ptr) { + if (array[nr].hash == hash) + break; + nr++; + if (nr >= size) + nr = 0; + } + return array + nr; +} + + +/* + * Insert a new hash entry pointer into the table. + * + * If that hash entry already existed, return the pointer to + * the existing entry (and the caller can create a list of the + * pointers or do anything else). If it didn't exist, return + * NULL (and the caller knows the pointer has been inserted). + */ +static void **insert_hash_entry(unsigned int hash, void *ptr, struct hash_table *table) +{ + struct hash_table_entry *entry = lookup_hash_entry(hash, table); + + if (!entry->ptr) { + entry->ptr = ptr; + entry->hash = hash; + table->nr++; + return NULL; + } + return &entry->ptr; +} + +static void grow_hash_table(struct hash_table *table) +{ + unsigned int i; + unsigned int old_size = table->size, new_size; + struct hash_table_entry *old_array = table->array, *new_array; + + new_size = alloc_nr(old_size); + new_array = xcalloc(sizeof(struct hash_table_entry), new_size); + table->size = new_size; + table->array = new_array; + table->nr = 0; + for (i = 0; i < old_size; i++) { + unsigned int hash = old_array[i].hash; + void *ptr = old_array[i].ptr; + if (ptr) + insert_hash_entry(hash, ptr, table); + } + free(old_array); +} + +void *lookup_hash(unsigned int hash, struct hash_table *table) +{ + if (!table->array) + return NULL; + return &lookup_hash_entry(hash, table)->ptr; +} + +void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table) +{ + unsigned int nr = table->nr; + if (nr >= table->size/2) + grow_hash_table(table); + return insert_hash_entry(hash, ptr, table); +} + +int for_each_hash(struct hash_table *table, int (*fn)(void *)) +{ + int sum = 0; + unsigned int i; + unsigned int size = table->size; + struct hash_table_entry *array = table->array; + + for (i = 0; i < size; i++) { + void *ptr = array->ptr; + array++; + if (ptr) { + int val = fn(ptr); + if (val < 0) + return val; + sum += val; + } + } + return sum; +} + +void free_hash(struct hash_table *table) +{ + free(table->array); + table->array = NULL; + table->size = 0; + table->nr = 0; +} diff --git a/hash.h b/hash.h new file mode 100644 index 0000000000..a8b0fbb5b5 --- /dev/null +++ b/hash.h @@ -0,0 +1,43 @@ +#ifndef HASH_H +#define HASH_H + +/* + * These are some simple generic hash table helper functions. + * Not necessarily suitable for all users, but good for things + * where you want to just keep track of a list of things, and + * have a good hash to use on them. + * + * It keeps the hash table at roughly 50-75% free, so the memory + * cost of the hash table itself is roughly + * + * 3 * 2*sizeof(void *) * nr_of_objects + * + * bytes. + * + * FIXME: on 64-bit architectures, we waste memory. It would be + * good to have just 32-bit pointers, requiring a special allocator + * for hashed entries or something. + */ +struct hash_table_entry { + unsigned int hash; + void *ptr; +}; + +struct hash_table { + unsigned int size, nr; + struct hash_table_entry *array; +}; + +extern void *lookup_hash(unsigned int hash, struct hash_table *table); +extern void **insert_hash(unsigned int hash, void *ptr, struct hash_table *table); +extern int for_each_hash(struct hash_table *table, int (*fn)(void *)); +extern void free_hash(struct hash_table *table); + +static inline void init_hash(struct hash_table *table) +{ + table->size = 0; + table->nr = 0; + table->array = NULL; +} + +#endif @@ -37,24 +37,25 @@ static inline void mput_char(char c, unsigned int num) putchar(c); } -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; - -static void add_cmdname(const char *name, int len) +static struct cmdnames { + int alloc; + int cnt; + struct cmdname { + size_t len; + char name[1]; + } **names; +} main_cmds, other_cmds; + +static void add_cmdname(struct cmdnames *cmds, const char *name, int len) { - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = xrealloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - } - ent = xmalloc(sizeof(*ent) + len); + struct cmdname *ent = xmalloc(sizeof(*ent) + len); + ent->len = len; memcpy(ent->name, name, len); ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; + + ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc); + cmds->names[cmds->cnt++] = ent; } static int cmdname_compare(const void *a_, const void *b_) @@ -64,7 +65,42 @@ static int cmdname_compare(const void *a_, const void *b_) return strcmp(a->name, b->name); } -static void pretty_print_string_list(struct cmdname **cmdname, int longest) +static void uniq(struct cmdnames *cmds) +{ + int i, j; + + if (!cmds->cnt) + return; + + for (i = j = 1; i < cmds->cnt; i++) + if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name)) + cmds->names[j++] = cmds->names[i]; + + cmds->cnt = j; +} + +static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) { + int ci, cj, ei; + int cmp; + + ci = cj = ei = 0; + while (ci < cmds->cnt && ei < excludes->cnt) { + cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name); + if (cmp < 0) + cmds->names[cj++] = cmds->names[ci++]; + else if (cmp == 0) + ci++, ei++; + else if (cmp > 0) + ei++; + } + + while (ci < cmds->cnt) + cmds->names[cj++] = cmds->names[ci++]; + + cmds->cnt = cj; +} + +static void pretty_print_string_list(struct cmdnames *cmds, int longest) { int cols = 1, rows; int space = longest + 1; /* min 1 SP between words */ @@ -73,9 +109,7 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) if (space < max_cols) cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + rows = (cmds->cnt + cols - 1) / cols; for (i = 0; i < rows; i++) { printf(" "); @@ -83,71 +117,112 @@ static void pretty_print_string_list(struct cmdname **cmdname, int longest) for (j = 0; j < cols; j++) { int n = j * rows + i; int size = space; - if (n >= cmdname_cnt) + if (n >= cmds->cnt) break; - if (j == cols-1 || n + rows >= cmdname_cnt) + if (j == cols-1 || n + rows >= cmds->cnt) size = 1; - printf("%-*s", size, cmdname[n]->name); + printf("%-*s", size, cmds->names[n]->name); } putchar('\n'); } } -static void list_commands(const char *exec_path, const char *pattern) +static unsigned int list_commands_in_dir(struct cmdnames *cmds, + const char *path) { unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); + const char *prefix = "git-"; + int prefix_len = strlen(prefix); + DIR *dir = opendir(path); struct dirent *de; - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); - } - - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; + if (!dir || chdir(path)) + return 0; while ((de = readdir(dir)) != NULL) { struct stat st; int entlen; - if (prefixcmp(de->d_name, "git-")) + if (prefixcmp(de->d_name, prefix)) continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ + + if (stat(de->d_name, &st) || /* stat, not lstat */ !S_ISREG(st.st_mode) || !(st.st_mode & S_IXUSR)) continue; - entlen = strlen(de->d_name); + entlen = strlen(de->d_name) - prefix_len; if (has_extension(de->d_name, ".exe")) entlen -= 4; if (longest < entlen) longest = entlen; - add_cmdname(de->d_name + 4, entlen-4); + add_cmdname(cmds, de->d_name + prefix_len, entlen); } closedir(dir); - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); + return longest; } -static void list_common_cmds_help(void) +static void list_commands(void) +{ + unsigned int longest = 0; + unsigned int len; + const char *env_path = getenv("PATH"); + char *paths, *path, *colon; + const char *exec_path = git_exec_path(); + + if (exec_path) + longest = list_commands_in_dir(&main_cmds, exec_path); + + if (!env_path) { + fprintf(stderr, "PATH not set\n"); + exit(1); + } + + path = paths = xstrdup(env_path); + while (1) { + if ((colon = strchr(path, ':'))) + *colon = 0; + + len = list_commands_in_dir(&other_cmds, path); + if (len > longest) + longest = len; + + if (!colon) + break; + path = colon + 1; + } + free(paths); + + qsort(main_cmds.names, main_cmds.cnt, + sizeof(*main_cmds.names), cmdname_compare); + uniq(&main_cmds); + + qsort(other_cmds.names, other_cmds.cnt, + sizeof(*other_cmds.names), cmdname_compare); + uniq(&other_cmds); + exclude_cmds(&other_cmds, &main_cmds); + + if (main_cmds.cnt) { + printf("available git commands in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(&main_cmds, longest); + putchar('\n'); + } + + if (other_cmds.cnt) { + printf("git commands available from elsewhere on your $PATH\n"); + printf("---------------------------------------------------\n"); + pretty_print_string_list(&other_cmds, longest); + putchar('\n'); + } +} + +void list_common_cmds_help(void) { int i, longest = 0; @@ -198,19 +273,17 @@ int cmd_version(int argc, const char **argv, const char *prefix) int cmd_help(int argc, const char **argv, const char *prefix) { const char *help_cmd = argc > 1 ? argv[1] : NULL; - const char *exec_path = git_exec_path(); if (!help_cmd) { printf("usage: %s\n\n", git_usage_string); list_common_cmds_help(); - exit(1); + exit(0); } else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) { printf("usage: %s\n\n", git_usage_string); - if(exec_path) - list_commands(exec_path, "git-*"); - exit(1); + list_commands(); + exit(0); } else diff --git a/index-pack.c b/index-pack.c index db58e05041..61ea7621be 100644 --- a/index-pack.c +++ b/index-pack.c @@ -46,7 +46,7 @@ static int nr_resolved_deltas; static int from_stdin; static int verbose; -static struct progress progress; +static struct progress *progress; /* We always read in 4kB chunks. */ static unsigned char input_buffer[4096]; @@ -87,6 +87,8 @@ static void *fill(int min) die("early EOF"); die("read error on input: %s", strerror(errno)); } + if (from_stdin) + display_throughput(progress, ret); input_len += ret; } while (input_len < min); return input_buffer; @@ -106,7 +108,7 @@ static void use(int bytes) consumed_bytes += bytes; } -static const char *open_pack_file(const char *pack_name) +static char *open_pack_file(char *pack_name) { if (from_stdin) { input_fd = 0; @@ -406,7 +408,9 @@ static void parse_pack_objects(unsigned char *sha1) * - remember base (SHA1 or offset) for all deltas. */ if (verbose) - start_progress(&progress, "Indexing %u objects...", "", nr_objects); + progress = start_progress( + from_stdin ? "Receiving objects" : "Indexing objects", + nr_objects); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; data = unpack_raw_entry(obj, &delta->base); @@ -418,12 +422,10 @@ static void parse_pack_objects(unsigned char *sha1) } else sha1_object(data, obj->size, obj->type, obj->idx.sha1); free(data); - if (verbose) - display_progress(&progress, i+1); + display_progress(progress, i+1); } objects[i].idx.offset = consumed_bytes; - if (verbose) - stop_progress(&progress); + stop_progress(&progress); /* Check pack integrity */ flush(); @@ -455,7 +457,7 @@ static void parse_pack_objects(unsigned char *sha1) * for some more deltas. */ if (verbose) - start_progress(&progress, "Resolving %u deltas...", "", nr_deltas); + progress = start_progress("Resolving deltas", nr_deltas); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; union delta_base base; @@ -486,8 +488,7 @@ static void parse_pack_objects(unsigned char *sha1) obj->size, obj->type); } free(data); - if (verbose) - display_progress(&progress, nr_resolved_deltas); + display_progress(progress, nr_resolved_deltas); } } @@ -594,8 +595,7 @@ static void fix_unresolved_deltas(int nr_unresolved) die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); append_obj_to_pack(d->base.sha1, data, size, type); free(data); - if (verbose) - display_progress(&progress, nr_resolved_deltas); + display_progress(progress, nr_resolved_deltas); } free(sorted_by_pos); } @@ -686,15 +686,15 @@ static void final(const char *final_pack_name, const char *curr_pack_name, int main(int argc, char **argv) { int i, fix_thin_pack = 0; - const char *curr_pack, *pack_name = NULL; - const char *curr_index, *index_name = NULL; + char *curr_pack, *pack_name = NULL; + char *curr_index, *index_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; unsigned char sha1[20]; for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + char *arg = argv[i]; if (*arg == '-') { if (!strcmp(arg, "--stdin")) { @@ -774,8 +774,7 @@ int main(int argc, char **argv) deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); parse_pack_objects(sha1); if (nr_deltas == nr_resolved_deltas) { - if (verbose) - stop_progress(&progress); + stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); } else { @@ -788,11 +787,10 @@ int main(int argc, char **argv) (nr_objects + nr_unresolved + 1) * sizeof(*objects)); fix_unresolved_deltas(nr_unresolved); - if (verbose) { - stop_progress(&progress); + stop_progress(&progress); + if (verbose) fprintf(stderr, "%d objects were added to complete this thin pack.\n", nr_objects - nr_objects_initial); - } fixup_pack_header_footer(output_fd, sha1, curr_pack, nr_objects); } @@ -815,6 +813,10 @@ int main(int argc, char **argv) free(objects); free(index_name_buf); free(keep_name_buf); + if (pack_name == NULL) + free(curr_pack); + if (index_name == NULL) + free(curr_index); return 0; } diff --git a/log-tree.c b/log-tree.c index 62edd34455..3763ce94fc 100644 --- a/log-tree.c +++ b/log-tree.c @@ -15,7 +15,7 @@ static void show_parents(struct commit *commit, int abbrev) } } -static void show_decorations(struct commit *commit) +void show_decorations(struct commit *commit) { const char *prefix; struct name_decoration *decoration; diff --git a/log-tree.h b/log-tree.h index e82b56a20d..b33f7cd7ac 100644 --- a/log-tree.h +++ b/log-tree.h @@ -12,5 +12,6 @@ int log_tree_diff_flush(struct rev_info *); int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt, const char *sep); +void show_decorations(struct commit *commit); #endif diff --git a/pack-write.c b/pack-write.c index 979bdfff7c..665e2b29b8 100644 --- a/pack-write.c +++ b/pack-write.c @@ -17,7 +17,8 @@ static int sha1_compare(const void *_a, const void *_b) * the SHA1 hash of sorted object names. The objects array passed in * will be sorted by SHA1 on exit. */ -const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1) +char *write_idx_file(char *index_name, struct pack_idx_entry **objects, + int nr_objects, unsigned char *sha1) { struct sha1file *f; struct pack_idx_entry **sorted_by_sha, **list, **last; @@ -55,7 +55,7 @@ struct pack_idx_entry { off_t offset; }; -extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); +extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int verify_pack(struct packed_git *, int); extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t); diff --git a/peek-remote.c b/peek-remote.c index ceb787170e..8d20f7c9c6 100644 --- a/peek-remote.c +++ b/peek-remote.c @@ -25,7 +25,7 @@ int main(int argc, char **argv) int i, ret; char *dest = NULL; int fd[2]; - pid_t pid; + struct child_process *conn; int nongit = 0; unsigned flags = 0; @@ -64,12 +64,10 @@ int main(int argc, char **argv) if (!dest || i != argc - 1) usage(peek_remote_usage); - pid = git_connect(fd, dest, uploadpack, 0); - if (pid < 0) - return 1; + conn = git_connect(fd, dest, uploadpack, 0); ret = peek_remote(fd, flags); close(fd[0]); close(fd[1]); - ret |= finish_connect(pid); + ret |= finish_connect(conn); return !!ret; } diff --git a/progress.c b/progress.c index 4344f4eed5..3f6a602a53 100644 --- a/progress.c +++ b/progress.c @@ -1,6 +1,40 @@ +/* + * Simple text-based progress display module for GIT + * + * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org> + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + #include "git-compat-util.h" #include "progress.h" +#define TP_IDX_MAX 8 + +struct throughput { + struct timeval prev_tv; + off_t total; + unsigned long count; + unsigned long avg_bytes; + unsigned long last_bytes[TP_IDX_MAX]; + unsigned int avg_misecs; + unsigned int last_misecs[TP_IDX_MAX]; + unsigned int idx; + char display[32]; +}; + +struct progress { + const char *title; + int last_value; + unsigned total; + unsigned last_percent; + unsigned delay; + unsigned delayed_percent_treshold; + struct throughput *throughput; +}; + static volatile sig_atomic_t progress_update; static void progress_interval(int signum) @@ -35,10 +69,11 @@ static void clear_progress_signal(void) progress_update = 0; } -int display_progress(struct progress *progress, unsigned n) +static int display(struct progress *progress, unsigned n, int done) { + char *eol, *tp; + if (progress->delay) { - char buf[80]; if (!progress_update || --progress->delay) return 0; if (progress->total) { @@ -51,60 +86,151 @@ int display_progress(struct progress *progress, unsigned n) return 0; } } - if (snprintf(buf, sizeof(buf), - progress->delayed_title, progress->total)) - fprintf(stderr, "%s\n", buf); } + + progress->last_value = n; + tp = (progress->throughput) ? progress->throughput->display : ""; + eol = done ? ", done. \n" : " \r"; if (progress->total) { unsigned percent = n * 100 / progress->total; if (percent != progress->last_percent || progress_update) { progress->last_percent = percent; - fprintf(stderr, "%s%4u%% (%u/%u) done\r", - progress->prefix, percent, n, progress->total); + fprintf(stderr, "%s: %3u%% (%u/%u)%s%s", + progress->title, percent, n, + progress->total, tp, eol); progress_update = 0; - progress->need_lf = 1; return 1; } } else if (progress_update) { - fprintf(stderr, "%s%u\r", progress->prefix, n); + fprintf(stderr, "%s: %u%s%s", progress->title, n, tp, eol); progress_update = 0; - progress->need_lf = 1; return 1; } + return 0; } -void start_progress(struct progress *progress, const char *title, - const char *prefix, unsigned total) +void display_throughput(struct progress *progress, unsigned long n) { - char buf[80]; - progress->prefix = prefix; - progress->total = total; - progress->last_percent = -1; - progress->delay = 0; - progress->need_lf = 0; - if (snprintf(buf, sizeof(buf), title, total)) - fprintf(stderr, "%s\n", buf); - set_progress_signal(); + struct throughput *tp; + struct timeval tv; + unsigned int misecs; + + if (!progress) + return; + tp = progress->throughput; + + gettimeofday(&tv, NULL); + + if (!tp) { + progress->throughput = tp = calloc(1, sizeof(*tp)); + if (tp) + tp->prev_tv = tv; + return; + } + + tp->total += n; + tp->count += n; + + /* + * We have x = bytes and y = microsecs. We want z = KiB/s: + * + * z = (x / 1024) / (y / 1000000) + * z = x / y * 1000000 / 1024 + * z = x / (y * 1024 / 1000000) + * z = x / y' + * + * To simplify things we'll keep track of misecs, or 1024th of a sec + * obtained with: + * + * y' = y * 1024 / 1000000 + * y' = y / (1000000 / 1024) + * y' = y / 977 + */ + misecs = (tv.tv_sec - tp->prev_tv.tv_sec) * 1024; + misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977; + + if (misecs > 512) { + int l = sizeof(tp->display); + tp->prev_tv = tv; + tp->avg_bytes += tp->count; + tp->avg_misecs += misecs; + + if (tp->total > 1 << 30) { + l -= snprintf(tp->display, l, ", %u.%2.2u GiB", + (int)(tp->total >> 30), + (int)(tp->total & ((1 << 30) - 1)) / 10737419); + } else if (tp->total > 1 << 20) { + l -= snprintf(tp->display, l, ", %u.%2.2u MiB", + (int)(tp->total >> 20), + ((int)(tp->total & ((1 << 20) - 1)) + * 100) >> 20); + } else if (tp->total > 1 << 10) { + l -= snprintf(tp->display, l, ", %u.%2.2u KiB", + (int)(tp->total >> 10), + ((int)(tp->total & ((1 << 10) - 1)) + * 100) >> 10); + } else { + l -= snprintf(tp->display, l, ", %u bytes", + (int)tp->total); + } + snprintf(tp->display + sizeof(tp->display) - l, l, + " | %lu KiB/s", tp->avg_bytes / tp->avg_misecs); + + tp->avg_bytes -= tp->last_bytes[tp->idx]; + tp->avg_misecs -= tp->last_misecs[tp->idx]; + tp->last_bytes[tp->idx] = tp->count; + tp->last_misecs[tp->idx] = misecs; + tp->idx = (tp->idx + 1) % TP_IDX_MAX; + tp->count = 0; + + if (progress->last_value != -1 && progress_update) + display(progress, progress->last_value, 0); + } +} + +int display_progress(struct progress *progress, unsigned n) +{ + return progress ? display(progress, n, 0) : 0; } -void start_progress_delay(struct progress *progress, const char *title, - const char *prefix, unsigned total, - unsigned percent_treshold, unsigned delay) +struct progress *start_progress_delay(const char *title, unsigned total, + unsigned percent_treshold, unsigned delay) { - progress->prefix = prefix; + struct progress *progress = malloc(sizeof(*progress)); + if (!progress) { + /* unlikely, but here's a good fallback */ + fprintf(stderr, "%s...\n", title); + return NULL; + } + progress->title = title; progress->total = total; + progress->last_value = -1; progress->last_percent = -1; progress->delayed_percent_treshold = percent_treshold; - progress->delayed_title = title; progress->delay = delay; - progress->need_lf = 0; + progress->throughput = NULL; set_progress_signal(); + return progress; } -void stop_progress(struct progress *progress) +struct progress *start_progress(const char *title, unsigned total) { + return start_progress_delay(title, total, 0, 0); +} + +void stop_progress(struct progress **p_progress) +{ + struct progress *progress = *p_progress; + if (!progress) + return; + *p_progress = NULL; + if (progress->last_value != -1) { + /* Force the last update */ + progress_update = 1; + display(progress, progress->last_value, 1); + } clear_progress_signal(); - if (progress->need_lf) - fputc('\n', stderr); + free(progress->throughput); + free(progress); } diff --git a/progress.h b/progress.h index a7c17ca7c4..61cb68dfa5 100644 --- a/progress.h +++ b/progress.h @@ -1,22 +1,13 @@ #ifndef PROGRESS_H #define PROGRESS_H -struct progress { - const char *prefix; - unsigned total; - unsigned last_percent; - unsigned delay; - unsigned delayed_percent_treshold; - const char *delayed_title; - int need_lf; -}; +struct progress; +void display_throughput(struct progress *progress, unsigned long n); int display_progress(struct progress *progress, unsigned n); -void start_progress(struct progress *progress, const char *title, - const char *prefix, unsigned total); -void start_progress_delay(struct progress *progress, const char *title, - const char *prefix, unsigned total, - unsigned percent_treshold, unsigned delay); -void stop_progress(struct progress *progress); +struct progress *start_progress(const char *title, unsigned total); +struct progress *start_progress_delay(const char *title, unsigned total, + unsigned percent_treshold, unsigned delay); +void stop_progress(struct progress **progress); #endif diff --git a/run-command.c b/run-command.c index 7e779d33ee..d99a6c4ea7 100644 --- a/run-command.c +++ b/run-command.c @@ -17,8 +17,8 @@ static inline void dup_devnull(int to) int start_command(struct child_process *cmd) { - int need_in, need_out; - int fdin[2], fdout[2]; + int need_in, need_out, need_err; + int fdin[2], fdout[2], fderr[2]; need_in = !cmd->no_stdin && cmd->in < 0; if (need_in) { @@ -41,12 +41,26 @@ int start_command(struct child_process *cmd) cmd->close_out = 1; } + need_err = cmd->err < 0; + if (need_err) { + if (pipe(fderr) < 0) { + if (need_in) + close_pair(fdin); + if (need_out) + close_pair(fdout); + return -ERR_RUN_COMMAND_PIPE; + } + cmd->err = fderr[0]; + } + cmd->pid = fork(); if (cmd->pid < 0) { if (need_in) close_pair(fdin); if (need_out) close_pair(fdout); + if (need_err) + close_pair(fderr); return -ERR_RUN_COMMAND_FORK; } @@ -73,6 +87,11 @@ int start_command(struct child_process *cmd) close(cmd->out); } + if (need_err) { + dup2(fderr[1], 2); + close_pair(fderr); + } + if (cmd->dir && chdir(cmd->dir)) die("exec %s: cd to %s failed (%s)", cmd->argv[0], cmd->dir, strerror(errno)); @@ -102,19 +121,17 @@ int start_command(struct child_process *cmd) else if (cmd->out > 1) close(cmd->out); + if (need_err) + close(fderr[1]); + return 0; } -int finish_command(struct child_process *cmd) +static int wait_or_whine(pid_t pid) { - if (cmd->close_in) - close(cmd->in); - if (cmd->close_out) - close(cmd->out); - for (;;) { int status, code; - pid_t waiting = waitpid(cmd->pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); if (waiting < 0) { if (errno == EINTR) @@ -122,7 +139,7 @@ int finish_command(struct child_process *cmd) error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (waiting != cmd->pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; @@ -136,6 +153,15 @@ int finish_command(struct child_process *cmd) } } +int finish_command(struct child_process *cmd) +{ + if (cmd->close_in) + close(cmd->in); + if (cmd->close_out) + close(cmd->out); + return wait_or_whine(cmd->pid); +} + int run_command(struct child_process *cmd) { int code = start_command(cmd); @@ -178,3 +204,34 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const cmd.env = env; return run_command(&cmd); } + +int start_async(struct async *async) +{ + int pipe_out[2]; + + if (pipe(pipe_out) < 0) + return error("cannot create pipe: %s", strerror(errno)); + + async->pid = fork(); + if (async->pid < 0) { + error("fork (async) failed: %s", strerror(errno)); + close_pair(pipe_out); + return -1; + } + if (!async->pid) { + close(pipe_out[0]); + exit(!!async->proc(pipe_out[1], async->data)); + } + async->out = pipe_out[0]; + close(pipe_out[1]); + return 0; +} + +int finish_async(struct async *async) +{ + int ret = 0; + + if (wait_or_whine(async->pid)) + ret = error("waitpid (async) failed"); + return ret; +} diff --git a/run-command.h b/run-command.h index 7958eb1e0b..94e1e9d516 100644 --- a/run-command.h +++ b/run-command.h @@ -16,6 +16,7 @@ struct child_process { pid_t pid; int in; int out; + int err; const char *dir; const char *const *env; unsigned close_in:1; @@ -42,4 +43,26 @@ int run_command_v_opt_cd(const char **argv, int opt, const char *dir); */ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env); +/* + * The purpose of the following functions is to feed a pipe by running + * a function asynchronously and providing output that the caller reads. + * + * It is expected that no synchronization and mutual exclusion between + * the caller and the feed function is necessary so that the function + * can run in a thread without interfering with the caller. + */ +struct async { + /* + * proc writes to fd and closes it; + * returns 0 on success, non-zero on failure + */ + int (*proc)(int fd, void *data); + void *data; + int out; /* caller reads from here and closes it */ + pid_t pid; +}; + +int start_async(struct async *async); +int finish_async(struct async *async); + #endif diff --git a/send-pack.c b/send-pack.c index e9b9a39f41..5e127a1b7b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -178,6 +178,35 @@ static int receive_status(int in) return ret; } +static void update_tracking_ref(struct remote *remote, struct ref *ref) +{ + struct refspec rs; + int will_delete_ref; + + rs.src = ref->name; + rs.dst = NULL; + + if (!ref->peer_ref) + return; + + will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); + + if (!will_delete_ref && + !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) + return; + + if (!remote_find_tracking(remote, &rs)) { + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (is_null_sha1(ref->peer_ref->new_sha1)) { + if (delete_ref(rs.dst, NULL)) + error("Failed to delete"); + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); + free(rs.dst); + } +} + static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec) { struct ref *ref; @@ -306,22 +335,6 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); } - if (remote && !dry_run) { - struct refspec rs; - rs.src = ref->name; - rs.dst = NULL; - if (!remote_find_tracking(remote, &rs)) { - fprintf(stderr, " Also local %s\n", rs.dst); - if (will_delete_ref) { - if (delete_ref(rs.dst, NULL)) { - error("Failed to delete"); - } - } else - update_ref("update by push", rs.dst, - ref->new_sha1, NULL, 0, 0); - free(rs.dst); - } - } } packet_flush(out); @@ -334,6 +347,11 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha ret = -4; } + if (!dry_run && remote && ret == 0) { + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(remote, ref); + } + if (!new_refs && ret == 0) fprintf(stderr, "Everything up-to-date\n"); return ret; @@ -366,7 +384,7 @@ int main(int argc, char **argv) char *dest = NULL; char **heads = NULL; int fd[2], ret; - pid_t pid; + struct child_process *conn; char *remote_name = NULL; struct remote *remote = NULL; @@ -434,12 +452,10 @@ int main(int argc, char **argv) } } - pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0); - if (pid < 0) - return 1; + conn = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0); ret = send_pack(fd[0], fd[1], remote, nr_heads, heads); close(fd[0]); close(fd[1]); - ret |= finish_connect(pid); + ret |= finish_connect(conn); return !!ret; } @@ -24,17 +24,11 @@ static int do_cvs_cmd(const char *me, char *arg) const char *cvsserver_argv[3] = { "cvsserver", "server", NULL }; - const char *oldpath = getenv("PATH"); - struct strbuf newpath = STRBUF_INIT; if (!arg || strcmp(arg, "server")) die("git-cvsserver only handles server: %s", arg); - strbuf_addstr(&newpath, git_exec_path()); - strbuf_addch(&newpath, ':'); - strbuf_addstr(&newpath, oldpath); - - setenv("PATH", strbuf_detach(&newpath, NULL), 1); + setup_path(NULL); return execv_git_cmd(cvsserver_argv); } diff --git a/sideband.c b/sideband.c index 277fa3c10d..ab8a1e990d 100644 --- a/sideband.c +++ b/sideband.c @@ -17,7 +17,7 @@ int recv_sideband(const char *me, int in_stream, int out, int err) strcpy(buf, "remote:"); while (1) { int band, len; - len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX); + len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX); if (len == 0) break; if (len < 1) { @@ -35,7 +35,22 @@ int recv_sideband(const char *me, int in_stream, int out, int err) return SIDEBAND_REMOTE_ERROR; case 2: buf[7] = ' '; - safe_write(err, buf, 8+len); + len += 8; + while (1) { + int brk = 8; + while (brk < len) { + brk++; + if (buf[brk-1] == '\n' || + buf[brk-1] == '\r') + break; + } + safe_write(err, buf, brk); + if (brk < len) { + memmove(buf + 8, buf + brk, len - brk); + len = len - brk + 8; + } else + break; + } continue; case 1: safe_write(out, buf+8, len); diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index a839f4e074..cb860296ed 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -42,7 +42,12 @@ test_expect_success check ' git diff --raw --exit-code :test :test.i && id=$(git rev-parse --verify :test) && embedded=$(sed -ne "$script" test.i) && - test "z$id" = "z$embedded" + test "z$id" = "z$embedded" && + + git cat-file blob :test.t > test.r && + + ./rot13.sh < test.o > test.t && + cmp test.r test.t ' # If an expanded ident ever gets into the repository, we want to make sure that diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4fbd5b1f47..86f9b5346a 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -254,4 +254,32 @@ test_expect_success 'push with dry-run' ' check_push_result $old_commit heads/master ' +test_expect_success 'push updates local refs' ' + + rm -rf parent child && + mkdir parent && cd parent && git init && + echo one >foo && git add foo && git commit -m one && + cd .. && + git clone parent child && cd child && + echo two >foo && git commit -a -m two && + git push && + test $(git rev-parse master) = $(git rev-parse remotes/origin/master) + +' + +test_expect_success 'push does not update local refs on failure' ' + + rm -rf parent child && + mkdir parent && cd parent && git init && + echo one >foo && git add foo && git commit -m one && + echo exit 1 >.git/hooks/pre-receive && + chmod +x .git/hooks/pre-receive && + cd .. && + git clone parent child && cd child && + echo two >foo && git commit -a -m two || exit 1 + git push && exit 1 + test $(git rev-parse master) != $(git rev-parse remotes/origin/master) + +' + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 03cdba5808..53956c08e2 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -71,6 +71,63 @@ test_expect_success 'bisect start with one bad and good' ' git bisect next ' +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is bad, +# so we should find $HASH2 as the first bad commit +test_expect_success 'bisect skip: successfull result' ' + git bisect reset && + git bisect start $HASH4 $HASH1 && + git bisect skip && + git bisect bad > my_bisect_log.txt && + grep "$HASH2 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 and $HASH2 +# so we should not be able to tell the first bad commit +# among $HASH2, $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 3 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect skip > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + +# $HASH1 is good, $HASH4 is bad, we skip $HASH3 +# but $HASH2 is good, +# so we should not be able to tell the first bad commit +# among $HASH3 and $HASH4 +test_expect_success 'bisect skip: cannot tell between 2 commits' ' + git bisect start $HASH4 $HASH1 && + git bisect skip || return 1 + + if git bisect good > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH1 my_bisect_log.txt && + ! grep $HASH2 my_bisect_log.txt && + grep $HASH3 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect reset + fi +' + # We want to automatically find the commit that # introduced "Another" into hello. test_expect_success \ @@ -99,6 +156,67 @@ test_expect_success \ grep "$HASH4 is first bad commit" my_bisect_log.txt && git bisect reset' +# $HASH1 is good, $HASH5 is bad, we skip $HASH3 +# but $HASH4 is good, +# so we should find $HASH5 as the first bad commit +HASH5= +test_expect_success 'bisect skip: add line and then a new test' ' + add_line_into_file "5: Another new line." hello && + HASH5=$(git rev-parse --verify HEAD) && + git bisect start $HASH5 $HASH1 && + git bisect skip && + git bisect good > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect log > log_to_replay.txt + git bisect reset +' + +test_expect_success 'bisect skip and bisect replay' ' + git bisect replay log_to_replay.txt > my_bisect_log.txt && + grep "$HASH5 is first bad commit" my_bisect_log.txt && + git bisect reset +' + +HASH6= +test_expect_success 'bisect run & skip: cannot tell between 2' ' + add_line_into_file "6: Yet a line." hello && + HASH6=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "grep line hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH6 $HASH1 && + if git bisect run ./test_script.sh > my_bisect_log.txt + then + echo Oops, should have failed. + false + else + test $? -eq 2 && + grep "first bad commit could be any of" my_bisect_log.txt && + ! grep $HASH3 my_bisect_log.txt && + ! grep $HASH6 my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + grep $HASH5 my_bisect_log.txt + fi +' + +HASH7= +test_expect_success 'bisect run & skip: find first bad' ' + git bisect reset && + add_line_into_file "7: Should be the last line." hello && + HASH7=$(git rev-parse --verify HEAD) && + echo "#"\!"/bin/sh" > test_script.sh && + echo "tail -1 hello | grep Ciao > /dev/null && exit 125" >> test_script.sh && + echo "tail -1 hello | grep day > /dev/null && exit 125" >> test_script.sh && + echo "grep Yet hello > /dev/null" >> test_script.sh && + echo "test \$? -ne 0" >> test_script.sh && + chmod +x test_script.sh && + git bisect start $HASH7 $HASH1 && + git bisect run ./test_script.sh > my_bisect_log.txt && + grep "$HASH6 is first bad commit" my_bisect_log.txt +' + # # test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 714de6e575..603a8cd5e7 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -60,6 +60,7 @@ esac # . ./test-lib.sh [ "x$TERM" != "xdumb" ] && + [ -t 1 ] && tput bold >/dev/null 2>&1 && tput setaf 1 >/dev/null 2>&1 && tput sgr0 >/dev/null 2>&1 && diff --git a/transport.c b/transport.c index 400af71c76..d44fe7cee7 100644 --- a/transport.c +++ b/transport.c @@ -107,7 +107,7 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) return; for (;;) { - int cmp, len; + int cmp = cmp, len; if (!fgets(buffer, sizeof(buffer), f)) { fclose(f); @@ -601,18 +601,13 @@ static struct ref *get_refs_via_connect(const struct transport *transport) struct git_transport_data *data = transport->data; struct ref *refs; int fd[2]; - pid_t pid; char *dest = xstrdup(transport->url); - - pid = git_connect(fd, dest, data->uploadpack, 0); - - if (pid < 0) - die("Failed to connect to \"%s\"", transport->url); + struct child_process *conn = git_connect(fd, dest, data->uploadpack, 0); get_remote_heads(fd[0], &refs, 0, NULL, 0); packet_flush(fd[1]); - finish_connect(pid); + finish_connect(conn); free(dest); diff --git a/unpack-trees.c b/unpack-trees.c index ccfeb6e245..c527d7d049 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -297,7 +297,7 @@ static void check_updates(struct cache_entry **src, int nr, { unsigned short mask = htons(CE_UPDATE); unsigned cnt = 0, total = 0; - struct progress progress; + struct progress *progress = NULL; char last_symlink[PATH_MAX]; if (o->update && o->verbose_update) { @@ -307,8 +307,8 @@ static void check_updates(struct cache_entry **src, int nr, total++; } - start_progress_delay(&progress, "Checking %u files out...", - "", total, 50, 2); + progress = start_progress_delay("Checking out files", + total, 50, 2); cnt = 0; } @@ -316,9 +316,8 @@ static void check_updates(struct cache_entry **src, int nr, while (nr--) { struct cache_entry *ce = *src++; - if (total) - if (!ce->ce_mode || ce->ce_flags & mask) - display_progress(&progress, ++cnt); + if (!ce->ce_mode || ce->ce_flags & mask) + display_progress(progress, ++cnt); if (!ce->ce_mode) { if (o->update) unlink_entry(ce->name, last_symlink); @@ -332,8 +331,7 @@ static void check_updates(struct cache_entry **src, int nr, } } } - if (total) - stop_progress(&progress);; + stop_progress(&progress); } int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o) diff --git a/upload-pack.c b/upload-pack.c index fe96ef15c4..67994680f2 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -9,6 +9,7 @@ #include "diff.h" #include "revision.h" #include "list-objects.h" +#include "run-command.h" static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; @@ -96,110 +97,86 @@ static void show_edge(struct commit *commit) fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); } +static int do_rev_list(int fd, void *create_full_pack) +{ + int i; + struct rev_info revs; + + pack_pipe = fdopen(fd, "w"); + if (create_full_pack) + use_thin_pack = 0; /* no point doing it */ + init_revisions(&revs, NULL); + revs.tag_objects = 1; + revs.tree_objects = 1; + revs.blob_objects = 1; + if (use_thin_pack) + revs.edge_hint = 1; + + if (create_full_pack) { + const char *args[] = {"rev-list", "--all", NULL}; + setup_revisions(2, args, &revs, NULL); + } else { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; + /* why??? */ + o->flags &= ~UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; + o->flags |= UNINTERESTING; + add_pending_object(&revs, o, NULL); + } + setup_revisions(0, NULL, &revs, NULL); + } + prepare_revision_walk(&revs); + mark_edges_uninteresting(revs.commits, &revs, show_edge); + traverse_commit_list(&revs, show_commit, show_object); + return 0; +} + static void create_pack_file(void) { - /* Pipes between rev-list to pack-objects, pack-objects to us - * and pack-objects error stream for progress bar. - */ - int lp_pipe[2], pu_pipe[2], pe_pipe[2]; - pid_t pid_rev_list, pid_pack_objects; + struct async rev_list; + struct child_process pack_objects; int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; int buffered = -1; + const char *argv[10]; + int arg = 0; - if (pipe(lp_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - pid_rev_list = fork(); - if (pid_rev_list < 0) + rev_list.proc = do_rev_list; + /* .data is just a boolean: any non-NULL value will do */ + rev_list.data = create_full_pack ? &rev_list : NULL; + if (start_async(&rev_list)) die("git-upload-pack: unable to fork git-rev-list"); - if (!pid_rev_list) { - int i; - struct rev_info revs; - - close(lp_pipe[0]); - pack_pipe = fdopen(lp_pipe[1], "w"); - - if (create_full_pack) - use_thin_pack = 0; /* no point doing it */ - init_revisions(&revs, NULL); - revs.tag_objects = 1; - revs.tree_objects = 1; - revs.blob_objects = 1; - if (use_thin_pack) - revs.edge_hint = 1; - - if (create_full_pack) { - const char *args[] = {"rev-list", "--all", NULL}; - setup_revisions(2, args, &revs, NULL); - } else { - for (i = 0; i < want_obj.nr; i++) { - struct object *o = want_obj.objects[i].item; - /* why??? */ - o->flags &= ~UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - for (i = 0; i < have_obj.nr; i++) { - struct object *o = have_obj.objects[i].item; - o->flags |= UNINTERESTING; - add_pending_object(&revs, o, NULL); - } - setup_revisions(0, NULL, &revs, NULL); - } - prepare_revision_walk(&revs); - mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); - exit(0); - } - - if (pipe(pu_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - if (pipe(pe_pipe) < 0) - die("git-upload-pack: unable to create pipe"); - pid_pack_objects = fork(); - if (pid_pack_objects < 0) { + argv[arg++] = "pack-objects"; + argv[arg++] = "--stdout"; + if (!no_progress) + argv[arg++] = "--progress"; + if (use_ofs_delta) + argv[arg++] = "--delta-base-offset"; + argv[arg++] = NULL; + + memset(&pack_objects, 0, sizeof(pack_objects)); + pack_objects.in = rev_list.out; /* start_command closes it */ + pack_objects.out = -1; + pack_objects.err = -1; + pack_objects.git_cmd = 1; + pack_objects.argv = argv; + + if (start_command(&pack_objects)) { /* daemon sets things up to ignore TERM */ - kill(pid_rev_list, SIGKILL); + kill(rev_list.pid, SIGKILL); die("git-upload-pack: unable to fork git-pack-objects"); } - if (!pid_pack_objects) { - const char *argv[10]; - int i = 0; - - dup2(lp_pipe[0], 0); - dup2(pu_pipe[1], 1); - dup2(pe_pipe[1], 2); - - close(lp_pipe[0]); - close(lp_pipe[1]); - close(pu_pipe[0]); - close(pu_pipe[1]); - close(pe_pipe[0]); - close(pe_pipe[1]); - - argv[i++] = "pack-objects"; - argv[i++] = "--stdout"; - if (!no_progress) - argv[i++] = "--progress"; - if (use_ofs_delta) - argv[i++] = "--delta-base-offset"; - argv[i++] = NULL; - - execv_git_cmd(argv); - kill(pid_rev_list, SIGKILL); - die("git-upload-pack: unable to exec git-pack-objects"); - } - - close(lp_pipe[0]); - close(lp_pipe[1]); - /* We read from pe_pipe[0] to capture stderr output for - * progress bar, and pu_pipe[0] to capture the pack data. + /* We read from pack_objects.err to capture stderr output for + * progress bar, and pack_objects.out to capture the pack data. */ - close(pe_pipe[1]); - close(pu_pipe[1]); while (1) { const char *who; @@ -214,14 +191,14 @@ static void create_pack_file(void) pollsize = 0; pe = pu = -1; - if (0 <= pu_pipe[0]) { - pfd[pollsize].fd = pu_pipe[0]; + if (0 <= pack_objects.out) { + pfd[pollsize].fd = pack_objects.out; pfd[pollsize].events = POLLIN; pu = pollsize; pollsize++; } - if (0 <= pe_pipe[0]) { - pfd[pollsize].fd = pe_pipe[0]; + if (0 <= pack_objects.err) { + pfd[pollsize].fd = pack_objects.err; pfd[pollsize].events = POLLIN; pe = pollsize; pollsize++; @@ -254,13 +231,13 @@ static void create_pack_file(void) *cp++ = buffered; outsz++; } - sz = xread(pu_pipe[0], cp, + sz = xread(pack_objects.out, cp, sizeof(data) - outsz); if (0 < sz) ; else if (sz == 0) { - close(pu_pipe[0]); - pu_pipe[0] = -1; + close(pack_objects.out); + pack_objects.out = -1; } else goto fail; @@ -279,13 +256,13 @@ static void create_pack_file(void) /* Status ready; we ship that in the side-band * or dump to the standard error. */ - sz = xread(pe_pipe[0], progress, + sz = xread(pack_objects.err, progress, sizeof(progress)); if (0 < sz) send_client_data(2, progress, sz); else if (sz == 0) { - close(pe_pipe[0]); - pe_pipe[0] = -1; + close(pack_objects.err); + pack_objects.err = -1; } else goto fail; @@ -293,12 +270,12 @@ static void create_pack_file(void) } /* See if the children are still there */ - if (pid_rev_list || pid_pack_objects) { + if (rev_list.pid || pack_objects.pid) { pid = waitpid(-1, &status, WNOHANG); if (!pid) continue; - who = ((pid == pid_rev_list) ? "git-rev-list" : - (pid == pid_pack_objects) ? "git-pack-objects" : + who = ((pid == rev_list.pid) ? "git-rev-list" : + (pid == pack_objects.pid) ? "git-pack-objects" : NULL); if (!who) { if (pid < 0) { @@ -315,11 +292,11 @@ static void create_pack_file(void) who); goto fail; } - if (pid == pid_rev_list) - pid_rev_list = 0; - if (pid == pid_pack_objects) - pid_pack_objects = 0; - if (pid_rev_list || pid_pack_objects) + if (pid == rev_list.pid) + rev_list.pid = 0; + if (pid == pack_objects.pid) + pack_objects.pid = 0; + if (rev_list.pid || pack_objects.pid) continue; } @@ -340,10 +317,10 @@ static void create_pack_file(void) return; } fail: - if (pid_pack_objects) - kill(pid_pack_objects, SIGKILL); - if (pid_rev_list) - kill(pid_rev_list, SIGKILL); + if (pack_objects.pid) + kill(pack_objects.pid, SIGKILL); + if (rev_list.pid) + kill(rev_list.pid, SIGKILL); send_client_data(3, abort_msg, sizeof(abort_msg)); die("git-upload-pack: %s", abort_msg); } diff --git a/wt-status.c b/wt-status.c index 10ce6eedc7..03b5ec4488 100644 --- a/wt-status.c +++ b/wt-status.c @@ -52,31 +52,34 @@ void wt_status_prepare(struct wt_status *s) head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; + s->fp = stdout; + s->index_file = get_index_file(); } -static void wt_status_print_cached_header(const char *reference) +static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# Changes to be committed:"); - if (reference) { - color_printf_ln(c, "# (use \"git reset %s <file>...\" to unstage)", reference); + color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (s->reference) { + color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { - color_printf_ln(c, "# (use \"git rm --cached <file>...\" to unstage)"); + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); } - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_header(const char *main, const char *sub) +static void wt_status_print_header(struct wt_status *s, + const char *main, const char *sub) { const char *c = color(WT_STATUS_HEADER); - color_printf_ln(c, "# %s:", main); - color_printf_ln(c, "# (%s)", sub); - color_printf_ln(c, "#"); + color_fprintf_ln(s->fp, c, "# %s:", main); + color_fprintf_ln(s->fp, c, "# (%s)", sub); + color_fprintf_ln(s->fp, c, "#"); } -static void wt_status_print_trailer(void) +static void wt_status_print_trailer(struct wt_status *s) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); } static const char *quote_crlf(const char *in, char *buf, size_t sz) @@ -108,7 +111,8 @@ static const char *quote_crlf(const char *in, char *buf, size_t sz) return ret; } -static void wt_status_print_filepair(int t, struct diff_filepair *p) +static void wt_status_print_filepair(struct wt_status *s, + int t, struct diff_filepair *p) { const char *c = color(t); const char *one, *two; @@ -117,36 +121,36 @@ static void wt_status_print_filepair(int t, struct diff_filepair *p) one = quote_crlf(p->one->path, onebuf, sizeof(onebuf)); two = quote_crlf(p->two->path, twobuf, sizeof(twobuf)); - color_printf(color(WT_STATUS_HEADER), "#\t"); + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); switch (p->status) { case DIFF_STATUS_ADDED: - color_printf(c, "new file: %s", one); + color_fprintf(s->fp, c, "new file: %s", one); break; case DIFF_STATUS_COPIED: - color_printf(c, "copied: %s -> %s", one, two); + color_fprintf(s->fp, c, "copied: %s -> %s", one, two); break; case DIFF_STATUS_DELETED: - color_printf(c, "deleted: %s", one); + color_fprintf(s->fp, c, "deleted: %s", one); break; case DIFF_STATUS_MODIFIED: - color_printf(c, "modified: %s", one); + color_fprintf(s->fp, c, "modified: %s", one); break; case DIFF_STATUS_RENAMED: - color_printf(c, "renamed: %s -> %s", one, two); + color_fprintf(s->fp, c, "renamed: %s -> %s", one, two); break; case DIFF_STATUS_TYPE_CHANGED: - color_printf(c, "typechange: %s", one); + color_fprintf(s->fp, c, "typechange: %s", one); break; case DIFF_STATUS_UNKNOWN: - color_printf(c, "unknown: %s", one); + color_fprintf(s->fp, c, "unknown: %s", one); break; case DIFF_STATUS_UNMERGED: - color_printf(c, "unmerged: %s", one); + color_fprintf(s->fp, c, "unmerged: %s", one); break; default: die("bug: unhandled diff status %c", p->status); } - printf("\n"); + fprintf(s->fp, "\n"); } static void wt_status_print_updated_cb(struct diff_queue_struct *q, @@ -160,14 +164,14 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q, if (q->queue[i]->status == 'U') continue; if (!shown_header) { - wt_status_print_cached_header(s->reference); + wt_status_print_cached_header(s); s->commitable = 1; shown_header = 1; } - wt_status_print_filepair(WT_STATUS_UPDATED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); } if (shown_header) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_changed_cb(struct diff_queue_struct *q, @@ -184,18 +188,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q, msg = use_add_rm_msg; break; } - wt_status_print_header("Changed but not updated", msg); + wt_status_print_header(s, "Changed but not updated", msg); } for (i = 0; i < q->nr; i++) - wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]); + wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]); if (q->nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_read_cache(struct wt_status *s) { discard_cache(); - read_cache(); + read_cache_from(s->index_file); } static void wt_status_print_initial(struct wt_status *s) @@ -206,16 +210,16 @@ static void wt_status_print_initial(struct wt_status *s) wt_read_cache(s); if (active_nr) { s->commitable = 1; - wt_status_print_cached_header(NULL); + wt_status_print_cached_header(s); } for (i = 0; i < active_nr; i++) { - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UPDATED), "new file: %s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UPDATED), "new file: %s", quote_crlf(active_cache[i]->name, buf, sizeof(buf))); } if (active_nr) - wt_status_print_trailer(); + wt_status_print_trailer(s); } static void wt_status_print_updated(struct wt_status *s) @@ -282,12 +286,12 @@ static void wt_status_print_untracked(struct wt_status *s) } if (!shown_header) { s->workdir_untracked = 1; - wt_status_print_header("Untracked files", + wt_status_print_header(s, "Untracked files", use_add_to_include_msg); shown_header = 1; } - color_printf(color(WT_STATUS_HEADER), "#\t"); - color_printf_ln(color(WT_STATUS_UNTRACKED), "%.*s", + color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%.*s", ent->len, ent->name); } } @@ -317,14 +321,14 @@ void wt_status_print(struct wt_status *s) branch_name = ""; on_what = "Not currently on any branch."; } - color_printf_ln(color(WT_STATUS_HEADER), + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# %s%s", on_what, branch_name); } if (s->is_initial) { - color_printf_ln(color(WT_STATUS_HEADER), "#"); - color_printf_ln(color(WT_STATUS_HEADER), "# Initial commit"); - color_printf_ln(color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); wt_status_print_initial(s); } else { @@ -338,7 +342,7 @@ void wt_status_print(struct wt_status *s) wt_status_print_verbose(s); if (!s->commitable) { if (s->amend) - printf("# No changes\n"); + fprintf(s->fp, "# No changes\n"); else if (s->workdir_dirty) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); else if (s->workdir_untracked) diff --git a/wt-status.h b/wt-status.h index cfea4ae688..77449326db 100644 --- a/wt-status.h +++ b/wt-status.h @@ -1,6 +1,8 @@ #ifndef STATUS_H #define STATUS_H +#include <stdio.h> + enum color_wt_status { WT_STATUS_HEADER, WT_STATUS_UPDATED, @@ -19,6 +21,8 @@ struct wt_status { int commitable; int workdir_dirty; int workdir_untracked; + const char *index_file; + FILE *fp; }; int git_status_config(const char *var, const char *value); |