diff options
69 files changed, 2312 insertions, 740 deletions
diff --git a/.gitignore b/.gitignore index 0eaba0a278..e8d2731ee5 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ git-diff-tree git-describe git-fast-import git-fetch +git-fetch--tool git-fetch-pack git-findtags git-fmt-merge-msg @@ -75,6 +76,7 @@ git-merge-ours git-merge-recursive git-merge-resolve git-merge-stupid +git-mergetool git-mktag git-mktree git-name-rev diff --git a/Documentation/RelNotes-1.5.0.4.txt b/Documentation/RelNotes-1.5.0.4.txt new file mode 100644 index 0000000000..b727a8d1e5 --- /dev/null +++ b/Documentation/RelNotes-1.5.0.4.txt @@ -0,0 +1,24 @@ +GIT v1.5.0.4 Release Notes +========================== + +Fixes since v1.5.0.3 +-------------------- + +* Bugfixes + + - git.el does not add duplicate sign-off lines. + + - git-commit shows the full stat of the resulting commit, not + just about the files in the current directory, when run from + a subdirectory. + + - "git-checkout -m '@{8 hours ago}'" had a funny failure from + eval; fixed. + + - git-gui updates. + +* Documentation updates + +* User manual updates + + diff --git a/Documentation/RelNotes-1.5.0.5.txt b/Documentation/RelNotes-1.5.0.5.txt new file mode 100644 index 0000000000..aa86149d4f --- /dev/null +++ b/Documentation/RelNotes-1.5.0.5.txt @@ -0,0 +1,28 @@ +GIT v1.5.0.5 Release Notes +========================== + +Fixes since v1.5.0.3 +-------------------- + +* Bugfixes + + - git-merge (hence git-pull) did not refuse fast-forwarding + when the working tree had local changes that would have + conflicted with it. + + - git.el does not add duplicate sign-off lines. + + - git-commit shows the full stat of the resulting commit, not + just about the files in the current directory, when run from + a subdirectory. + + - "git-checkout -m '@{8 hours ago}'" had a funny failure from + eval; fixed. + + - git-gui updates. + +* Documentation updates + +* User manual updates + + diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index f61c77aa7c..b54382b2bf 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -124,6 +124,7 @@ git-merge-index plumbingmanipulators git-merge mainporcelain git-merge-one-file purehelpers git-merge-tree ancillaryinterrogators +git-mergetool ancillarymanipulators git-mktag plumbingmanipulators git-mktree plumbingmanipulators git-mv mainporcelain diff --git a/Documentation/config.txt b/Documentation/config.txt index 5408dd67d3..66886424bb 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -272,6 +272,10 @@ branch.<name>.merge:: `git fetch`) to lookup the default branch for merging. Without this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. + If you wish to setup `git pull` so that it merges into <name> from + another branch in the local repository, you can point + branch.<name>.merge to the desired branch, and use the special setting + `.` (a period) for branch.<name>.remote. color.branch:: A boolean to enable/disable color in the output of @@ -453,6 +457,11 @@ merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. +merge.tool:: + Controls which merge resolution program is used by + gitlink:git-mergetool[l]. Valid values are: "kdiff3", "tkdiff", + "meld", "xxdiff", "emerge", "vimdiff" + merge.verbosity:: Controls the amount of output shown by the recursive merge strategy. Level 0 outputs nothing except a final error diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d8696b7b36..77a3f78dd7 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -159,5 +159,10 @@ -w:: Shorthand for "--ignore-all-space". +--exit-code:: + Make the program exit with codes similar to diff(1). + That is, it exits with 1 if there were differences and + 0 means no differences. + For more detailed explanation on these common options, see also link:diffcore.html[diffcore documentation]. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 3ea3b80635..603f87f3b5 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]] -'git-branch' [-l] [-f] <branchname> [<start-point>] +'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>] 'git-branch' (-m | -M) [<oldbranch>] <newbranch> 'git-branch' (-d | -D) [-r] <branchname>... @@ -26,6 +26,13 @@ It will start out with a head equal to the one given as <start-point>. If no <start-point> is given, the branch will be created with a head equal to that of the currently checked out branch. +When a local branch is started off a remote branch, git can setup the +branch so that gitlink:git-pull[1] will appropriately merge from that +remote branch. If this behavior is desired, it is possible to make it +the default using the global `branch.autosetupmerge` configuration +flag. Otherwise, it can be chosen per-branch using the `--track` +and `--no-track` options. + With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>. If <oldbranch> had a corresponding reflog, it is renamed to match <newbranch>, and a reflog entry is created to remember the branch diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 1ae77be450..f5b2d5017b 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,7 +8,7 @@ git-checkout - Checkout and switch to a branch SYNOPSIS -------- [verse] -'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>] +'git-checkout' [-q] [-f] [-b [--track | --no-track] <new_branch> [-l]] [-m] [<branch>] 'git-checkout' [<tree-ish>] <paths>... DESCRIPTION @@ -18,7 +18,8 @@ When <paths> are not given, this command switches branches by updating the index and working tree to reflect the specified branch, <branch>, and updating HEAD to be <branch> or, if specified, <new_branch>. Using -b will cause <new_branch> to -be created. +be created; in this case you can use the --track or --no-track +options, which will be passed to `git branch`. When <paths> are given, this command does *not* switch branches. It updates the named paths in the working tree from @@ -45,6 +46,16 @@ OPTIONS by gitlink:git-check-ref-format[1]. Some of these checks may restrict the characters allowed in a branch name. +--track:: + When -b is given and a branch is created off a remote branch, + setup so that git-pull will automatically retrieve data from + the remote branch. + +--no-track:: + When -b is given and a branch is created off a remote branch, + force that git-pull will automatically retrieve data from + the remote branch independent of the configuration settings. + -l:: Create the new branch's ref log. This activates recording of all changes to made the branch ref, enabling use of date diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 68de5881bd..c759efb7fc 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -86,7 +86,7 @@ OPTIONS Remove the line matching the key from config file. --unset-all:: - Remove all matching lines from config file. + Remove all lines matching the key from config file. -l, --list:: List all variables set in config file. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index e328db3797..1c6f6a7e27 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -121,10 +121,10 @@ so that calling 'cvs' effectively calls git-cvsserver. Clients known to work --------------------- -CVS 1.12.9 on Debian -CVS 1.11.17 on MacOSX (from Fink package) -Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) -TortoiseCVS +- CVS 1.12.9 on Debian +- CVS 1.11.17 on MacOSX (from Fink package) +- Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) +- TortoiseCVS Operations supported -------------------- @@ -148,13 +148,16 @@ Copyright and Authors This program is copyright The Open University UK - 2006. -Authors: Martyn Smith <martyn@catalyst.net.nz> - Martin Langhoff <martin@catalyst.net.nz> - with ideas and patches from participants of the git-list <git@vger.kernel.org>. +Authors: + +- Martyn Smith <martyn@catalyst.net.nz> +- Martin Langhoff <martin@catalyst.net.nz> + +with ideas and patches from participants of the git-list <git@vger.kernel.org>. Documentation -------------- -Documentation by Martyn Smith <martyn@catalyst.net.nz> and Martin Langhoff <martin@catalyst.net.nz> Matthias Urlichs <smurf@smurf.noris.de>. +Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@catalyst.net.nz>, and Matthias Urlichs <smurf@smurf.noris.de>. GIT --- diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt new file mode 100644 index 0000000000..5baaaca0b5 --- /dev/null +++ b/Documentation/git-mergetool.txt @@ -0,0 +1,46 @@ +git-mergetool(1) +================ + +NAME +---- +git-mergetool - Run merge conflict resolution tools to resolve merge conflicts + +SYNOPSIS +-------- +'git-mergetool' [--tool=<tool>] [<file>]... + +DESCRIPTION +----------- + +Use 'git mergetool' to run one of several merge utilities to resolve +merge conflicts. It is typically run after gitlink:git-merge[1]. + +If one or more <file> parameters are given, the merge tool program will +be run to resolve differences on each file. If no <file> names are +specified, 'git mergetool' will run the merge tool program on every file +with merge conflicts. + +OPTIONS +------- +-t or --tool=<tool>:: + Use the merge resolution program specified by <tool>. + Valid merge tools are: + kdiff3, tkdiff, meld, xxdiff, emerge, and vimdiff. + + 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. + +Author +------ +Written by Theodore Y Ts'o <tytso@mit.edu> + +Documentation +-------------- +Documentation by Theodore Y Ts'o. + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 9b3aabb6fe..682313e95d 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -60,7 +60,8 @@ The --cc option must be repeated for each user you want on the cc list. is not set, this will be prompted for. --no-signed-off-by-cc:: - Do not add emails found in Signed-off-by: lines to the cc list. + Do not add emails found in Signed-off-by: or Cc: lines to the + cc list. --quiet:: Make git-send-email less verbose. One line per email should be diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 9b5a3d6196..a0d34e0058 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -104,6 +104,14 @@ accepts. However '--fetch-all' only fetches from the current Like 'git-rebase'; this requires that the working tree be clean and have no uncommitted changes. ++ +-- +-l;; +--local;; + Do not fetch remotely; only run 'git-rebase' against the + last fetched commit from the upstream SVN. +-- ++ 'dcommit':: Commit each diff from a specified head directly to the SVN diff --git a/Documentation/git.txt b/Documentation/git.txt index e875e8318d..31397dc539 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -35,7 +35,9 @@ ifdef::stalenotes[] You are reading the documentation for the latest version of git. Documentation for older releases are available here: -* link:v1.5.0.3/git.html[documentation for release 1.5.0.3] +* link:v1.5.0.5/git.html[documentation for release 1.5.0.5] + +* link:v1.5.0.5/RelNotes-1.5.0.5.txt[release notes for 1.5.0.5] * link:v1.5.0.3/RelNotes-1.5.0.3.txt[release notes for 1.5.0.3] @@ -179,7 +179,7 @@ SCRIPT_SH = \ git-clean.sh git-clone.sh git-commit.sh \ git-fetch.sh git-gc.sh \ git-ls-remote.sh \ - git-merge-one-file.sh git-parse-remote.sh \ + git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \ git-pull.sh git-rebase.sh \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ @@ -293,6 +293,7 @@ BUILTIN_OBJS = \ builtin-diff-files.o \ builtin-diff-index.o \ builtin-diff-tree.o \ + builtin-fetch--tool.o \ builtin-fmt-merge-msg.o \ builtin-for-each-ref.o \ builtin-fsck.o \ diff --git a/builtin-add.c b/builtin-add.c index 87e16aa220..9fcf514dbc 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -12,6 +12,8 @@ static const char builtin_add_usage[] = "git-add [-n] [-v] [-f] [--interactive | -i] [--] <filepattern>..."; +static const char *excludes_file; + static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { char *seen; @@ -67,6 +69,8 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) path = git_path("info/exclude"); if (!access(path, R_OK)) add_excludes_from_file(dir, path); + if (!access(excludes_file, R_OK)) + add_excludes_from_file(dir, excludes_file); /* * Calculate common prefix for the pathspec, and @@ -88,6 +92,18 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) prune_directory(dir, pathspec, baselen); } +static int git_add_config(const char *var, const char *value) +{ + if (!strcmp(var, "core.excludesfile")) { + if (!value) + die("core.excludesfile without value"); + excludes_file = xstrdup(value); + return 0; + } + + return git_default_config(var, value); +} + static struct lock_file lock_file; static const char ignore_warning[] = @@ -115,7 +131,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) exit(1); } - git_config(git_default_config); + git_config(git_add_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); diff --git a/builtin-branch.c b/builtin-branch.c index 06d8a8ce04..a4494ee337 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -12,7 +12,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = - "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]"; + "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]"; #define REF_UNKNOWN_TYPE 0x00 #define REF_LOCAL_BRANCH 0x01 @@ -22,6 +22,8 @@ static const char builtin_branch_usage[] = static const char *head; static unsigned char head_sha1[20]; +static int branch_track_remotes; + static int branch_use_color; static char branch_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ @@ -64,6 +66,9 @@ int git_branch_config(const char *var, const char *value) color_parse(value, var, branch_colors[slot]); return 0; } + if (!strcmp(var, "branch.autosetupmerge")) + branch_track_remotes = git_config_bool(var, value); + return git_default_config(var, value); } @@ -309,14 +314,119 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev) free_ref_list(&ref_list); } +static char *config_repo; +static char *config_remote; +static const char *start_ref; +static int start_len; +static int base_len; + +static int get_remote_branch_name(const char *value) +{ + const char *colon; + const char *end; + + if (*value == '+') + value++; + + colon = strchr(value, ':'); + if (!colon) + return 0; + + end = value + strlen(value); + + /* Try an exact match first. */ + if (!strcmp(colon + 1, start_ref)) { + /* Truncate the value before the colon. */ + nfasprintf(&config_repo, "%.*s", colon - value, value); + return 1; + } + + /* Try with a wildcard match now. */ + if (end - value > 2 && end[-2] == '/' && end[-1] == '*' && + colon - value > 2 && colon[-2] == '/' && colon[-1] == '*' && + (end - 2) - (colon + 1) == base_len && + !strncmp(colon + 1, start_ref, base_len)) { + /* Replace the star with the remote branch name. */ + nfasprintf(&config_repo, "%.*s%s", + (colon - 2) - value, value, + start_ref + base_len); + return 1; + } + + return 0; +} + +static int get_remote_config(const char *key, const char *value) +{ + const char *var; + if (prefixcmp(key, "remote.")) + return 0; + + var = strrchr(key, '.'); + if (var == key + 6) + return 0; + + if (!strcmp(var, ".fetch") && get_remote_branch_name(value)) + nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7); + + return 0; +} + +static void set_branch_merge(const char *name, const char *config_remote, + const char *config_repo) +{ + char key[1024]; + if (sizeof(key) <= + snprintf(key, sizeof(key), "branch.%s.remote", name)) + die("what a long branch name you have!"); + git_config_set(key, config_remote); + + /* + * We do not have to check if we have enough space for + * the 'merge' key, since it's shorter than the + * previous 'remote' key, which we already checked. + */ + snprintf(key, sizeof(key), "branch.%s.merge", name); + git_config_set(key, config_repo); +} + +static void set_branch_defaults(const char *name, const char *real_ref) +{ + const char *slash = strrchr(real_ref, '/'); + + if (!slash) + return; + + start_ref = real_ref; + start_len = strlen(real_ref); + base_len = slash - real_ref; + git_config(get_remote_config); + if (!config_repo && !config_remote && + !prefixcmp(real_ref, "refs/heads/")) { + set_branch_merge(name, ".", real_ref); + printf("Branch %s set up to track local branch %s.\n", + name, real_ref); + } + + if (config_repo && config_remote) { + set_branch_merge(name, config_remote, config_repo); + printf("Branch %s set up to track remote branch %s.\n", + name, real_ref); + } + + if (config_repo) + free(config_repo); + if (config_remote) + free(config_remote); +} + static void create_branch(const char *name, const char *start_name, - unsigned char *start_sha1, - int force, int reflog) + int force, int reflog, int track) { struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char ref[PATH_MAX], msg[PATH_MAX + 20]; + char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; int forcing = 0; snprintf(ref, sizeof ref, "refs/heads/%s", name); @@ -331,12 +441,23 @@ static void create_branch(const char *name, const char *start_name, forcing = 1; } - if (start_sha1) - /* detached HEAD */ - hashcpy(sha1, start_sha1); - else if (get_sha1(start_name, sha1)) + real_ref = NULL; + if (get_sha1(start_name, sha1)) die("Not a valid object name: '%s'.", start_name); + switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { + case 0: + /* Not branching from any existing branch */ + real_ref = NULL; + break; + case 1: + /* Unique completion -- good */ + break; + default: + die("Ambiguous object name: '%s'.", start_name); + break; + } + if ((commit = lookup_commit_reference(sha1)) == NULL) die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); @@ -355,8 +476,17 @@ static void create_branch(const char *name, const char *start_name, snprintf(msg, sizeof msg, "branch: Created from %s", start_name); + /* When branching off a remote branch, set up so that git-pull + automatically merges from there. So far, this is only done for + remotes registered via .git/config. */ + if (real_ref && track) + set_branch_defaults(name, real_ref); + if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); + + if (real_ref) + free(real_ref); } static void rename_branch(const char *oldname, const char *newname, int force) @@ -398,11 +528,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) int delete = 0, force_delete = 0, force_create = 0; int rename = 0, force_rename = 0; int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0; - int reflog = 0; + int reflog = 0, track; int kinds = REF_LOCAL_BRANCH; int i; git_config(git_branch_config); + track = branch_track_remotes; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -413,6 +544,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) i++; break; } + if (!strcmp(arg, "--track")) { + track = 1; + continue; + } + if (!strcmp(arg, "--no-track")) { + track = 0; + continue; + } if (!strcmp(arg, "-d")) { delete = 1; continue; @@ -498,10 +637,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) rename_branch(head, argv[i], force_rename); else if (rename && (i == argc - 2)) rename_branch(argv[i], argv[i + 1], force_rename); - else if (i == argc - 1) - create_branch(argv[i], head, head_sha1, force_create, reflog); - else if (i == argc - 2) - create_branch(argv[i], argv[i+1], NULL, force_create, reflog); + else if (i == argc - 1 || i == argc - 2) + create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head, + force_create, reflog, track); else usage(builtin_branch_usage); diff --git a/builtin-diff-files.c b/builtin-diff-files.c index aec8338429..6ba5077a2b 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -17,6 +17,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) { struct rev_info rev; int nongit = 0; + int result; prefix = setup_git_directory_gently(&nongit); init_revisions(&rev, prefix); @@ -29,5 +30,6 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; - return run_diff_files_cmd(&rev, argc, argv); + result = run_diff_files_cmd(&rev, argc, argv); + return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result; } diff --git a/builtin-diff-index.c b/builtin-diff-index.c index 083599d5c4..d90eba95a6 100644 --- a/builtin-diff-index.c +++ b/builtin-diff-index.c @@ -14,6 +14,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) struct rev_info rev; int cached = 0; int i; + int result; init_revisions(&rev, prefix); git_config(git_default_config); /* no "diff" UI options */ @@ -42,5 +43,6 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) perror("read_cache"); return -1; } - return run_diff_index(&rev, cached); + result = run_diff_index(&rev, cached); + return rev.diffopt.exit_with_status ? rev.diffopt.has_changes: result; } diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index 24cb2d7f84..0b591c8716 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -118,7 +118,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (!read_stdin) - return 0; + return opt->diffopt.exit_with_status ? + opt->diffopt.has_changes: 0; if (opt->diffopt.detect_rename) opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE | @@ -133,5 +134,5 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) else diff_tree_stdin(line); } - return 0; + return opt->diffopt.exit_with_status ? opt->diffopt.has_changes: 0; } diff --git a/builtin-diff.c b/builtin-diff.c index 4efbb8237b..21d13f0b30 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -190,6 +190,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) const char *path = NULL; struct blobinfo blob[2]; int nongit = 0; + int result = 0; /* * We could get N tree-ish in the rev.pending_objects list. @@ -292,17 +293,17 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (!ents) { switch (blobs) { case 0: - return run_diff_files_cmd(&rev, argc, argv); + result = run_diff_files_cmd(&rev, argc, argv); break; case 1: if (paths != 1) usage(builtin_diff_usage); - return builtin_diff_b_f(&rev, argc, argv, blob, path); + result = builtin_diff_b_f(&rev, argc, argv, blob, path); break; case 2: if (paths) usage(builtin_diff_usage); - return builtin_diff_blobs(&rev, argc, argv, blob); + result = builtin_diff_blobs(&rev, argc, argv, blob); break; default: usage(builtin_diff_usage); @@ -311,19 +312,21 @@ int cmd_diff(int argc, const char **argv, const char *prefix) else if (blobs) usage(builtin_diff_usage); else if (ents == 1) - return builtin_diff_index(&rev, argc, argv); + result = builtin_diff_index(&rev, argc, argv); else if (ents == 2) - return builtin_diff_tree(&rev, argc, argv, ent); + result = builtin_diff_tree(&rev, argc, argv, ent); else if ((ents == 3) && (ent[0].item->flags & UNINTERESTING)) { /* diff A...B where there is one sane merge base between * A and B. We have ent[0] == merge-base, ent[1] == A, * and ent[2] == B. Show diff between the base and B. */ ent[1] = ent[2]; - return builtin_diff_tree(&rev, argc, argv, ent); + result = builtin_diff_tree(&rev, argc, argv, ent); } else - return builtin_diff_combined(&rev, argc, argv, + result = builtin_diff_combined(&rev, argc, argv, ent, ents); - usage(builtin_diff_usage); + if (rev.diffopt.exit_with_status) + result = rev.diffopt.has_changes; + return result; } diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c new file mode 100644 index 0000000000..e9d6764550 --- /dev/null +++ b/builtin-fetch--tool.c @@ -0,0 +1,505 @@ +#include "cache.h" +#include "refs.h" +#include "commit.h" + +#define CHUNK_SIZE 1024 + +static char *get_stdin(void) +{ + int offset = 0; + char *data = xmalloc(CHUNK_SIZE); + + while (1) { + int cnt = xread(0, data + offset, CHUNK_SIZE); + if (cnt < 0) + die("error reading standard input: %s", + strerror(errno)); + if (cnt == 0) { + data[offset] = 0; + break; + } + offset += cnt; + data = xrealloc(data, offset + CHUNK_SIZE); + } + return data; +} + +static void show_new(enum object_type type, unsigned char *sha1_new) +{ + fprintf(stderr, " %s: %s\n", typename(type), + find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); +} + +static int update_ref(const char *action, + const char *refname, + unsigned char *sha1, + unsigned char *oldval) +{ + int len; + char msg[1024]; + char *rla = getenv("GIT_REFLOG_ACTION"); + static struct ref_lock *lock; + + if (!rla) + rla = "(reflog update)"; + len = snprintf(msg, sizeof(msg), "%s: %s", rla, action); + if (sizeof(msg) <= len) + die("insanely long action"); + lock = lock_any_ref_for_update(refname, oldval); + if (!lock) + return 1; + if (write_ref_sha1(lock, sha1, msg) < 0) + return 1; + return 0; +} + +static int update_local_ref(const char *name, + const char *new_head, + const char *note, + int verbose, int force) +{ + unsigned char sha1_old[20], sha1_new[20]; + char oldh[41], newh[41]; + struct commit *current, *updated; + enum object_type type; + + if (get_sha1_hex(new_head, sha1_new)) + die("malformed object name %s", new_head); + + type = sha1_object_info(sha1_new, NULL); + if (type < 0) + die("object %s not found", new_head); + + if (!*name) { + /* Not storing */ + if (verbose) { + fprintf(stderr, "* fetched %s\n", note); + show_new(type, sha1_new); + } + return 0; + } + + if (get_sha1(name, sha1_old)) { + char *msg; + just_store: + /* new ref */ + if (!strncmp(name, "refs/tags/", 10)) + msg = "storing tag"; + else + msg = "storing head"; + fprintf(stderr, "* %s: storing %s\n", + name, note); + show_new(type, sha1_new); + return update_ref(msg, name, sha1_new, NULL); + } + + if (!hashcmp(sha1_old, sha1_new)) { + if (verbose) { + fprintf(stderr, "* %s: same as %s\n", name, note); + show_new(type, sha1_new); + } + return 0; + } + + if (!strncmp(name, "refs/tags/", 10)) { + fprintf(stderr, "* %s: updating with %s\n", name, note); + show_new(type, sha1_new); + return update_ref("updating tag", name, sha1_new, NULL); + } + + current = lookup_commit_reference(sha1_old); + updated = lookup_commit_reference(sha1_new); + if (!current || !updated) + goto just_store; + + strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV)); + strcpy(newh, find_unique_abbrev(sha1_new, DEFAULT_ABBREV)); + + if (in_merge_bases(current, &updated, 1)) { + fprintf(stderr, "* %s: fast forward to %s\n", + name, note); + fprintf(stderr, " old..new: %s..%s\n", oldh, newh); + return update_ref("fast forward", name, sha1_new, sha1_old); + } + if (!force) { + fprintf(stderr, + "* %s: not updating to non-fast forward %s\n", + name, note); + fprintf(stderr, + " old...new: %s...%s\n", oldh, newh); + return 1; + } + fprintf(stderr, + "* %s: forcing update to non-fast forward %s\n", + name, note); + fprintf(stderr, " old...new: %s...%s\n", oldh, newh); + return update_ref("forced-update", name, sha1_new, sha1_old); +} + +static int append_fetch_head(FILE *fp, + const char *head, const char *remote, + const char *remote_name, const char *remote_nick, + const char *local_name, int not_for_merge, + int verbose, int force) +{ + struct commit *commit; + int remote_len, i, note_len; + unsigned char sha1[20]; + char note[1024]; + const char *what, *kind; + + if (get_sha1(head, sha1)) + return error("Not a valid object name: %s", head); + commit = lookup_commit_reference(sha1); + if (!commit) + not_for_merge = 1; + + if (!strcmp(remote_name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!strncmp(remote_name, "refs/heads/", 11)) { + kind = "branch"; + what = remote_name + 11; + } + else if (!strncmp(remote_name, "refs/tags/", 10)) { + kind = "tag"; + what = remote_name + 10; + } + else if (!strncmp(remote_name, "refs/remotes/", 13)) { + kind = "remote branch"; + what = remote_name + 13; + } + else { + kind = ""; + what = remote_name; + } + + remote_len = strlen(remote); + for (i = remote_len - 1; remote[i] == '/' && 0 <= i; i--) + ; + remote_len = i + 1; + if (4 < i && !strncmp(".git", remote + i - 3, 4)) + remote_len = i - 3; + + note_len = 0; + if (*what) { + if (*kind) + note_len += sprintf(note + note_len, "%s ", kind); + note_len += sprintf(note + note_len, "'%s' of ", what); + } + note_len += sprintf(note + note_len, "%.*s", remote_len, remote); + fprintf(fp, "%s\t%s\t%s\n", + sha1_to_hex(commit ? commit->object.sha1 : sha1), + not_for_merge ? "not-for-merge" : "", + note); + return update_local_ref(local_name, head, note, verbose, force); +} + +static char *keep; +static void remove_keep(void) +{ + if (keep && *keep) + unlink(keep); +} + +static void remove_keep_on_signal(int signo) +{ + remove_keep(); + signal(SIGINT, SIG_DFL); + raise(signo); +} + +static char *find_local_name(const char *remote_name, const char *refs, + int *force_p, int *not_for_merge_p) +{ + const char *ref = refs; + int len = strlen(remote_name); + + while (ref) { + const char *next; + int single_force, not_for_merge; + + while (*ref == '\n') + ref++; + if (!*ref) + break; + next = strchr(ref, '\n'); + + single_force = not_for_merge = 0; + if (*ref == '+') { + single_force = 1; + ref++; + } + if (*ref == '.') { + not_for_merge = 1; + ref++; + if (*ref == '+') { + single_force = 1; + ref++; + } + } + if (!strncmp(remote_name, ref, len) && ref[len] == ':') { + const char *local_part = ref + len + 1; + char *ret; + int retlen; + + if (!next) + retlen = strlen(local_part); + else + retlen = next - local_part; + ret = xmalloc(retlen + 1); + memcpy(ret, local_part, retlen); + ret[retlen] = 0; + *force_p = single_force; + *not_for_merge_p = not_for_merge; + return ret; + } + ref = next; + } + return NULL; +} + +static int fetch_native_store(FILE *fp, + const char *remote, + const char *remote_nick, + const char *refs, + int verbose, int force) +{ + char buffer[1024]; + int err = 0; + + signal(SIGINT, remove_keep_on_signal); + atexit(remove_keep); + + while (fgets(buffer, sizeof(buffer), stdin)) { + int len; + char *cp; + char *local_name; + int single_force, not_for_merge; + + for (cp = buffer; *cp && !isspace(*cp); cp++) + ; + if (*cp) + *cp++ = 0; + len = strlen(cp); + if (len && cp[len-1] == '\n') + cp[--len] = 0; + if (!strcmp(buffer, "failed")) + die("Fetch failure: %s", remote); + if (!strcmp(buffer, "pack")) + continue; + if (!strcmp(buffer, "keep")) { + char *od = get_object_directory(); + int len = strlen(od) + strlen(cp) + 50; + keep = xmalloc(len); + sprintf(keep, "%s/pack/pack-%s.keep", od, cp); + continue; + } + + local_name = find_local_name(cp, refs, + &single_force, ¬_for_merge); + if (!local_name) + continue; + err |= append_fetch_head(fp, + buffer, remote, cp, remote_nick, + local_name, not_for_merge, + verbose, force || single_force); + } + return err; +} + +static int parse_reflist(const char *reflist) +{ + const char *ref; + + printf("refs='"); + for (ref = reflist; ref; ) { + const char *next; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + printf("\n%.*s", (int)(next - ref), ref); + ref = next; + } + printf("'\n"); + + printf("rref='"); + for (ref = reflist; ref; ) { + const char *next, *colon; + while (*ref && isspace(*ref)) + ref++; + if (!*ref) + break; + for (next = ref; *next && !isspace(*next); next++) + ; + if (*ref == '.') + ref++; + if (*ref == '+') + ref++; + colon = strchr(ref, ':'); + putchar('\n'); + printf("%.*s", (int)((colon ? colon : next) - ref), ref); + ref = next; + } + printf("'\n"); + return 0; +} + +static int expand_refs_wildcard(const char *ls_remote_result, int numrefs, + const char **refs) +{ + int i, matchlen, replacelen; + int found_one = 0; + const char *remote = *refs++; + numrefs--; + + if (numrefs == 0) { + fprintf(stderr, "Nothing specified for fetching with remote.%s.fetch\n", + remote); + printf("empty\n"); + } + + for (i = 0; i < numrefs; i++) { + const char *ref = refs[i]; + const char *lref = ref; + const char *colon; + const char *tail; + const char *ls; + const char *next; + + if (*lref == '+') + lref++; + colon = strchr(lref, ':'); + tail = lref + strlen(lref); + if (!(colon && + 2 < colon - lref && + colon[-1] == '*' && + colon[-2] == '/' && + 2 < tail - (colon + 1) && + tail[-1] == '*' && + tail[-2] == '/')) { + /* not a glob */ + if (!found_one++) + printf("explicit\n"); + printf("%s\n", ref); + continue; + } + + /* glob */ + if (!found_one++) + printf("glob\n"); + + /* lref to colon-2 is remote hierarchy name; + * colon+1 to tail-2 is local. + */ + matchlen = (colon-1) - lref; + replacelen = (tail-1) - (colon+1); + for (ls = ls_remote_result; ls; ls = next) { + const char *eol; + unsigned char sha1[20]; + int namelen; + + while (*ls && isspace(*ls)) + ls++; + next = strchr(ls, '\n'); + eol = !next ? (ls + strlen(ls)) : next; + if (!memcmp("^{}", eol-3, 3)) + continue; + if (eol - ls < 40) + continue; + if (get_sha1_hex(ls, sha1)) + continue; + ls += 40; + while (ls < eol && isspace(*ls)) + ls++; + /* ls to next (or eol) is the name. + * is it identical to lref to colon-2? + */ + if ((eol - ls) <= matchlen || + strncmp(ls, lref, matchlen)) + continue; + + /* Yes, it is a match */ + namelen = eol - ls; + if (lref != ref) + putchar('+'); + printf("%.*s:%.*s%.*s\n", + namelen, ls, + replacelen, colon + 1, + namelen - matchlen, ls + matchlen); + } + } + return 0; +} + +int cmd_fetch__tool(int argc, const char **argv, const char *prefix) +{ + int verbose = 0; + int force = 0; + + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp("-v", arg)) + verbose = 1; + else if (!strcmp("-f", arg)) + force = 1; + else + break; + argc--; + argv++; + } + + if (argc <= 1) + return error("Missing subcommand"); + + if (!strcmp("append-fetch-head", argv[1])) { + int result; + FILE *fp; + + if (argc != 8) + return error("append-fetch-head takes 6 args"); + fp = fopen(git_path("FETCH_HEAD"), "a"); + result = append_fetch_head(fp, argv[2], argv[3], + argv[4], argv[5], + argv[6], !!argv[7][0], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("native-store", argv[1])) { + int result; + FILE *fp; + + if (argc != 5) + return error("fetch-native-store takes 3 args"); + fp = fopen(git_path("FETCH_HEAD"), "a"); + result = fetch_native_store(fp, argv[2], argv[3], argv[4], + verbose, force); + fclose(fp); + return result; + } + if (!strcmp("parse-reflist", argv[1])) { + const char *reflist; + if (argc != 3) + return error("parse-reflist takes 1 arg"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return parse_reflist(reflist); + } + if (!strcmp("expand-refs-wildcard", argv[1])) { + const char *reflist; + if (argc < 4) + return error("expand-refs-wildcard takes at least 2 args"); + reflist = argv[2]; + if (!strcmp(reflist, "-")) + reflist = get_stdin(); + return expand_refs_wildcard(reflist, argc - 3, argv + 3); + } + + return error("Unknown subcommand: %s", argv[1]); +} diff --git a/builtin-grep.c b/builtin-grep.c index 694da5ba09..4510d35324 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -431,6 +431,19 @@ static const char emsg_missing_context_len[] = static const char emsg_missing_argument[] = "option requires an argument -%s"; +static int strtoul_ui(char const *s, unsigned int *result) +{ + unsigned long ul; + char *p; + + errno = 0; + ul = strtoul(s, &p, 10); + if (errno || *p || p == s || (unsigned int) ul != ul) + return -1; + *result = ul; + return 0; +} + int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; @@ -553,7 +566,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) scan = arg + 1; break; } - if (sscanf(scan, "%u", &num) != 1) + if (strtoul_ui(scan, &num)) die(emsg_invalid_context_len, scan); switch (arg[1]) { case 'A': diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index f54e8752fb..d94578cb4a 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -11,19 +11,22 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; static int keep_subject; static const char *metainfo_charset; static char line[1000]; -static char date[1000]; static char name[1000]; static char email[1000]; -static char subject[1000]; static enum { TE_DONTCARE, TE_QP, TE_BASE64, } transfer_encoding; -static char charset[256]; +static enum { + TYPE_TEXT, TYPE_OTHER, +} message_type; -static char multipart_boundary[1000]; -static int multipart_boundary_len; +static char charset[256]; static int patch_lines; +static char **p_hdr_data, **s_hdr_data; + +#define MAX_HDR_PARSED 10 +#define MAX_BOUNDARIES 5 static char *sanity_check(char *name, char *email) { @@ -137,15 +140,13 @@ static int handle_from(char *in_line) return 1; } -static int handle_date(char *line) +static int handle_header(char *line, char *data, int ofs) { - strcpy(date, line); - return 0; -} + if (!line || !data) + return 1; + + strcpy(data, line+ofs); -static int handle_subject(char *line) -{ - strcpy(subject, line); return 0; } @@ -177,17 +178,32 @@ static int slurp_attr(const char *line, const char *name, char *attr) return 1; } -static int handle_subcontent_type(char *line) +struct content_type { + char *boundary; + int boundary_len; +}; + +static struct content_type content[MAX_BOUNDARIES]; + +static struct content_type *content_top = content; + +static int handle_content_type(char *line) { - /* We do not want to mess with boundary. Note that we do not - * handle nested multipart. - */ - if (strcasestr(line, "boundary=")) { - fprintf(stderr, "Not handling nested multipart message.\n"); - exit(1); + char boundary[256]; + + if (strcasestr(line, "text/") == NULL) + message_type = TYPE_OTHER; + if (slurp_attr(line, "boundary=", boundary + 2)) { + memcpy(boundary, "--", 2); + if (content_top++ >= &content[MAX_BOUNDARIES]) { + fprintf(stderr, "Too many boundaries to handle\n"); + exit(1); + } + content_top->boundary_len = strlen(boundary); + content_top->boundary = xmalloc(content_top->boundary_len+1); + strcpy(content_top->boundary, boundary); } - slurp_attr(line, "charset=", charset); - if (*charset) { + if (slurp_attr(line, "charset=", charset)) { int i, c; for (i = 0; (c = charset[i]) != 0; i++) charset[i] = tolower(c); @@ -195,17 +211,6 @@ static int handle_subcontent_type(char *line) return 0; } -static int handle_content_type(char *line) -{ - *multipart_boundary = 0; - if (slurp_attr(line, "boundary=", multipart_boundary + 2)) { - memcpy(multipart_boundary, "--", 2); - multipart_boundary_len = strlen(multipart_boundary); - } - slurp_attr(line, "charset=", charset); - return 0; -} - static int handle_content_transfer_encoding(char *line) { if (strcasestr(line, "base64")) @@ -219,7 +224,7 @@ static int handle_content_transfer_encoding(char *line) static int is_multipart_boundary(const char *line) { - return (!memcmp(line, multipart_boundary, multipart_boundary_len)); + return (!memcmp(line, content_top->boundary, content_top->boundary_len)); } static int eatspace(char *line) @@ -230,62 +235,6 @@ static int eatspace(char *line) return len; } -#define SEEN_FROM 01 -#define SEEN_DATE 02 -#define SEEN_SUBJECT 04 -#define SEEN_BOGUS_UNIX_FROM 010 -#define SEEN_PREFIX 020 - -/* First lines of body can have From:, Date:, and Subject: or empty */ -static void handle_inbody_header(int *seen, char *line) -{ - if (*seen & SEEN_PREFIX) - return; - if (isspace(*line)) { - char *cp; - for (cp = line + 1; *cp; cp++) { - if (!isspace(*cp)) - break; - } - if (!*cp) - return; - } - if (!memcmp(">From", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_BOGUS_UNIX_FROM)) { - *seen |= SEEN_BOGUS_UNIX_FROM; - return; - } - } - if (!memcmp("From:", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_FROM) && handle_from(line+6)) { - *seen |= SEEN_FROM; - return; - } - } - if (!memcmp("Date:", line, 5) && isspace(line[5])) { - if (!(*seen & SEEN_DATE)) { - handle_date(line+6); - *seen |= SEEN_DATE; - return; - } - } - if (!memcmp("Subject:", line, 8) && isspace(line[8])) { - if (!(*seen & SEEN_SUBJECT)) { - handle_subject(line+9); - *seen |= SEEN_SUBJECT; - return; - } - } - if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { - if (!(*seen & SEEN_SUBJECT)) { - handle_subject(line); - *seen |= SEEN_SUBJECT; - return; - } - } - *seen |= SEEN_PREFIX; -} - static char *cleanup_subject(char *subject) { if (keep_subject) @@ -296,7 +245,7 @@ static char *cleanup_subject(char *subject) switch (*subject) { case 'r': case 'R': if (!memcmp("e:", subject+1, 2)) { - subject +=3; + subject += 3; continue; } break; @@ -341,57 +290,62 @@ static void cleanup_space(char *buf) } static void decode_header(char *it); -typedef int (*header_fn_t)(char *); -struct header_def { - const char *name; - header_fn_t func; - int namelen; +static char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", }; -static void check_header(char *line, struct header_def *header) +static int check_header(char *line, char **hdr_data) { int i; - if (header[0].namelen <= 0) { - for (i = 0; header[i].name; i++) - header[i].namelen = strlen(header[i].name); - } - for (i = 0; header[i].name; i++) { - int len = header[i].namelen; - if (!strncasecmp(line, header[i].name, len) && + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if (!hdr_data[i] && + !strncasecmp(line, header[i], len) && line[len] == ':' && isspace(line[len + 1])) { /* Unwrap inline B and Q encoding, and optionally * normalize the meta information to utf8. */ decode_header(line + len + 2); - header[i].func(line + len + 2); - break; + hdr_data[i] = xmalloc(1000 * sizeof(char)); + if (! handle_header(line, hdr_data[i], len + 2)) { + return 1; + } } } -} -static void check_subheader_line(char *line) -{ - static struct header_def header[] = { - { "Content-Type", handle_subcontent_type }, - { "Content-Transfer-Encoding", - handle_content_transfer_encoding }, - { NULL }, - }; - check_header(line, header); -} -static void check_header_line(char *line) -{ - static struct header_def header[] = { - { "From", handle_from }, - { "Date", handle_date }, - { "Subject", handle_subject }, - { "Content-Type", handle_content_type }, - { "Content-Transfer-Encoding", - handle_content_transfer_encoding }, - { NULL }, - }; - check_header(line, header); + /* Content stuff */ + if (!strncasecmp(line, "Content-Type", 12) && + line[12] == ':' && isspace(line[12 + 1])) { + decode_header(line + 12 + 2); + if (! handle_content_type(line)) { + return 1; + } + } + if (!strncasecmp(line, "Content-Transfer-Encoding", 25) && + line[25] == ':' && isspace(line[25 + 1])) { + decode_header(line + 25 + 2); + if (! handle_content_transfer_encoding(line)) { + return 1; + } + } + + /* for inbody stuff */ + if (!memcmp(">From", line, 5) && isspace(line[5])) + return 1; + if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) { + for (i = 0; header[i]; i++) { + if (!memcmp("Subject: ", header[i], 9)) { + if (! handle_header(line, hdr_data[i], 0)) { + return 1; + } + } + } + } + + /* no match */ + return 0; } static int is_rfc2822_header(char *line) @@ -647,147 +601,253 @@ static void decode_transfer_encoding(char *line) } } -static void handle_info(void) +static int handle_filter(char *line); + +static int find_boundary(void) { - char *sub; + while(fgets(line, sizeof(line), fin) != NULL) { + if (is_multipart_boundary(line)) + return 1; + } + return 0; +} + +static int handle_boundary(void) +{ +again: + if (!memcmp(line+content_top->boundary_len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + free(content_top->boundary); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (content_top-- < content) { + fprintf(stderr, "Detected mismatched boundaries, " + "can't recover\n"); + exit(1); + } + handle_filter("\n"); + + /* skip to the next boundary */ + if (!find_boundary()) + return 0; + goto again; + } + + /* set some defaults */ + transfer_encoding = TE_DONTCARE; + charset[0] = 0; + message_type = TYPE_TEXT; - sub = cleanup_subject(subject); - cleanup_space(name); - cleanup_space(date); - cleanup_space(email); - cleanup_space(sub); + /* slurp in this section's info */ + while (read_one_header_line(line, sizeof(line), fin)) + check_header(line, p_hdr_data); - fprintf(fout, "Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", - name, email, sub, date); + /* eat the blank line after section info */ + return (fgets(line, sizeof(line), fin) != NULL); } -/* We are inside message body and have read line[] already. - * Spit out the commit log. - */ -static int handle_commit_msg(int *seen) +static inline int patchbreak(const char *line) { - if (!cmitmsg) - return 0; - do { - if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3) || - !memcmp("Index: ", line, 7)) - break; - if ((multipart_boundary[0] && is_multipart_boundary(line))) { - /* We come here when the first part had only - * the commit message without any patch. We - * pretend we have not seen this line yet, and - * go back to the loop. - */ + /* Beginning of a "diff -" header? */ + if (!memcmp("diff -", line, 6)) + return 1; + + /* CVS "Index: " line? */ + if (!memcmp("Index: ", line, 7)) + return 1; + + /* + * "--- <filename>" starts patches without headers + * "---<sp>*" is a manual separator + */ + if (!memcmp("---", line, 3)) { + line += 3; + /* space followed by a filename? */ + if (line[0] == ' ' && !isspace(line[1])) return 1; + /* Just whitespace? */ + for (;;) { + unsigned char c = *line++; + if (c == '\n') + return 1; + if (!isspace(c)) + break; } + return 0; + } + return 0; +} - /* Unwrap transfer encoding and optionally - * normalize the log message to UTF-8. - */ - decode_transfer_encoding(line); - if (metainfo_charset) - convert_to_utf8(line, charset); - handle_inbody_header(seen, line); - if (!(*seen & SEEN_PREFIX)) - continue; +static int handle_commit_msg(char *line) +{ + static int still_looking = 1; - fputs(line, cmitmsg); - } while (fgets(line, sizeof(line), fin) != NULL); - fclose(cmitmsg); - cmitmsg = NULL; + if (!cmitmsg) + return 0; + + if (still_looking) { + char *cp = line; + if (isspace(*line)) { + for (cp = line + 1; *cp; cp++) { + if (!isspace(*cp)) + break; + } + if (!*cp) + return 0; + } + if ((still_looking = check_header(cp, s_hdr_data)) != 0) + return 0; + } + + if (patchbreak(line)) { + fclose(cmitmsg); + cmitmsg = NULL; + return 1; + } + + fputs(line, cmitmsg); return 0; } -/* We have done the commit message and have the first - * line of the patch in line[]. - */ -static void handle_patch(void) +static int handle_patch(char *line) { - do { - if (multipart_boundary[0] && is_multipart_boundary(line)) - break; - /* Only unwrap transfer encoding but otherwise do not - * do anything. We do *NOT* want UTF-8 conversion - * here; we are dealing with the user payload. - */ - decode_transfer_encoding(line); - fputs(line, patchfile); - patch_lines++; - } while (fgets(line, sizeof(line), fin) != NULL); + fputs(line, patchfile); + patch_lines++; + return 0; } -/* multipart boundary and transfer encoding are set up for us, and we - * are at the end of the sub header. do equivalent of handle_body up - * to the next boundary without closing patchfile --- we will expect - * that the first part to contain commit message and a patch, and - * handle other parts as pure patches. - */ -static int handle_multipart_one_part(int *seen) +static int handle_filter(char *line) { - int n = 0; + static int filter = 0; - while (fgets(line, sizeof(line), fin) != NULL) { - again: - n++; - if (is_multipart_boundary(line)) + /* filter tells us which part we left off on + * a non-zero return indicates we hit a filter point + */ + switch (filter) { + case 0: + if (!handle_commit_msg(line)) break; - if (handle_commit_msg(seen)) - goto again; - handle_patch(); - break; + filter++; + case 1: + if (!handle_patch(line)) + break; + filter++; + default: + return 1; } - if (n == 0) - return -1; + return 0; } -static void handle_multipart_body(void) +static void handle_body(void) { - int seen = 0; - int part_num = 0; + int rc = 0; + static char newline[2000]; + static char *np = newline; /* Skip up to the first boundary */ - while (fgets(line, sizeof(line), fin) != NULL) - if (is_multipart_boundary(line)) { - part_num = 1; + if (content_top->boundary) { + if (!find_boundary()) + return; + } + + do { + /* process any boundary lines */ + if (content_top->boundary && is_multipart_boundary(line)) { + /* flush any leftover */ + if ((transfer_encoding == TE_BASE64) && + (np != newline)) { + handle_filter(newline); + } + if (!handle_boundary()) + return; + } + + /* Unwrap transfer encoding and optionally + * normalize the log message to UTF-8. + */ + decode_transfer_encoding(line); + if (metainfo_charset) + convert_to_utf8(line, charset); + + switch (transfer_encoding) { + case TE_BASE64: + { + char *op = line; + + /* binary data most likely doesn't have newlines */ + if (message_type != TYPE_TEXT) { + rc = handle_filter(line); + break; + } + + /* this is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + + do { + while (*op != '\n' && *op != 0) + *np++ = *op++; + *np = *op; + if (*np != 0) { + /* should be sitting on a new line */ + *(++np) = 0; + op++; + rc = handle_filter(newline); + np = newline; + } + } while (*op != 0); + /* the partial chunk is saved in newline and + * will be appended by the next iteration of fgets + */ break; } - if (!part_num) - return; - /* We are on boundary line. Start slurping the subhead. */ - while (1) { - int hdr = read_one_header_line(line, sizeof(line), fin); - if (!hdr) { - if (handle_multipart_one_part(&seen) < 0) - return; - /* Reset per part headers */ - transfer_encoding = TE_DONTCARE; - charset[0] = 0; + default: + rc = handle_filter(line); } - else - check_subheader_line(line); - } - fclose(patchfile); - if (!patch_lines) { - fprintf(stderr, "No patch found\n"); - exit(1); - } + if (rc) + /* nothing left to filter */ + break; + } while (fgets(line, sizeof(line), fin)); + + return; } -/* Non multipart message */ -static void handle_body(void) +static void handle_info(void) { - int seen = 0; + char *sub; + char *hdr; + int i; - handle_commit_msg(&seen); - handle_patch(); - fclose(patchfile); - if (!patch_lines) { - fprintf(stderr, "No patch found\n"); - exit(1); + for (i = 0; header[i]; i++) { + + /* only print inbody headers if we output a patch file */ + if (patch_lines && s_hdr_data[i]) + hdr = s_hdr_data[i]; + else if (p_hdr_data[i]) + hdr = p_hdr_data[i]; + else + continue; + + if (!memcmp(header[i], "Subject", 7)) { + sub = cleanup_subject(hdr); + cleanup_space(sub); + fprintf(fout, "Subject: %s\n", sub); + } else if (!memcmp(header[i], "From", 4)) { + handle_from(hdr); + fprintf(fout, "Author: %s\n", name); + fprintf(fout, "Email: %s\n", email); + } else { + cleanup_space(hdr); + fprintf(fout, "%s: %s\n", header[i], hdr); + } } + fprintf(fout, "\n"); } int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, @@ -809,18 +869,16 @@ int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, fclose(cmitmsg); return -1; } - while (1) { - int hdr = read_one_header_line(line, sizeof(line), fin); - if (!hdr) { - if (multipart_boundary[0]) - handle_multipart_body(); - else - handle_body(); - handle_info(); - break; - } - check_header_line(line); - } + + p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); + s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *)); + + /* process the email header */ + while (read_one_header_line(line, sizeof(line), fin)) + check_header(line, p_hdr_data); + + handle_body(); + handle_info(); return 0; } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index f8ebad0b2f..73d448b890 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -166,11 +166,12 @@ static void prepare_pack_revindex(struct pack_revindex *rix) struct packed_git *p = rix->p; int num_ent = num_packed_objects(p); int i; - void *index = p->index_base + 256; + const char *index = p->index_data; + index += 4 * 256; rix->revindex = xmalloc(sizeof(*rix->revindex) * (num_ent + 1)); for (i = 0; i < num_ent; i++) { - unsigned int hl = *((unsigned int *)((char *) index + 24*i)); + uint32_t hl = *((uint32_t *)(index + 24 * i)); rix->revindex[i].offset = ntohl(hl); rix->revindex[i].nr = i; } @@ -217,11 +218,11 @@ static off_t find_packed_object_size(struct packed_git *p, off_t ofs) return entry[1].offset - ofs; } -static unsigned char *find_packed_object_name(struct packed_git *p, - off_t ofs) +static const unsigned char *find_packed_object_name(struct packed_git *p, + off_t ofs) { struct revindex_entry *entry = find_packed_object(p, ofs); - return (unsigned char *)(p->index_base + 256) + 24 * entry->nr + 4; + return ((unsigned char *)p->index_data) + 4 * 256 + 24 * entry->nr + 4; } static void *delta_against(void *buf, unsigned long size, struct object_entry *entry) @@ -996,7 +997,8 @@ static void check_object(struct object_entry *entry) * delta. */ if (!no_reuse_delta) { - unsigned char c, *base_name; + unsigned char c; + const unsigned char *base_name; off_t ofs; unsigned long used_0; /* there is at least 20 bytes left in the pack */ diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 2d7726e8b9..29343aefc8 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -304,8 +304,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) if (!access(".mailmap", R_OK)) read_mailmap(".mailmap"); - if (rev.pending.nr == 0) + if (rev.pending.nr == 0) { + if (isatty(0)) + fprintf(stderr, "(reading log to summarize from standard input)\n"); read_from_stdin(&list); + } else get_from_rev(&rev, &list); @@ -32,6 +32,7 @@ extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); +extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix); extern int cmd_format_patch(int argc, const char **argv, const char *prefix); @@ -371,9 +371,11 @@ struct pack_window { extern struct packed_git { struct packed_git *next; struct pack_window *windows; - uint32_t *index_base; + const void *index_data; off_t index_size; off_t pack_size; + time_t mtime; + int index_version; int pack_fd; int pack_local; unsigned char sha1[20]; @@ -411,7 +413,7 @@ extern int server_supports(const char *feature); extern struct packed_git *parse_pack_index(unsigned char *sha1); extern struct packed_git *parse_pack_index_file(const unsigned char *sha1, - char *idx_path); + const char *idx_path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); @@ -423,7 +425,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void unuse_pack(struct pack_window **); -extern struct packed_git *add_packed_git(char *, int, int); +extern struct packed_git *add_packed_git(const char *, int, int); extern uint32_t num_packed_objects(const struct packed_git *p); extern int nth_packed_object_sha1(const struct packed_git *, uint32_t, unsigned char*); extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); @@ -480,6 +482,7 @@ extern struct tag *alloc_tag_node(void); extern void alloc_report(void); /* trace.c */ +extern int nfasprintf(char **str, const char *fmt, ...); extern int nfvasprintf(char **str, const char *fmt, va_list va); extern void trace_printf(const char *format, ...); extern void trace_argv_printf(const char **argv, int count, const char *format, ...); @@ -351,12 +351,12 @@ int git_default_config(const char *var, const char *value) } if (!strcmp(var, "i18n.commitencoding")) { - git_commit_encoding = strdup(value); + git_commit_encoding = xstrdup(value); return 0; } if (!strcmp(var, "i18n.logoutputencoding")) { - git_log_output_encoding = strdup(value); + git_log_output_encoding = xstrdup(value); return 0; } diff --git a/diff-lib.c b/diff-lib.c index 6abb981534..5c5b05bfe3 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -170,8 +170,10 @@ static int handle_diff_files_args(struct rev_info *revs, else if (!strcmp(argv[1], "--theirs")) revs->max_count = 3; else if (!strcmp(argv[1], "-n") || - !strcmp(argv[1], "--no-index")) + !strcmp(argv[1], "--no-index")) { revs->max_count = -2; + revs->diffopt.exit_with_status = 1; + } else if (!strcmp(argv[1], "-q")) *silent = 1; else @@ -237,6 +239,7 @@ int setup_diff_no_index(struct rev_info *revs, break; } else if (i < argc - 3 && !strcmp(argv[i], "--no-index")) { i = argc - 3; + revs->diffopt.exit_with_status = 1; break; } if (argc != i + 2 || (!is_outside_repo(argv[i + 1], nongit, prefix) && @@ -321,6 +324,9 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed) struct cache_entry *ce = active_cache[i]; int changed; + if (revs->diffopt.quiet && revs->diffopt.has_changes) + break; + if (!ce_path_match(ce, revs->prune_data)) continue; @@ -562,6 +568,9 @@ static int diff_cache(struct rev_info *revs, struct cache_entry *ce = *ac; int same = (entries > 1) && ce_same_name(ce, ac[1]); + if (revs->diffopt.quiet && revs->diffopt.has_changes) + break; + if (!ce_path_match(ce, pathspec)) goto skip_entry; @@ -1958,6 +1958,23 @@ int diff_setup_done(struct diff_options *options) if (options->abbrev <= 0 || 40 < options->abbrev) options->abbrev = 40; /* full */ + /* + * It does not make sense to show the first hit we happened + * to have found. It does not make sense not to return with + * exit code in such a case either. + */ + if (options->quiet) { + options->output_format = DIFF_FORMAT_NO_OUTPUT; + options->exit_with_status = 1; + } + + /* + * If we postprocess in diffcore, we cannot simply return + * upon the first hit. We need to run diff as usual. + */ + if (options->pickaxe || options->filter) + options->quiet = 0; + return 0; } @@ -2134,6 +2151,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->color_diff = options->color_diff_words = 1; else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; + else if (!strcmp(arg, "--exit-code")) + options->exit_with_status = 1; + else if (!strcmp(arg, "--quiet")) + options->quiet = 1; else return 0; return 1; @@ -2898,6 +2919,8 @@ static void diffcore_apply_filter(const char *filter) void diffcore_std(struct diff_options *options) { + if (options->quiet) + return; if (options->break_opt != -1) diffcore_break(options->break_opt); if (options->detect_rename) @@ -2910,18 +2933,11 @@ void diffcore_std(struct diff_options *options) diffcore_order(options->orderfile); diff_resolve_rename_copy(); diffcore_apply_filter(options->filter); -} - -void diffcore_std_no_resolve(struct diff_options *options) -{ - if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); - if (options->orderfile) - diffcore_order(options->orderfile); - diffcore_apply_filter(options->filter); + options->has_changes = !!diff_queued_diff.nr; } + void diff_addremove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, @@ -2957,6 +2973,7 @@ void diff_addremove(struct diff_options *options, fill_filespec(two, sha1, mode); diff_queue(&diff_queued_diff, one, two); + options->has_changes = 1; } void diff_change(struct diff_options *options, @@ -2982,6 +2999,7 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_sha1, new_mode); diff_queue(&diff_queued_diff, one, two); + options->has_changes = 1; } void diff_unmerge(struct diff_options *options, @@ -56,7 +56,10 @@ struct diff_options { silent_on_remove:1, find_copies_harder:1, color_diff:1, - color_diff_words:1; + color_diff_words:1, + has_changes:1, + quiet:1, + exit_with_status:1; int context; int break_opt; int detect_rename; @@ -170,8 +173,6 @@ extern int diff_setup_done(struct diff_options *); extern void diffcore_std(struct diff_options *); -extern void diffcore_std_no_resolve(struct diff_options *); - #define COMMON_DIFF_OPTIONS_HELP \ "\ncommon diff options:\n" \ " -z output diff-raw with lines terminated with NUL.\n" \ @@ -290,6 +290,10 @@ do git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ <"$dotest/$msgnum" >"$dotest/info" || stop_here $this + test -s $dotest/patch || { + echo "Patch is empty. Was is split wrong?" + stop_here $this + } git-stripspace < "$dotest/msg" > "$dotest/msg-clean" ;; esac diff --git a/git-applymbox.sh b/git-applymbox.sh index 1f68599ae5..3efd6a7464 100755 --- a/git-applymbox.sh +++ b/git-applymbox.sh @@ -77,6 +77,10 @@ do *) git-mailinfo $keep_subject $utf8 \ .dotest/msg .dotest/patch <$i >.dotest/info || exit 1 + test -s .dotest/patch || { + echo "Patch is empty. Was is split wrong?" + exit 1 + } git-stripspace < .dotest/msg > .dotest/msg-clean ;; esac diff --git a/git-checkout.sh b/git-checkout.sh index 14835a4aa9..39ffa8b8a3 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -12,6 +12,7 @@ new= new_name= force= branch= +track= newbranch= newbranch_log= merge= @@ -33,7 +34,10 @@ while [ "$#" != "0" ]; do die "git checkout: we do not like '$newbranch' as a branch name." ;; "-l") - newbranch_log=1 + newbranch_log=-l + ;; + "--track"|"--no-track") + track="$arg" ;; "-f") force=1 @@ -85,6 +89,11 @@ while [ "$#" != "0" ]; do esac done +case "$newbranch,$track" in +,--*) + die "git checkout: --track and --no-track require -b" +esac + case "$force$merge" in 11) die "git checkout: -f and -m are incompatible" @@ -196,7 +205,7 @@ else work=`git write-tree` && git read-tree --reset -u $new || exit - eval GITHEAD_$new=${new_name:-${branch:-$new}} && + eval GITHEAD_$new='${new_name:-${branch:-$new}}' && eval GITHEAD_$work=local && export GITHEAD_$new GITHEAD_$work && git merge-recursive $old -- $new $work @@ -235,11 +244,7 @@ fi # if [ "$?" -eq 0 ]; then if [ "$newbranch" ]; then - if [ "$newbranch_log" ]; then - mkdir -p $(dirname "$GIT_DIR/logs/refs/heads/$newbranch") - touch "$GIT_DIR/logs/refs/heads/$newbranch" - fi - git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit + git-branch $track $newbranch_log "$newbranch" "$new_name" || exit branch="$newbranch" fi if test -n "$branch" diff --git a/git-fetch.sh b/git-fetch.sh index 5ae0d28cc0..e218042843 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -109,133 +109,11 @@ ls_remote_result=$(git ls-remote $exec "$remote") || die "Cannot get the repository state from $remote" append_fetch_head () { - head_="$1" - remote_="$2" - remote_name_="$3" - remote_nick_="$4" - local_name_="$5" - case "$6" in - t) not_for_merge_='not-for-merge' ;; - '') not_for_merge_= ;; - esac - - # remote-nick is the URL given on the command line (or a shorthand) - # remote-name is the $GIT_DIR relative refs/ path we computed - # for this refspec. - - # the $note_ variable will be fed to git-fmt-merge-msg for further - # processing. - case "$remote_name_" in - HEAD) - note_= ;; - refs/heads/*) - note_="$(expr "$remote_name_" : 'refs/heads/\(.*\)')" - note_="branch '$note_' of " ;; - refs/tags/*) - note_="$(expr "$remote_name_" : 'refs/tags/\(.*\)')" - note_="tag '$note_' of " ;; - refs/remotes/*) - note_="$(expr "$remote_name_" : 'refs/remotes/\(.*\)')" - note_="remote branch '$note_' of " ;; - *) - note_="$remote_name of " ;; - esac - remote_1_=$(expr "z$remote_" : 'z\(.*\)\.git/*$') && - remote_="$remote_1_" - note_="$note_$remote_" - - # 2.6.11-tree tag would not be happy to be fed to resolve. - if git-cat-file commit "$head_" >/dev/null 2>&1 - then - headc_=$(git-rev-parse --verify "$head_^0") || exit - echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD" - else - echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD" - fi - - update_local_ref "$local_name_" "$head_" "$note_" -} - -update_local_ref () { - # If we are storing the head locally make sure that it is - # a fast forward (aka "reverse push"). - - label_=$(git-cat-file -t $2) - newshort_=$(git-rev-parse --short $2) - if test -z "$1" ; then - [ "$verbose" ] && echo >&2 "* fetched $3" - [ "$verbose" ] && echo >&2 " $label_: $newshort_" - return 0 - fi - oldshort_=$(git show-ref --hash --abbrev "$1" 2>/dev/null) - - case "$1" in - refs/tags/*) - # Tags need not be pointing at commits so there - # is no way to guarantee "fast-forward" anyway. - if test -n "$oldshort_" - then - if now_=$(git show-ref --hash "$1") && test "$now_" = "$2" - then - [ "$verbose" ] && echo >&2 "* $1: same as $3" - [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||: - else - echo >&2 "* $1: updating with $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: updating tag" "$1" "$2" - fi - else - echo >&2 "* $1: storing $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: storing tag" "$1" "$2" - fi - ;; - - refs/heads/* | refs/remotes/*) - # $1 is the ref being updated. - # $2 is the new value for the ref. - local=$(git-rev-parse --verify "$1^0" 2>/dev/null) - if test "$local" - then - # Require fast-forward. - mb=$(git-merge-base "$local" "$2") && - case "$2,$mb" in - $local,*) - if test -n "$verbose" - then - echo >&2 "* $1: same as $3" - echo >&2 " $label_: $newshort_" - fi - ;; - *,$local) - echo >&2 "* $1: fast forward to $3" - echo >&2 " old..new: $oldshort_..$newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: fast-forward" "$1" "$2" "$local" - ;; - *) - false - ;; - esac || { - case ",$force,$single_force," in - *,t,*) - echo >&2 "* $1: forcing update to non-fast forward $3" - echo >&2 " old...new: $oldshort_...$newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: forced-update" "$1" "$2" "$local" - ;; - *) - echo >&2 "* $1: not updating to non-fast forward $3" - echo >&2 " old...new: $oldshort_...$newshort_" - exit 1 - ;; - esac - } - else - echo >&2 "* $1: storing $3" - echo >&2 " $label_: $newshort_" - git-update-ref -m "$GIT_REFLOG_ACTION: storing head" "$1" "$2" - fi - ;; - esac + flags= + test -n "$verbose" && flags="$flags -v" + test -n "$force" && flags="$flags -f" + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ + git-fetch--tool $flags append-fetch-head "$@" } # updating the current HEAD with git-fetch in a bare @@ -279,7 +157,40 @@ then fi fi -fetch_main () { +fetch_all_at_once () { + + eval=$(echo "$1" | git-fetch--tool parse-reflist "-") + eval "$eval" + + ( : subshell because we muck with IFS + IFS=" $LF" + ( + if test "$remote" = . ; then + git-show-ref $rref || echo failed "$remote" + elif test -f "$remote" ; then + test -n "$shallow_depth" && + die "shallow clone with bundle is not supported" + git-bundle unbundle "$remote" $rref || + echo failed "$remote" + else + git-fetch-pack --thin $exec $keep $shallow_depth $no_progress \ + "$remote" $rref || + echo failed "$remote" + fi + ) | + ( + flags= + test -n "$verbose" && flags="$flags -v" + test -n "$force" && flags="$flags -f" + GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \ + git-fetch--tool $flags native-store \ + "$remote" "$remote_nick" "$refs" + ) + ) || exit + +} + +fetch_per_ref () { reflist="$1" refs= rref= @@ -371,9 +282,6 @@ fetch_main () { rsync_slurped_objects=t } ;; - *) - # We will do git native transport with just one call later. - continue ;; esac append_fetch_head "$head" "$remote" \ @@ -381,80 +289,17 @@ fetch_main () { done - case "$remote" in - http://* | https://* | ftp://* | rsync://* ) - ;; # we are already done. - *) - ( : subshell because we muck with IFS - IFS=" $LF" - ( - if test -f "$remote" ; then - test -n "$shallow_depth" && - die "shallow clone with bundle is not supported" - git-bundle unbundle "$remote" $rref || - echo failed "$remote" - else - git-fetch-pack --thin $exec $keep $shallow_depth $no_progress \ - "$remote" $rref || - echo failed "$remote" - fi - ) | - ( - trap ' - if test -n "$keepfile" && test -f "$keepfile" - then - rm -f "$keepfile" - fi - ' 0 - - keepfile= - while read sha1 remote_name - do - case "$sha1" in - failed) - echo >&2 "Fetch failure: $remote" - exit 1 ;; - # special line coming from index-pack with the pack name - pack) - continue ;; - keep) - keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep" - continue ;; - esac - found= - single_force= - for ref in $refs - do - case "$ref" in - +$remote_name:*) - single_force=t - not_for_merge= - found="$ref" - break ;; - .+$remote_name:*) - single_force=t - not_for_merge=t - found="$ref" - break ;; - .$remote_name:*) - not_for_merge=t - found="$ref" - break ;; - $remote_name:*) - not_for_merge= - found="$ref" - break ;; - esac - done - local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') - append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" \ - "$not_for_merge" || exit - done - ) - ) || exit ;; - esac +} +fetch_main () { + case "$remote" in + http://* | https://* | ftp://* | rsync://* ) + fetch_per_ref "$@" + ;; + *) + fetch_all_at_once "$@" + ;; + esac } fetch_main "$reflist" || exit diff --git a/git-merge.sh b/git-merge.sh index 6ce62c860a..8759c5a7e0 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -292,13 +292,13 @@ f,*) # Again the most common case of merging one remote. echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)" git-update-index --refresh 2>/dev/null - new_head=$(git-rev-parse --verify "$1^0") && - git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && msg="Fast forward" if test -n "$have_message" then msg="$msg (no commit created; -m option ignored)" fi + new_head=$(git-rev-parse --verify "$1^0") && + git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && finish "$new_head" "$msg" || exit dropsave exit 0 diff --git a/git-mergetool.sh b/git-mergetool.sh new file mode 100755 index 0000000000..7942fd0b64 --- /dev/null +++ b/git-mergetool.sh @@ -0,0 +1,359 @@ +#!/bin/sh +# +# This program resolves merge conflicts in git +# +# Copyright (c) 2006 Theodore Y. Ts'o +# +# This file is licensed under the GPL v2, or a later version +# at the discretion of Junio C Hammano. +# + +USAGE='[--tool=tool] [file to merge] ...' +SUBDIRECTORY_OK=Yes +. git-sh-setup +require_work_tree + +# Returns true if the mode reflects a symlink +function is_symlink () { + test "$1" = 120000 +} + +function local_present () { + test -n "$local_mode" +} + +function remote_present () { + test -n "$remote_mode" +} + +function base_present () { + test -n "$base_mode" +} + +cleanup_temp_files () { + if test "$1" = --save-backup ; then + mv -- "$BACKUP" "$path.orig" + rm -f -- "$LOCAL" "$REMOTE" "$BASE" + else + rm -f -- "$LOCAL" "$REMOTE" "$BASE" "$BACKUP" + fi +} + +function describe_file () { + mode="$1" + branch="$2" + file="$3" + + echo -n " " + if test -z "$mode"; then + echo -n "'$path' was deleted" + elif is_symlink "$mode" ; then + echo -n "'$path' is a symlink containing '" + cat "$file" + echo -n "'" + else + if base_present; then + echo -n "'$path' was created" + else + echo -n "'$path' was modified" + fi + fi + echo " in the $branch branch" +} + + +resolve_symlink_merge () { + while /bin/true; do + echo -n "Use (r)emote or (l)ocal, or (a)bort? " + read ans + case "$ans" in + [lL]*) + git-checkout-index -f --stage=2 -- "$path" + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [rR]*) + git-checkout-index -f --stage=3 -- "$path" + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [qQ]*) + exit 1 + ;; + esac + done +} + +resolve_deleted_merge () { + while /bin/true; do + echo -n "Use (m)odified or (d)eleted file, or (a)bort? " + read ans + case "$ans" in + [mM]*) + git-add -- "$path" + cleanup_temp_files --save-backup + return + ;; + [dD]*) + git-rm -- "$path" + cleanup_temp_files + return + ;; + [qQ]*) + exit 1 + ;; + esac + done +} + +merge_file () { + path="$1" + + if test ! -f "$path" ; then + echo "$path: file not found" + exit 1 + fi + + f=`git-ls-files -u -- "$path"` + if test -z "$f" ; then + echo "$path: file does not need merging" + exit 1 + fi + + BACKUP="$path.BACKUP.$$" + LOCAL="$path.LOCAL.$$" + REMOTE="$path.REMOTE.$$" + BASE="$path.BASE.$$" + + mv -- "$path" "$BACKUP" + cp -- "$BACKUP" "$path" + + base_mode=`git ls-files -u -- "$path" | awk '{if ($3==1) print $1;}'` + local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'` + remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'` + + base_present && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null + local_present && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null + remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null + + if test -z "$local_mode" -o -z "$remote_mode"; then + echo "Deleted merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + resolve_deleted_merge + return + fi + + if is_symlink "$local_mode" || is_symlink "$remote_mode"; then + echo "Symlink merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + resolve_symlink_merge + return + fi + + echo "Normal merge conflict for $path:" + describe_file "$local_mode" "local" "$LOCAL" + describe_file "$remote_mode" "remote" "$REMOTE" + echo -n "Hit return to start merge resolution tool ($merge_tool): " + read ans + + case "$merge_tool" in + kdiff3) + if base_present ; then + (kdiff3 --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)" \ + -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1) + fi + status=$? + if test "$status" -eq 0; then + rm "$BACKUP" + fi + ;; + tkdiff) + if base_present ; then + tkdiff -a "$BASE" -o "$path" -- "$LOCAL" "$REMOTE" + else + tkdiff -o "$path" -- "$LOCAL" "$REMOTE" + fi + status=$? + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + meld|vimdiff) + touch "$BACKUP" + $merge_tool -- "$LOCAL" "$path" "$REMOTE" + if test "$path" -nt "$BACKUP" ; then + status=0; + else + while true; do + echo "$path seems unchanged." + echo -n "Was the merge successful? [y/n] " + read answer < /dev/tty + case "$answer" in + y*|Y*) status=0; break ;; + n*|N*) status=1; break ;; + esac + done + fi + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + xxdiff) + touch "$BACKUP" + if base_present ; then + xxdiff -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 \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$path" -- "$LOCAL" "$REMOTE" + fi + if test "$path" -nt "$BACKUP" ; then + status=0; + else + while true; do + echo "$path seems unchanged." + echo -n "Was the merge successful? [y/n] " + read answer < /dev/tty + case "$answer" in + y*|Y*) status=0; break ;; + n*|N*) status=1; break ;; + esac + done + fi + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + emerge) + if base_present ; then + emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path" + else + emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path" + fi + status=$? + if test "$status" -eq 0; then + mv -- "$BACKUP" "$path.orig" + fi + ;; + esac + if test "$status" -ne 0; then + echo "merge of $path failed" 1>&2 + mv -- "$BACKUP" "$path" + exit 1 + fi + git add -- "$path" + cleanup_temp_files +} + +while case $# in 0) break ;; esac +do + case "$1" in + -t|--tool*) + case "$#,$1" in + *,*=*) + merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'` + ;; + 1,*) + usage ;; + *) + merge_tool="$2" + shift ;; + esac + ;; + --) + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift +done + +if test -z "$merge_tool"; then + merge_tool=`git-config merge.tool` + case "$merge_tool" in + kdiff3 | tkdiff | xxdiff | meld | emerge | vimdiff) + ;; # happy + *) + echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" + echo >&2 "Resetting to default..." + unset merge_tool + ;; + esac +fi + +if test -z "$merge_tool" ; then + if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool="kdiff3"; + elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=tkdiff + elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=xxdiff + elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then + merge_tool=meld + elif type emacs >/dev/null 2>&1; then + merge_tool=emerge + elif type vimdiff >/dev/null 2>&1; then + merge_tool=vimdiff + else + echo "No available merge resolution programs available." + exit 1 + fi +fi + +case "$merge_tool" in + kdiff3|tkdiff|meld|xxdiff|vimdiff) + 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` + if test -z "$files" ; then + echo "No files need merging" + exit 0 + fi + echo Merging the files: $files + git ls-files -u | sed -e 's/^[^ ]* //' | sort -u | while read i + do + echo "" + merge_file "$i" < /dev/tty > /dev/tty + done +else + while test $# -gt 0; do + echo "" + merge_file "$1" + shift + done +fi +exit 0 diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 5208ee6ce0..437b0c3b1b 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -9,6 +9,9 @@ get_data_source () { */*) echo '' ;; + .) + echo self + ;; *) if test "$(git-config --get "remote.$1.url")" then @@ -31,6 +34,9 @@ get_remote_url () { '') echo "$1" ;; + self) + echo "$1" + ;; config) git-config --get "remote.$1.url" ;; @@ -57,7 +63,7 @@ get_default_remote () { get_remote_default_refs_for_push () { data_source=$(get_data_source "$1") case "$data_source" in - '' | branches) + '' | branches | self) ;; # no default push mapping, just send matching refs. config) git-config --get-all "remote.$1.push" ;; @@ -81,51 +87,8 @@ get_remote_default_refs_for_push () { # is to help prevent randomly "globbed" ref from being chosen as # a merge candidate expand_refs_wildcard () { - remote="$1" - shift - first_one=yes - if test "$#" = 0 - then - echo empty - echo >&2 "Nothing specified for fetching with remote.$remote.fetch" - fi - for ref - do - lref=${ref#'+'} - # a non glob pattern is given back as-is. - expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || { - if test -n "$first_one" - then - echo "explicit" - first_one= - fi - echo "$ref" - continue - } - - # glob - if test -n "$first_one" - then - echo "glob" - first_one= - fi - from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'` - to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'` - local_force= - test "z$lref" = "z$ref" || local_force='+' - echo "$ls_remote_result" | - sed -e '/\^{}$/d' | - ( - IFS=' ' - while read sha1 name - do - # ignore the ones that do not start with $from - mapped=${name#"$from"} - test "z$name" = "z$mapped" && continue - echo "${local_force}${name}:${to}${mapped}" - done - ) - done + echo "$ls_remote_result" | + git fetch--tool expand-refs-wildcard "-" "$@" } # Subroutine to canonicalize remote:local notation. @@ -206,6 +169,10 @@ get_remote_default_refs_for_fetch () { case "$data_source" in '') echo "HEAD:" ;; + self) + canon_refs_list_for_fetch -d "$1" \ + $(git-for-each-ref --format='%(refname):') + ;; config) canon_refs_list_for_fetch -d "$1" \ $(git-config --get-all "remote.$1.fetch") ;; @@ -220,7 +187,7 @@ get_remote_default_refs_for_fetch () { }' "$GIT_DIR/remotes/$1") ;; *) - die "internal error: get-remote-default-ref-for-push $1" ;; + die "internal error: get-remote-default-ref-for-fetch $1" ;; esac } diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 671a5ff865..08ac9bb729 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -73,6 +73,10 @@ mkdir $tmp_dir || exit 2 for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do echo $patch_name (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3 + test -s $dotest/patch || { + echo "Patch is empty. Was is split wrong?" + stop_here $this + } # Parse the author information export GIT_AUTHOR_NAME=$(sed -ne 's/Author: //p' "$tmp_info") diff --git a/git-send-email.perl b/git-send-email.perl index 6989c0260f..ae50990d08 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -65,8 +65,8 @@ Options: Defaults to on. --no-signed-off-cc Suppress the automatic addition of email addresses - that appear in a Signed-off-by: line, to the cc: list. - Note: Using this option is not recommended. + that appear in Signed-off-by: or Cc: lines to the cc: + list. Note: Using this option is not recommended. --smtp-server If set, specifies the outgoing SMTP server to use. Defaults to localhost. @@ -572,8 +572,8 @@ foreach my $t (@files) { } } else { $message .= $_; - if (/^Signed-off-by: (.*)$/i && !$no_signed_off_cc) { - my $c = $1; + if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) { + my $c = $2; chomp $c; push @cc, $c; printf("(sob) Adding cc: %s from line '%s'\n", diff --git a/git-svn.perl b/git-svn.perl index 326e89fe03..e8457893db 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -56,7 +56,7 @@ my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, $_version, $_fetch_all, - $_merge, $_strategy, $_dry_run, + $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_verbose); $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, @@ -145,6 +145,7 @@ my %cmd = ( { 'merge|m|M' => \$_merge, 'verbose|v' => \$_verbose, 'strategy|s=s' => \$_strategy, + 'local|l' => \$_local, 'fetch-all|all' => \$_fetch_all, %fc_opts } ], 'commit-diff' => [ \&cmd_commit_diff, @@ -439,7 +440,9 @@ sub cmd_rebase { command_noisy('status'); exit 1; } - $_fetch_all ? $gs->fetch_all : $gs->fetch; + unless ($_local) { + $_fetch_all ? $gs->fetch_all : $gs->fetch; + } command_noisy(rebase_cmd(), $gs->refname); } @@ -243,6 +243,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "diff-files", cmd_diff_files }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fetch--tool", cmd_fetch__tool, RUN_SETUP }, { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, { "for-each-ref", cmd_for_each_ref, RUN_SETUP }, { "format-patch", cmd_format_patch, RUN_SETUP }, diff --git a/local-fetch.c b/local-fetch.c index 7cfe8b3587..4b650efa8b 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -64,9 +64,9 @@ static int copy_file(const char *source, char *dest, const char *hex, } /* If we got ENOENT there is no point continuing. */ if (errno == ENOENT) { - if (warn_if_not_exists) - fprintf(stderr, "does not exist %s\n", source); - return -1; + if (!warn_if_not_exists) + return -1; + return error("does not exist %s", source); } } if (use_symlink) { @@ -74,9 +74,8 @@ static int copy_file(const char *source, char *dest, const char *hex, if (stat(source, &st)) { if (!warn_if_not_exists && errno == ENOENT) return -1; - fprintf(stderr, "cannot stat %s: %s\n", source, - strerror(errno)); - return -1; + return error("cannot stat %s: %s", source, + strerror(errno)); } if (!symlink(source, dest)) { pull_say("symlink %s\n", hex); @@ -90,25 +89,21 @@ static int copy_file(const char *source, char *dest, const char *hex, if (ifd < 0) { if (!warn_if_not_exists && errno == ENOENT) return -1; - fprintf(stderr, "cannot open %s\n", source); - return -1; + return error("cannot open %s", source); } ofd = open(dest, O_WRONLY | O_CREAT | O_EXCL, 0666); if (ofd < 0) { - fprintf(stderr, "cannot open %s\n", dest); close(ifd); - return -1; + return error("cannot open %s", dest); } status = copy_fd(ifd, ofd); close(ofd); if (status) - fprintf(stderr, "cannot write %s\n", dest); - else - pull_say("copy %s\n", hex); - return status; + return error("cannot write %s", dest); + pull_say("copy %s\n", hex); + return 0; } - fprintf(stderr, "failed to copy %s with given copy methods.\n", hex); - return -1; + return error("failed to copy %s with given copy methods.", hex); } static int fetch_pack(const unsigned char *sha1) @@ -181,13 +176,11 @@ int fetch_ref(char *ref, unsigned char *sha1) ifd = open(filename, O_RDONLY); if (ifd < 0) { close(ifd); - fprintf(stderr, "cannot open %s\n", filename); - return -1; + return error("cannot open %s", filename); } if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) { close(ifd); - fprintf(stderr, "cannot read from %s\n", filename); - return -1; + return error("cannot read from %s", filename); } close(ifd); pull_say("ref %s\n", sha1_to_hex(sha1)); diff --git a/pack-check.c b/pack-check.c index 299c514128..d9883225ea 100644 --- a/pack-check.c +++ b/pack-check.c @@ -5,7 +5,7 @@ static int verify_packfile(struct packed_git *p, struct pack_window **w_curs) { off_t index_size = p->index_size; - void *index_base = p->index_base; + const unsigned char *index_base = p->index_data; SHA_CTX ctx; unsigned char sha1[20]; off_t offset = 0, pack_sig = p->pack_size - 20; @@ -31,7 +31,7 @@ static int verify_packfile(struct packed_git *p, if (hashcmp(sha1, use_pack(p, w_curs, pack_sig, NULL))) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); - if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40)) + if (hashcmp(sha1, index_base + index_size - 40)) return error("Packfile %s SHA1 mismatch with idx", p->pack_name); unuse_pack(w_curs); @@ -127,7 +127,7 @@ static void show_pack_info(struct packed_git *p) int verify_pack(struct packed_git *p, int verbose) { off_t index_size = p->index_size; - void *index_base = p->index_base; + const unsigned char *index_base = p->index_data; SHA_CTX ctx; unsigned char sha1[20]; int ret; @@ -137,7 +137,7 @@ int verify_pack(struct packed_git *p, int verbose) SHA1_Init(&ctx); SHA1_Update(&ctx, index_base, (unsigned int)(index_size - 20)); SHA1_Final(sha1, &ctx); - if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20)) + if (hashcmp(sha1, index_base + index_size - 20)) ret = error("Packfile index for %s SHA1 mismatch", p->pack_name); diff --git a/pack-redundant.c b/pack-redundant.c index c8f7d9af7b..40e579b2d9 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -17,7 +17,7 @@ static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; - unsigned char *sha1; + const unsigned char *sha1; }; static struct llist { struct llist_item *front; @@ -104,9 +104,9 @@ static struct llist * llist_copy(struct llist *list) return ret; } -static inline struct llist_item * llist_insert(struct llist *list, - struct llist_item *after, - unsigned char *sha1) +static inline struct llist_item *llist_insert(struct llist *list, + struct llist_item *after, + const unsigned char *sha1) { struct llist_item *new = llist_item_get(); new->sha1 = sha1; @@ -128,12 +128,14 @@ static inline struct llist_item * llist_insert(struct llist *list, return new; } -static inline struct llist_item *llist_insert_back(struct llist *list, unsigned char *sha1) +static inline struct llist_item *llist_insert_back(struct llist *list, + const unsigned char *sha1) { return llist_insert(list, list->back, sha1); } -static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, unsigned char *sha1, struct llist_item *hint) +static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, + const unsigned char *sha1, struct llist_item *hint) { struct llist_item *prev = NULL, *l; @@ -246,12 +248,12 @@ static struct pack_list * pack_list_difference(const struct pack_list *A, static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) { int p1_off, p2_off; - unsigned char *p1_base, *p2_base; + const unsigned char *p1_base, *p2_base; struct llist_item *p1_hint = NULL, *p2_hint = NULL; p1_off = p2_off = 256 * 4 + 4; - p1_base = (unsigned char *) p1->pack->index_base; - p2_base = (unsigned char *) p2->pack->index_base; + p1_base = p1->pack->index_data; + p2_base = p2->pack->index_data; while (p1_off <= p1->pack->index_size - 3 * 20 && p2_off <= p2->pack->index_size - 3 * 20) @@ -351,11 +353,11 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; - unsigned char *p1_base, *p2_base; + const unsigned char *p1_base, *p2_base; p1_off = p2_off = 256 * 4 + 4; - p1_base = (unsigned char *)p1->index_base; - p2_base = (unsigned char *)p2->index_base; + p1_base = p1->index_data; + p2_base = p2->index_data; while (p1_off <= p1->index_size - 3 * 20 && p2_off <= p2->index_size - 3 * 20) @@ -534,7 +536,7 @@ static struct pack_list * add_pack(struct packed_git *p) { struct pack_list l; size_t off; - unsigned char *base; + const unsigned char *base; if (!p->pack_local && !(alt_odb || verbose)) return NULL; @@ -543,7 +545,7 @@ static struct pack_list * add_pack(struct packed_git *p) llist_init(&l.all_objects); off = 256 * 4 + 4; - base = (unsigned char *)p->index_base; + base = p->index_data; while (off <= p->index_size - 3 * 20) { llist_insert_back(l.all_objects, base + off); off += 24; @@ -16,24 +16,15 @@ struct pack_header { }; /* - * Packed object index header - * - * struct pack_idx_header { - * uint32_t idx_signature; - * uint32_t idx_version; - * }; - * - * Note: this header isn't active yet. In future versions of git - * we may change the index file format. At that time we would start - * the first four bytes of the new index format with this signature, - * as all older git binaries would find this value illegal and abort - * reading the file. + * The first four bytes of index formats later than version 1 should + * start with this signature, as all older git binaries would find this + * value illegal and abort reading the file. * * This is the case because the number of objects in a packfile * cannot exceed 1,431,660,000 as every object would need at least - * 3 bytes of data and the overall packfile cannot exceed 4 GiB due - * to the 32 bit offsets used by the index. Clearly the signature - * exceeds this maximum. + * 3 bytes of data and the overall packfile cannot exceed 4 GiB with + * version 1 of the index file due to the offsets limited to 32 bits. + * Clearly the signature exceeds this maximum. * * Very old git binaries will also compare the first 4 bytes to the * next 4 bytes in the index and abort with a "non-monotonic index" @@ -43,6 +34,15 @@ struct pack_header { */ #define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ +/* + * Packed object index header + */ +struct pack_idx_header { + uint32_t idx_signature; + uint32_t idx_version; +}; + + extern int verify_pack(struct packed_git *, int); #define PH_ERROR_EOF (-1) diff --git a/perl/Makefile b/perl/Makefile index 5ec0389883..17d004e5a0 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -33,7 +33,7 @@ $(makfile): ../GIT-CFLAGS Makefile echo ' echo $(instdir_SQ)' >> $@ else $(makfile): Makefile.PL ../GIT-CFLAGS - $(QUIET_GEN)'$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' + '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)' endif # this is just added comfort for calling make directly in perl dir diff --git a/revision.c b/revision.c index 3c2eb125e6..bcdb6a1212 100644 --- a/revision.c +++ b/revision.c @@ -213,6 +213,13 @@ static int everybody_uninteresting(struct commit_list *orig) return 1; } +/* + * The goal is to get REV_TREE_NEW as the result only if the + * diff consists of all '+' (and no other changes), and + * REV_TREE_DIFFERENT otherwise (of course if the trees are + * the same we want REV_TREE_SAME). That means that once we + * get to REV_TREE_DIFFERENT, we do not have to look any further. + */ static int tree_difference = REV_TREE_SAME; static void file_add_remove(struct diff_options *options, @@ -236,6 +243,8 @@ static void file_add_remove(struct diff_options *options, diff = REV_TREE_NEW; } tree_difference = diff; + if (tree_difference == REV_TREE_DIFFERENT) + options->has_changes = 1; } static void file_change(struct diff_options *options, @@ -245,6 +254,7 @@ static void file_change(struct diff_options *options, const char *base, const char *path) { tree_difference = REV_TREE_DIFFERENT; + options->has_changes = 1; } int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2) @@ -254,6 +264,7 @@ int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2) if (!t2) return REV_TREE_DIFFERENT; tree_difference = REV_TREE_SAME; + revs->pruning.has_changes = 0; if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &revs->pruning) < 0) return REV_TREE_DIFFERENT; @@ -277,11 +288,12 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) empty.buf = ""; empty.size = 0; - tree_difference = 0; + tree_difference = REV_TREE_SAME; + revs->pruning.has_changes = 0; retval = diff_tree(&empty, &real, "", &revs->pruning); free(tree); - return retval >= 0 && !tree_difference; + return retval >= 0 && (tree_difference == REV_TREE_SAME); } static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) @@ -545,6 +557,7 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->ignore_merges = 1; revs->simplify_history = 1; revs->pruning.recursive = 1; + revs->pruning.quiet = 1; revs->pruning.add_remove = file_add_remove; revs->pruning.change = file_change; revs->lifo = 1; diff --git a/sha1_file.c b/sha1_file.c index 7faa8bcd50..ee64865b60 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -432,16 +432,15 @@ void pack_report() pack_mapped, peak_pack_mapped); } -static int check_packed_git_idx(const char *path, - unsigned long *idx_size_, - void **idx_map_) +static int check_packed_git_idx(const char *path, struct packed_git *p) { void *idx_map; - uint32_t *index; + struct pack_idx_header *hdr; size_t idx_size; - uint32_t nr, i; + uint32_t nr, i, *index; int fd = open(path, O_RDONLY); struct stat st; + if (fd < 0) return -1; if (fstat(fd, &st)) { @@ -456,15 +455,12 @@ static int check_packed_git_idx(const char *path, idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - index = idx_map; - *idx_map_ = idx_map; - *idx_size_ = idx_size; - /* a future index format would start with this, as older git * binaries would fail the non-monotonic index check below. * give a nicer warning to the user if we can. */ - if (index[0] == htonl(PACK_IDX_SIGNATURE)) { + hdr = idx_map; + if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) { munmap(idx_map, idx_size); return error("index file %s is a newer version" " and is not supported by this binary" @@ -473,6 +469,7 @@ static int check_packed_git_idx(const char *path, } nr = 0; + index = idx_map; for (i = 0; i < 256; i++) { uint32_t n = ntohl(index[i]); if (n < nr) { @@ -494,6 +491,9 @@ static int check_packed_git_idx(const char *path, return error("wrong index file size in %s", path); } + p->index_version = 1; + p->index_data = idx_map; + p->index_size = idx_size; return 0; } @@ -614,7 +614,7 @@ static int open_packed_git_1(struct packed_git *p) return error("end of packfile %s is unavailable", p->pack_name); if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1)) return error("packfile %s signature is unavailable", p->pack_name); - idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40; + idx_sha1 = ((unsigned char *)p->index_data) + p->index_size - 40; if (hashcmp(sha1, idx_sha1)) return error("packfile %s does not match index", p->pack_name); return 0; @@ -710,37 +710,37 @@ unsigned char* use_pack(struct packed_git *p, return win->base + offset; } -struct packed_git *add_packed_git(char *path, int path_len, int local) +struct packed_git *add_packed_git(const char *path, int path_len, int local) { struct stat st; - struct packed_git *p; - unsigned long idx_size; - void *idx_map; - unsigned char sha1[20]; + struct packed_git *p = xmalloc(sizeof(*p) + path_len + 2); - if (check_packed_git_idx(path, &idx_size, &idx_map)) + /* + * Make sure a corresponding .pack file exists and that + * the index looks sane. + */ + path_len -= strlen(".idx"); + if (path_len < 1) return NULL; - - /* do we have a corresponding .pack file? */ - strcpy(path + path_len - 4, ".pack"); - if (stat(path, &st) || !S_ISREG(st.st_mode)) { - munmap(idx_map, idx_size); + memcpy(p->pack_name, path, path_len); + strcpy(p->pack_name + path_len, ".pack"); + if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) || + check_packed_git_idx(path, p)) { + free(p); return NULL; } + /* ok, it looks sane as far as we can check without * actually mapping the pack file. */ - p = xmalloc(sizeof(*p) + path_len + 2); - strcpy(p->pack_name, path); - p->index_size = idx_size; p->pack_size = st.st_size; - p->index_base = idx_map; p->next = NULL; p->windows = NULL; p->pack_fd = -1; p->pack_local = local; - if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1)) - hashcpy(p->sha1, sha1); + p->mtime = st.st_mtime; + if (path_len < 40 || get_sha1_hex(path + path_len - 40, p->sha1)) + hashclr(p->sha1); return p; } @@ -750,23 +750,19 @@ struct packed_git *parse_pack_index(unsigned char *sha1) return parse_pack_index_file(sha1, path); } -struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path) +struct packed_git *parse_pack_index_file(const unsigned char *sha1, + const char *idx_path) { - struct packed_git *p; - unsigned long idx_size; - void *idx_map; - char *path; + const char *path = sha1_pack_name(sha1); + struct packed_git *p = xmalloc(sizeof(*p) + strlen(path) + 2); - if (check_packed_git_idx(idx_path, &idx_size, &idx_map)) + if (check_packed_git_idx(idx_path, p)) { + free(p); return NULL; + } - path = sha1_pack_name(sha1); - - p = xmalloc(sizeof(*p) + strlen(path) + 2); strcpy(p->pack_name, path); - p->index_size = idx_size; p->pack_size = 0; - p->index_base = idx_map; p->next = NULL; p->windows = NULL; p->pack_fd = -1; @@ -823,6 +819,60 @@ static void prepare_packed_git_one(char *objdir, int local) closedir(dir); } +static int sort_pack(const void *a_, const void *b_) +{ + struct packed_git *a = *((struct packed_git **)a_); + struct packed_git *b = *((struct packed_git **)b_); + int st; + + /* + * Local packs tend to contain objects specific to our + * variant of the project than remote ones. In addition, + * remote ones could be on a network mounted filesystem. + * Favor local ones for these reasons. + */ + st = a->pack_local - b->pack_local; + if (st) + return -st; + + /* + * Younger packs tend to contain more recent objects, + * and more recent objects tend to get accessed more + * often. + */ + if (a->mtime < b->mtime) + return 1; + else if (a->mtime == b->mtime) + return 0; + return -1; +} + +static void rearrange_packed_git(void) +{ + struct packed_git **ary, *p; + int i, n; + + for (n = 0, p = packed_git; p; p = p->next) + n++; + if (n < 2) + return; + + /* prepare an array of packed_git for easier sorting */ + ary = xcalloc(n, sizeof(struct packed_git *)); + for (n = 0, p = packed_git; p; p = p->next) + ary[n++] = p; + + qsort(ary, n, sizeof(struct packed_git *), sort_pack); + + /* link them back again */ + for (i = 0; i < n - 1; i++) + ary[i]->next = ary[i + 1]; + ary[n - 1]->next = NULL; + packed_git = ary[0]; + + free(ary); +} + static int prepare_packed_git_run_once = 0; void prepare_packed_git(void) { @@ -837,6 +887,7 @@ void prepare_packed_git(void) prepare_packed_git_one(alt->base, 0); alt->name[-1] = '/'; } + rearrange_packed_git(); prepare_packed_git_run_once = 1; } @@ -1301,6 +1352,65 @@ static void *unpack_compressed_entry(struct packed_git *p, return buffer; } +#define MAX_DELTA_CACHE (256) + +static struct delta_base_cache_entry { + struct packed_git *p; + off_t base_offset; + unsigned long size; + void *data; + enum object_type type; +} delta_base_cache[MAX_DELTA_CACHE]; + +static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) +{ + unsigned long hash; + + hash = (unsigned long)p + (unsigned long)base_offset; + hash += (hash >> 8) + (hash >> 16); + return hash & 0xff; +} + +static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, + unsigned long *base_size, enum object_type *type, int keep_cache) +{ + void *ret; + unsigned long hash = pack_entry_hash(p, base_offset); + struct delta_base_cache_entry *ent = delta_base_cache + hash; + + ret = ent->data; + if (ret && ent->p == p && ent->base_offset == base_offset) + goto found_cache_entry; + return unpack_entry(p, base_offset, type, base_size); + +found_cache_entry: + if (!keep_cache) + ent->data = NULL; + else { + ret = xmalloc(ent->size + 1); + memcpy(ret, ent->data, ent->size); + ((char *)ret)[ent->size] = 0; + } + *type = ent->type; + *base_size = ent->size; + return ret; +} + +static void add_delta_base_cache(struct packed_git *p, off_t base_offset, + void *base, unsigned long base_size, enum object_type type) +{ + unsigned long hash = pack_entry_hash(p, base_offset); + struct delta_base_cache_entry *ent = delta_base_cache + hash; + + if (ent->data) + free(ent->data); + ent->p = p; + ent->base_offset = base_offset; + ent->type = type; + ent->data = base; + ent->size = base_size; +} + static void *unpack_delta_entry(struct packed_git *p, struct pack_window **w_curs, off_t curpos, @@ -1314,7 +1424,7 @@ static void *unpack_delta_entry(struct packed_git *p, off_t base_offset; base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset); - base = unpack_entry(p, base_offset, type, &base_size); + base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0); if (!base) die("failed to read delta base object" " at %"PRIuMAX" from %s", @@ -1327,7 +1437,7 @@ static void *unpack_delta_entry(struct packed_git *p, if (!result) die("failed to apply delta"); free(delta_data); - free(base); + add_delta_base_cache(p, base_offset, base, base_size, *type); return result; } @@ -1367,24 +1477,27 @@ uint32_t num_packed_objects(const struct packed_git *p) int nth_packed_object_sha1(const struct packed_git *p, uint32_t n, unsigned char* sha1) { - void *index = p->index_base + 256; + const unsigned char *index = p->index_data; + index += 4 * 256; if (num_packed_objects(p) <= n) return -1; - hashcpy(sha1, (unsigned char *) index + (24 * n) + 4); + hashcpy(sha1, index + 24 * n + 4); return 0; } off_t find_pack_entry_one(const unsigned char *sha1, struct packed_git *p) { - uint32_t *level1_ofs = p->index_base; + const uint32_t *level1_ofs = p->index_data; int hi = ntohl(level1_ofs[*sha1]); int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1])); - void *index = p->index_base + 256; + const unsigned char *index = p->index_data; + + index += 4 * 256; do { int mi = (lo + hi) / 2; - int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); + int cmp = hashcmp(index + 24 * mi + 4, sha1); if (!cmp) return ntohl(*((uint32_t *)((char *)index + (24 * mi)))); if (cmp > 0) @@ -1508,7 +1621,7 @@ static void *read_packed_sha1(const unsigned char *sha1, if (!find_pack_entry(sha1, &e, NULL)) return NULL; else - return unpack_entry(e.p, e.offset, type, size); + return cache_or_unpack_entry(e.p, e.offset, size, type, 1); } /* diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 5565c27033..9558bdb631 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -47,17 +47,6 @@ test_expect_success \ test ! -f .git/refs/heads/d/e/f && test ! -f .git/logs/refs/heads/d/e/f' -cat >expect <<EOF -0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 checkout: Created from master -EOF -test_expect_success \ - 'git checkout -b g/h/i -l should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git-checkout -b g/h/i -l master && - test -f .git/refs/heads/g/h/i && - test -f .git/logs/refs/heads/g/h/i && - diff expect .git/logs/refs/heads/g/h/i' - test_expect_success \ 'git branch j/k should work after branch j has been deleted' \ 'git-branch j && @@ -117,4 +106,64 @@ test_expect_failure \ ln -s real-u .git/logs/refs/heads/u && git-branch -m u v' +test_expect_success 'test tracking setup via --track' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my1 local/master && + test $(git-config branch.my1.remote) = local && + test $(git-config branch.my1.merge) = refs/heads/master' + +test_expect_success 'test tracking setup (non-wildcard, matching)' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/master:refs/remotes/local/master && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my4 local/master && + test $(git-config branch.my4.remote) = local && + test $(git-config branch.my4.merge) = refs/heads/master' + +test_expect_success 'test tracking setup (non-wildcard, not matching)' \ + 'git-config remote.local.url . && + git-config remote.local.fetch refs/heads/s:refs/remotes/local/s && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --track my5 local/master && + ! test $(git-config branch.my5.remote) = local && + ! test $(git-config branch.my5.merge) = refs/heads/master' + +test_expect_success 'test tracking setup via config' \ + 'git-config branch.autosetupmerge true && + git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch my3 local/master && + test $(git-config branch.my3.remote) = local && + test $(git-config branch.my3.merge) = refs/heads/master' + +test_expect_success 'test overriding tracking setup via --no-track' \ + 'git-config branch.autosetupmerge true && + git-config remote.local.url . && + git-config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git-show-ref -q refs/remotes/local/master || git-fetch local) && + git-branch --no-track my2 local/master && + git-config branch.autosetupmerge false && + ! test $(git-config branch.my2.remote) = local && + ! test $(git-config branch.my2.merge) = refs/heads/master' + +test_expect_success 'test local tracking setup' \ + 'git branch --track my6 s && + test $(git-config branch.my6.remote) = . && + test $(git-config branch.my6.merge) = refs/heads/s' + +# Keep this test last, as it changes the current branch +cat >expect <<EOF +0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +EOF +test_expect_success \ + 'git checkout -b g/h/i -l should create a branch and a log' \ + 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git-checkout -b g/h/i -l master && + test -f .git/refs/heads/g/h/i && + test -f .git/logs/refs/heads/g/h/i && + diff expect .git/logs/refs/heads/g/h/i' + test_done diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh new file mode 100755 index 0000000000..68731908be --- /dev/null +++ b/t/t4017-diff-retval.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description='Return value of diffs' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >a && + git add . && + git commit -m first && + echo 2 >b && + git add . && + git commit -a -m second +' + +test_expect_success 'git diff-tree HEAD^ HEAD' ' + git diff-tree --exit-code HEAD^ HEAD + test $? = 1 +' +test_expect_success 'git diff-tree HEAD^ HEAD -- a' ' + git diff-tree --exit-code HEAD^ HEAD -- a + test $? = 0 +' +test_expect_success 'git diff-tree HEAD^ HEAD -- b' ' + git diff-tree --exit-code HEAD^ HEAD -- b + test $? = 1 +' +test_expect_success 'echo HEAD | git diff-tree --stdin' ' + echo $(git rev-parse HEAD) | git diff-tree --exit-code --stdin + test $? = 1 +' +test_expect_success 'git diff-tree HEAD HEAD' ' + git diff-tree --exit-code HEAD HEAD + test $? = 0 +' +test_expect_success 'git diff-files' ' + git diff-files --exit-code + test $? = 0 +' +test_expect_success 'git diff-index --cached HEAD' ' + git diff-index --exit-code --cached HEAD + test $? = 0 +' +test_expect_success 'git diff-index --cached HEAD^' ' + git diff-index --exit-code --cached HEAD^ + test $? = 1 +' +test_expect_success 'git diff-index --cached HEAD^' ' + echo text >>b && + echo 3 >c && + git add . && { + git diff-index --exit-code --cached HEAD^ + test $? = 1 + } +' +test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' ' + git commit -m "text in b" && { + git diff-tree -p --exit-code -Stext HEAD^ HEAD -- b + test $? = 1 + } +' +test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' ' + git diff-tree -p --exit-code -Snot-found HEAD^ HEAD -- b + test $? = 0 +' +test_expect_success 'git diff-files' ' + echo 3 >>c && { + git diff-files --exit-code + test $? = 1 + } +' +test_expect_success 'git diff-index --cached HEAD' ' + git update-index c && { + git diff-index --exit-code --cached HEAD + test $? = 1 + } +' + +test_done diff --git a/t/t4017-quiet.sh b/t/t4017-quiet.sh new file mode 100755 index 0000000000..e747e84227 --- /dev/null +++ b/t/t4017-quiet.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +test_description='Return value of diffs' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >a && + git add . && + git commit -m first && + echo 2 >b && + git add . && + git commit -a -m second +' + +test_expect_success 'git diff-tree HEAD^ HEAD' ' + git diff-tree --quiet HEAD^ HEAD >cnt + test $? = 1 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-tree HEAD^ HEAD -- a' ' + git diff-tree --quiet HEAD^ HEAD -- a >cnt + test $? = 0 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-tree HEAD^ HEAD -- b' ' + git diff-tree --quiet HEAD^ HEAD -- b >cnt + test $? = 1 && test $(wc -l <cnt) = 0 +' +# this diff outputs one line: sha1 of the given head +test_expect_success 'echo HEAD | git diff-tree --stdin' ' + echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt + test $? = 1 && test $(wc -l <cnt) = 1 +' +test_expect_success 'git diff-tree HEAD HEAD' ' + git diff-tree --quiet HEAD HEAD >cnt + test $? = 0 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-files' ' + git diff-files --quiet >cnt + test $? = 0 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-index --cached HEAD' ' + git diff-index --quiet --cached HEAD >cnt + test $? = 0 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-index --cached HEAD^' ' + git diff-index --quiet --cached HEAD^ >cnt + test $? = 1 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-index --cached HEAD^' ' + echo text >>b && + echo 3 >c && + git add . && { + git diff-index --quiet --cached HEAD^ >cnt + test $? = 1 && test $(wc -l <cnt) = 0 + } +' +test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' ' + git commit -m "text in b" && { + git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt + test $? = 1 && test $(wc -l <cnt) = 0 + } +' +test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' ' + git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt + test $? = 0 && test $(wc -l <cnt) = 0 +' +test_expect_success 'git diff-files' ' + echo 3 >>c && { + git diff-files --quiet >cnt + test $? = 1 && test $(wc -l <cnt) = 0 + } +' +test_expect_success 'git diff-index --cached HEAD' ' + git update-index c && { + git diff-index --quiet --cached HEAD >cnt + test $? = 1 && test $(wc -l <cnt) = 0 + } +' + +test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 4d2b781a18..ca96918da2 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git-mailsplit -o. ../t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 6' + test `cat last` = 8' for mail in `echo 00*` do diff --git a/t/t5100/info0007 b/t/t5100/info0007 new file mode 100644 index 0000000000..49bb0fec85 --- /dev/null +++ b/t/t5100/info0007 @@ -0,0 +1,5 @@ +Author: A U Thor +Email: a.u.thor@example.com +Subject: another patch +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/info0008 b/t/t5100/info0008 new file mode 100644 index 0000000000..e8a2951383 --- /dev/null +++ b/t/t5100/info0008 @@ -0,0 +1,5 @@ +Author: Junio C Hamano +Email: junio@kernel.org +Subject: another patch +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/msg0007 b/t/t5100/msg0007 new file mode 100644 index 0000000000..71b23c0236 --- /dev/null +++ b/t/t5100/msg0007 @@ -0,0 +1,2 @@ +Here is an empty patch from A U Thor. + diff --git a/t/t5100/msg0008 b/t/t5100/msg0008 new file mode 100644 index 0000000000..a80ecb97ef --- /dev/null +++ b/t/t5100/msg0008 @@ -0,0 +1,4 @@ +>Here is an empty patch from A U Thor. + +Hey you forgot the patch! + diff --git a/t/t5100/patch0005 b/t/t5100/patch0005 index 7d24b24af8..e7d6f66608 100644 --- a/t/t5100/patch0005 +++ b/t/t5100/patch0005 @@ -61,7 +61,7 @@ diff --git a/git-cvsimport-script b/git-cvsimport-script push(@old,$fn); -- -David Kågedal +David KÃ¥gedal - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@vger.kernel.org diff --git a/t/t5100/patch0007 b/t/t5100/patch0007 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/t/t5100/patch0007 diff --git a/t/t5100/patch0008 b/t/t5100/patch0008 new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/t/t5100/patch0008 diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 86bfc27147..b80c981c16 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -386,3 +386,21 @@ index 9123cdc..918dcf8 100644 -- 1.4.0.g6f2b +From nobody Mon Sep 17 00:00:00 2001 +From: A U Thor <a.u.thor@example.com> +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: [PATCH] another patch + +Here is an empty patch from A U Thor. + +From nobody Mon Sep 17 00:00:00 2001 +From: Junio C Hamano <junio@kernel.org> +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: re: [PATCH] another patch + +From: A U Thor <a.u.thor@example.com> +Subject: [PATCH] another patch +>Here is an empty patch from A U Thor. + +Hey you forgot the patch! + diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index f511547455..eacb1e92c2 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -64,7 +64,7 @@ test_expect_success \ cd "$TRASH" test_expect_success \ - 'pack with delta' \ + 'pack with REF_DELTA' \ 'pwd && packname_2=$(git-pack-objects test-2 <obj-list)' @@ -72,7 +72,7 @@ rm -fr .git2 mkdir .git2 test_expect_success \ - 'unpack with delta' \ + 'unpack with REF_DELTA' \ 'GIT_OBJECT_DIRECTORY=.git2/objects && export GIT_OBJECT_DIRECTORY && git-init && @@ -82,7 +82,7 @@ test_expect_success \ unset GIT_OBJECT_DIRECTORY cd "$TRASH/.git2" test_expect_success \ - 'check unpack with delta' \ + 'check unpack with REF_DELTA' \ '(cd ../.git && find objects -type f -print) | while read path do @@ -93,6 +93,42 @@ test_expect_success \ done' cd "$TRASH" +test_expect_success \ + 'pack with OFS_DELTA' \ + 'pwd && + packname_3=$(git-pack-objects --delta-base-offset test-3 <obj-list)' + +rm -fr .git2 +mkdir .git2 + +test_expect_success \ + 'unpack with OFS_DELTA' \ + 'GIT_OBJECT_DIRECTORY=.git2/objects && + export GIT_OBJECT_DIRECTORY && + git-init && + git-unpack-objects -n <test-3-${packname_3}.pack && + git-unpack-objects <test-3-${packname_3}.pack' + +unset GIT_OBJECT_DIRECTORY +cd "$TRASH/.git2" +test_expect_success \ + 'check unpack with OFS_DELTA' \ + '(cd ../.git && find objects -type f -print) | + while read path + do + cmp $path ../.git/$path || { + echo $path differs. + return 1 + } + done' +cd "$TRASH" + +test_expect_success \ + 'compare delta flavors' \ + 'size_2=`stat -c "%s" test-2-${packname_2}.pack` && + size_3=`stat -c "%s" test-3-${packname_3}.pack` && + test $size_2 -gt $size_3' + rm -fr .git2 mkdir .git2 @@ -111,12 +147,11 @@ test_expect_success \ } >current && diff expect current' - test_expect_success \ - 'use packed deltified objects' \ + 'use packed deltified (REF_DELTA) objects' \ 'GIT_OBJECT_DIRECTORY=.git2/objects && export GIT_OBJECT_DIRECTORY && - rm -f .git2/objects/pack/test-?.idx && + rm .git2/objects/pack/test-* && cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && { git-diff-tree --root -p $commit && while read object @@ -127,11 +162,28 @@ test_expect_success \ } >current && diff expect current' +test_expect_success \ + 'use packed deltified (OFS_DELTA) objects' \ + 'GIT_OBJECT_DIRECTORY=.git2/objects && + export GIT_OBJECT_DIRECTORY && + rm .git2/objects/pack/test-* && + cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && { + git-diff-tree --root -p $commit && + while read object + do + t=`git-cat-file -t $object` && + git-cat-file $t $object || return 1 + done <obj-list + } >current && + diff expect current' + unset GIT_OBJECT_DIRECTORY test_expect_success \ 'verify pack' \ - 'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx' + 'git-verify-pack test-1-${packname_1}.idx \ + test-2-${packname_2}.idx \ + test-3-${packname_3}.idx' test_expect_success \ 'corrupt a pack and see if verify catches' \ @@ -194,6 +246,13 @@ test_expect_success \ git-index-pack test-3.pack && cmp test-3.idx test-2-${packname_2}.idx && + cp test-3-${packname_3}.pack test-3.pack && + git-index-pack -o tmp.idx test-3-${packname_3}.pack && + cmp tmp.idx test-3-${packname_3}.idx && + + git-index-pack test-3.pack && + cmp test-3.idx test-3-${packname_3}.idx && + :' test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 7eb37838bb..243212d3da 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -29,5 +29,29 @@ test_expect_success 'checking the results' ' diff file cloned/file ' +test_expect_success 'test . as a remote' ' + + git branch copy master && + git config branch.copy.remote . && + git config branch.copy.merge refs/heads/master && + echo updated >file && + git commit -a -m updated && + git checkout copy && + test `cat file` = file && + git pull && + test `cat file` = updated +' + +test_expect_success 'the default remote . should not break explicit pull' ' + git checkout -b second master^ && + echo modified >file && + git commit -a -m modified && + git checkout copy && + git reset --hard HEAD^ && + test `cat file` = file && + git pull . second && + test `cat file` = modified +' + test_done diff --git a/templates/hooks--update b/templates/hooks--update index 5b82b68e93..8f6c4fea24 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -210,7 +210,7 @@ case "$refname_type" in fi # If this tag succeeds another, then show which tag it replaces - prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//') + prevtag=$(git describe --abbrev=0 $newrev^ 2>/dev/null) if [ -n "$prevtag" ]; then echo " replaces $prevtag" fi @@ -26,14 +26,14 @@ #include "quote.h" /* Stolen from "imap-send.c". */ -static int git_vasprintf(char **strp, const char *fmt, va_list ap) +int nfvasprintf(char **strp, const char *fmt, va_list ap) { int len; char tmp[1024]; if ((len = vsnprintf(tmp, sizeof(tmp), fmt, ap)) < 0 || !(*strp = xmalloc(len + 1))) - return -1; + die("Fatal: Out of memory\n"); if (len >= (int)sizeof(tmp)) vsprintf(*strp, fmt, ap); else @@ -41,13 +41,15 @@ static int git_vasprintf(char **strp, const char *fmt, va_list ap) return len; } -/* Stolen from "imap-send.c". */ -int nfvasprintf(char **str, const char *fmt, va_list va) +int nfasprintf(char **str, const char *fmt, ...) { - int ret = git_vasprintf(str, fmt, va); - if (ret < 0) - die("Fatal: Out of memory\n"); - return ret; + int rc; + va_list args; + + va_start(args, fmt); + rc = nfvasprintf(str, fmt, args); + va_end(args); + return rc; } /* Get a trace file descriptor from GIT_TRACE env variable. */ diff --git a/tree-diff.c b/tree-diff.c index c8275823d0..3940962e79 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -5,9 +5,8 @@ #include "diff.h" #include "tree.h" -static char *malloc_base(const char *base, const char *path, int pathlen) +static char *malloc_base(const char *base, int baselen, const char *path, int pathlen) { - int baselen = strlen(base); char *newbase = xmalloc(baselen + pathlen + 2); memcpy(newbase, base, baselen); memcpy(newbase + baselen, path, pathlen); @@ -16,9 +15,9 @@ static char *malloc_base(const char *base, const char *path, int pathlen) } static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, - const char *base); + const char *base, int baselen); -static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) +static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt) { unsigned mode1, mode2; const char *path1, *path2; @@ -28,15 +27,15 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const sha1 = tree_entry_extract(t1, &path1, &mode1); sha2 = tree_entry_extract(t2, &path2, &mode2); - pathlen1 = strlen(path1); - pathlen2 = strlen(path2); + pathlen1 = tree_entry_len(path1, sha1); + pathlen2 = tree_entry_len(path2, sha2); cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2); if (cmp < 0) { - show_entry(opt, "-", t1, base); + show_entry(opt, "-", t1, base, baselen); return -1; } if (cmp > 0) { - show_entry(opt, "+", t2, base); + show_entry(opt, "+", t2, base, baselen); return 1; } if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) @@ -47,14 +46,14 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const * file, we need to consider it a remove and an add. */ if (S_ISDIR(mode1) != S_ISDIR(mode2)) { - show_entry(opt, "-", t1, base); - show_entry(opt, "+", t2, base); + show_entry(opt, "-", t1, base, baselen); + show_entry(opt, "+", t2, base, baselen); return 0; } if (opt->recursive && S_ISDIR(mode1)) { int retval; - char *newbase = malloc_base(base, path1, pathlen1); + char *newbase = malloc_base(base, baselen, path1, pathlen1); if (opt->tree_in_recursive) opt->change(opt, mode1, mode2, sha1, sha2, base, path1); @@ -67,20 +66,20 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const return 0; } -static int interesting(struct tree_desc *desc, const char *base, struct diff_options *opt) +static int interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt) { const char *path; + const unsigned char *sha1; unsigned mode; int i; - int baselen, pathlen; + int pathlen; if (!opt->nr_paths) return 1; - (void)tree_entry_extract(desc, &path, &mode); + sha1 = tree_entry_extract(desc, &path, &mode); - pathlen = strlen(path); - baselen = strlen(base); + pathlen = tree_entry_len(path, sha1); for (i=0; i < opt->nr_paths; i++) { const char *match = opt->paths[i]; @@ -121,18 +120,18 @@ static int interesting(struct tree_desc *desc, const char *base, struct diff_opt } /* A whole sub-tree went away or appeared */ -static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen) { while (desc->size) { - if (interesting(desc, base, opt)) - show_entry(opt, prefix, desc, base); + if (interesting(desc, base, baselen, opt)) + show_entry(opt, prefix, desc, base, baselen); update_tree_entry(desc); } } /* A file entry went away or appeared */ static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, - const char *base) + const char *base, int baselen) { unsigned mode; const char *path; @@ -140,7 +139,8 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree if (opt->recursive && S_ISDIR(mode)) { enum object_type type; - char *newbase = malloc_base(base, path, strlen(path)); + int pathlen = tree_entry_len(path, sha1); + char *newbase = malloc_base(base, baselen, path, pathlen); struct tree_desc inner; void *tree; @@ -149,7 +149,7 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree die("corrupt tree sha %s", sha1_to_hex(sha1)); inner.buf = tree; - show_tree(opt, prefix, &inner, newbase); + show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen); free(tree); free(newbase); @@ -160,26 +160,30 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { + int baselen = strlen(base); + while (t1->size | t2->size) { - if (opt->nr_paths && t1->size && !interesting(t1, base, opt)) { + if (opt->quiet && opt->has_changes) + break; + if (opt->nr_paths && t1->size && !interesting(t1, base, baselen, opt)) { update_tree_entry(t1); continue; } - if (opt->nr_paths && t2->size && !interesting(t2, base, opt)) { + if (opt->nr_paths && t2->size && !interesting(t2, base, baselen, opt)) { update_tree_entry(t2); continue; } if (!t1->size) { - show_entry(opt, "+", t2, base); + show_entry(opt, "+", t2, base, baselen); update_tree_entry(t2); continue; } if (!t2->size) { - show_entry(opt, "-", t1, base); + show_entry(opt, "-", t1, base, baselen); update_tree_entry(t1); continue; } - switch (compare_tree_entry(t1, t2, base, opt)) { + switch (compare_tree_entry(t1, t2, base, baselen, opt)) { case -1: update_tree_entry(t1); continue; diff --git a/tree-walk.c b/tree-walk.c index 70f899957e..a4a4e2a989 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -32,7 +32,7 @@ static void entry_clear(struct name_entry *a) static void entry_extract(struct tree_desc *t, struct name_entry *a) { a->sha1 = tree_entry_extract(t, &a->path, &a->mode); - a->pathlen = strlen(a->path); + a->pathlen = tree_entry_len(a->path, a->sha1); } void update_tree_entry(struct tree_desc *desc) @@ -169,7 +169,7 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char sha1 = tree_entry_extract(t, &entry, mode); update_tree_entry(t); - entrylen = strlen(entry); + entrylen = tree_entry_len(entry, sha1); if (entrylen > namelen) continue; cmp = memcmp(name, entry, entrylen); diff --git a/tree-walk.h b/tree-walk.h index e57befa4da..a0d7afd89b 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -13,6 +13,11 @@ struct name_entry { int pathlen; }; +static inline int tree_entry_len(const char *name, const unsigned char *sha1) +{ + return (char *)sha1 - (char *)name - 1; +} + void update_tree_entry(struct tree_desc *); const unsigned char *tree_entry_extract(struct tree_desc *, const char **, unsigned int *); |