diff options
45 files changed, 1937 insertions, 1227 deletions
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 4a7e67a4d2..1b482abecd 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -3,38 +3,54 @@ git-rebase(1) NAME ---- -git-rebase - Rebase local commits to new upstream head +git-rebase - Rebase local commits to a new head SYNOPSIS -------- 'git-rebase' [--onto <newbase>] <upstream> [<branch>] +'git-rebase' --continue + +'git-rebase' --abort + DESCRIPTION ----------- -git-rebase applies to <upstream> (or optionally to <newbase>) commits -from <branch> that do not appear in <upstream>. When <branch> is not -specified it defaults to the current branch (HEAD). +git-rebase replaces <branch> with a new branch of the same name. When +the --onto option is provided the new branch starts out with a HEAD equal +to <newbase>, otherwise it is equal to <upstream>. It then attempts to +create a new commit for each commit from the original <branch> that does +not exist in the <upstream> branch. -When git-rebase is complete, <branch> will be updated to point to the -newly created line of commit objects, so the previous line will not be -accessible unless there are other references to it already. +It is possible that a merge failure will prevent this process from being +completely automatic. You will have to resolve any such merge failure +and run `git rebase --continue`. If you can not resolve the merge +failure, running `git rebase --abort` will restore the original <branch> +and remove the working files found in the .dotest directory. + +Note that if <branch> is not specified on the command line, the currently +checked out branch is used. Assume the following history exists and the current branch is "topic": +------------ A---B---C topic / D---E---F---G master +------------ From this point, the result of either of the following commands: + git-rebase master git-rebase master topic would be: +------------ A'--B'--C' topic / D---E---F---G master +------------ While, starting from the same point, the result of either of the following commands: @@ -44,21 +60,33 @@ commands: would be: +------------ A'--B'--C' topic / D---E---F---G master +------------ In case of conflict, git-rebase will stop at the first problematic commit -and leave conflict markers in the tree. After resolving the conflict manually -and updating the index with the desired resolution, you can continue the -rebasing process with +and leave conflict markers in the tree. You can use git diff to locate +the markers (<<<<<<) and make edits to resolve the conflict. For each +file you edit, you need to tell git that the conflict has been resolved, +typically this would be done with + + + git update-index <filename> + + +After resolving the conflict manually and updating the index with the +desired resolution, you can continue the rebasing process with + + + git rebase --continue - git am --resolved --3way Alternatively, you can undo the git-rebase with - git reset --hard ORIG_HEAD - rm -r .dotest + + git rebase --abort OPTIONS ------- @@ -73,6 +101,28 @@ OPTIONS <branch>:: Working branch; defaults to HEAD. +--continue:: + Restart the rebasing process after having resolved a merge conflict. + +--abort:: + Restore the original branch and abort the rebase operation. + +NOTES +----- +When you rebase a branch, you are changing its history in a way that +will cause problems for anyone who already has a copy of the branch +in their repository and tries to pull updates from you. You should +understand the implications of using 'git rebase' on a repository that +you share. + +When the git rebase command is run, it will first execute a "pre-rebase" +hook if one exists. You can use this hook to do sanity checks and +reject the rebase if it isn't appropriate. Please see the template +pre-rebase hook script for an example. + +You must be in the top directory of your project to start (or continue) +a rebase. Upon completion, <branch> will be the current branch. + Author ------ Written by Junio C Hamano <junkio@cox.net> diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 71f96bdd10..566cfa1836 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -15,6 +15,7 @@ SYNOPSIS 'git-repo-config' [type] --get-all name [value_regex] 'git-repo-config' [type] --unset name [value_regex] 'git-repo-config' [type] --unset-all name [value_regex] +'git-repo-config' -l | --list DESCRIPTION ----------- @@ -64,6 +65,9 @@ OPTIONS --unset-all:: Remove all matching lines from .git/config. +-l, --list:: + List all variables set in .git/config. + EXAMPLE ------- diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index 379571eef0..a5b1a0dbab 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -19,7 +19,8 @@ OPTIONS -l:: Cause the logical variables to be listed. In addition, all the variables of the git configuration file .git/config are listed - as well. + as well. (However, the configuration variables listing functionality + is deprecated in favor of `git-repo-config -l`.) EXAMPLE -------- @@ -199,7 +199,7 @@ LIB_H = \ tree-walk.h log-tree.h DIFF_OBJS = \ - diff.o diffcore-break.o diffcore-order.o \ + diff.o diff-lib.o diffcore-break.o diffcore-order.o \ diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ diffcore-delta.o log-tree.o @@ -213,6 +213,9 @@ LIB_OBJS = \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ $(DIFF_OBJS) +BUILTIN_OBJS = \ + builtin-log.o builtin-help.o + GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -462,10 +465,12 @@ all: strip: $(PROGRAMS) git$X $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X -git$X: git.c common-cmds.h $(GITLIBS) +git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ - $(ALL_LDFLAGS) $(LIBS) + $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) + +builtin-help.o: common-cmds.h $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -565,17 +570,17 @@ init-db.o: init-db.c $(CC) -c $(ALL_CFLAGS) \ -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c -$(LIB_OBJS): $(LIB_H) +$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS) $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) - $(AR) rcs $@ $(LIB_OBJS) + rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o $(XDIFF_LIB): $(XDIFF_OBJS) - $(AR) rcs $@ $(XDIFF_OBJS) + rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS) doc: diff --git a/builtin-help.c b/builtin-help.c new file mode 100644 index 0000000000..7470faa566 --- /dev/null +++ b/builtin-help.c @@ -0,0 +1,242 @@ +/* + * builtin-help.c + * + * Builtin help-related commands (help, usage, version) + */ +#include <sys/ioctl.h> +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" +#include "common-cmds.h" + +static const char git_usage[] = + "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; + +/* most gui terms set COLUMNS (although some don't export it) */ +static int term_columns(void) +{ + char *col_string = getenv("COLUMNS"); + int n_cols = 0; + + if (col_string && (n_cols = atoi(col_string)) > 0) + return n_cols; + +#ifdef TIOCGWINSZ + { + struct winsize ws; + if (!ioctl(1, TIOCGWINSZ, &ws)) { + if (ws.ws_col) + return ws.ws_col; + } + } +#endif + + return 80; +} + +static void oom(void) +{ + fprintf(stderr, "git: out of memory\n"); + exit(1); +} + +static inline void mput_char(char c, unsigned int num) +{ + while(num--) + putchar(c); +} + +static struct cmdname { + size_t len; + char name[1]; +} **cmdname; +static int cmdname_alloc, cmdname_cnt; + +static void add_cmdname(const char *name, int len) +{ + struct cmdname *ent; + if (cmdname_alloc <= cmdname_cnt) { + cmdname_alloc = cmdname_alloc + 200; + cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); + if (!cmdname) + oom(); + } + ent = malloc(sizeof(*ent) + len); + if (!ent) + oom(); + ent->len = len; + memcpy(ent->name, name, len); + ent->name[len] = 0; + cmdname[cmdname_cnt++] = ent; +} + +static int cmdname_compare(const void *a_, const void *b_) +{ + struct cmdname *a = *(struct cmdname **)a_; + struct cmdname *b = *(struct cmdname **)b_; + return strcmp(a->name, b->name); +} + +static void pretty_print_string_list(struct cmdname **cmdname, int longest) +{ + int cols = 1, rows; + int space = longest + 1; /* min 1 SP between words */ + int max_cols = term_columns() - 1; /* don't print *on* the edge */ + int i, j; + + if (space < max_cols) + cols = max_cols / space; + rows = (cmdname_cnt + cols - 1) / cols; + + qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); + + for (i = 0; i < rows; i++) { + printf(" "); + + for (j = 0; j < cols; j++) { + int n = j * rows + i; + int size = space; + if (n >= cmdname_cnt) + break; + if (j == cols-1 || n + rows >= cmdname_cnt) + size = 1; + printf("%-*s", size, cmdname[n]->name); + } + putchar('\n'); + } +} + +static void list_commands(const char *exec_path, const char *pattern) +{ + unsigned int longest = 0; + char path[PATH_MAX]; + int dirlen; + DIR *dir = opendir(exec_path); + struct dirent *de; + + if (!dir) { + fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); + exit(1); + } + + dirlen = strlen(exec_path); + if (PATH_MAX - 20 < dirlen) { + fprintf(stderr, "git: insanely long exec-path '%s'\n", + exec_path); + exit(1); + } + + memcpy(path, exec_path, dirlen); + path[dirlen++] = '/'; + + while ((de = readdir(dir)) != NULL) { + struct stat st; + int entlen; + + if (strncmp(de->d_name, "git-", 4)) + continue; + strcpy(path+dirlen, de->d_name); + if (stat(path, &st) || /* stat, not lstat */ + !S_ISREG(st.st_mode) || + !(st.st_mode & S_IXUSR)) + continue; + + entlen = strlen(de->d_name); + if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) + entlen -= 4; + + if (longest < entlen) + longest = entlen; + + add_cmdname(de->d_name + 4, entlen-4); + } + closedir(dir); + + printf("git commands available in '%s'\n", exec_path); + printf("----------------------------"); + mput_char('-', strlen(exec_path)); + putchar('\n'); + pretty_print_string_list(cmdname, longest - 4); + putchar('\n'); +} + +static void list_common_cmds_help(void) +{ + int i, longest = 0; + + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + if (longest < strlen(common_cmds[i].name)) + longest = strlen(common_cmds[i].name); + } + + puts("The most commonly used git commands are:"); + for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { + printf(" %s", common_cmds[i].name); + mput_char(' ', longest - strlen(common_cmds[i].name) + 4); + puts(common_cmds[i].help); + } + puts("(use 'git help -a' to get a list of all installed git commands)"); +} + +void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) +{ + if (fmt) { + va_list ap; + + va_start(ap, fmt); + printf("git: "); + vprintf(fmt, ap); + va_end(ap); + putchar('\n'); + } + else + puts(git_usage); + + if (exec_path) { + putchar('\n'); + if (show_all) + list_commands(exec_path, "git-*"); + else + list_common_cmds_help(); + } + + exit(1); +} + +static void show_man_page(const char *git_cmd) +{ + const char *page; + + if (!strncmp(git_cmd, "git", 3)) + page = git_cmd; + else { + int page_len = strlen(git_cmd) + 4; + char *p = malloc(page_len + 1); + strcpy(p, "git-"); + strcpy(p + 4, git_cmd); + p[page_len] = 0; + page = p; + } + + execlp("man", "man", page, NULL); +} + +int cmd_version(int argc, const char **argv, char **envp) +{ + printf("git version %s\n", git_version_string); + return 0; +} + +int cmd_help(int argc, const char **argv, char **envp) +{ + const char *help_cmd = argv[1]; + if (!help_cmd) + cmd_usage(0, git_exec_path(), NULL); + else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) + cmd_usage(1, git_exec_path(), NULL); + else + show_man_page(help_cmd); + return 0; +} + + diff --git a/builtin-log.c b/builtin-log.c new file mode 100644 index 0000000000..69f2911cb4 --- /dev/null +++ b/builtin-log.c @@ -0,0 +1,69 @@ +/* + * Builtin "git log" and related commands (show, whatchanged) + * + * (C) Copyright 2006 Linus Torvalds + * 2006 Junio Hamano + */ +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "log-tree.h" + +static int cmd_log_wc(int argc, const char **argv, char **envp, + struct rev_info *rev) +{ + struct commit *commit; + + rev->abbrev = DEFAULT_ABBREV; + rev->commit_format = CMIT_FMT_DEFAULT; + rev->verbose_header = 1; + argc = setup_revisions(argc, argv, rev, "HEAD"); + + if (argc > 1) + die("unrecognized argument: %s", argv[1]); + + prepare_revision_walk(rev); + setup_pager(); + while ((commit = get_revision(rev)) != NULL) { + log_tree_commit(rev, commit); + free(commit->buffer); + commit->buffer = NULL; + } + return 0; +} + +int cmd_whatchanged(int argc, const char **argv, char **envp) +{ + struct rev_info rev; + + init_revisions(&rev); + rev.diff = 1; + rev.diffopt.recursive = 1; + return cmd_log_wc(argc, argv, envp, &rev); +} + +int cmd_show(int argc, const char **argv, char **envp) +{ + struct rev_info rev; + + init_revisions(&rev); + rev.diff = 1; + rev.diffopt.recursive = 1; + rev.combine_merges = 1; + rev.dense_combined_merges = 1; + rev.always_show_header = 1; + rev.ignore_merges = 0; + rev.no_walk = 1; + return cmd_log_wc(argc, argv, envp, &rev); +} + +int cmd_log(int argc, const char **argv, char **envp) +{ + struct rev_info rev; + + init_revisions(&rev); + rev.always_show_header = 1; + rev.diffopt.recursive = 1; + return cmd_log_wc(argc, argv, envp, &rev); +} diff --git a/builtin.h b/builtin.h new file mode 100644 index 0000000000..47408a0585 --- /dev/null +++ b/builtin.h @@ -0,0 +1,23 @@ +#ifndef BUILTIN_H +#define BUILTIN_H + +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +extern const char git_version_string[]; + +void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) +#ifdef __GNUC__ + __attribute__((__format__(__printf__, 3, 4), __noreturn__)) +#endif + ; + +extern int cmd_help(int argc, const char **argv, char **envp); +extern int cmd_version(int argc, const char **argv, char **envp); + +extern int cmd_whatchanged(int argc, const char **argv, char **envp); +extern int cmd_show(int argc, const char **argv, char **envp); +extern int cmd_log(int argc, const char **argv, char **envp); + +#endif @@ -135,6 +135,7 @@ extern const char *setup_git_directory(void); extern const char *prefix_path(const char *prefix, int len, const char *path); extern const char *prefix_filename(const char *prefix, int len, const char *path); extern void verify_filename(const char *prefix, const char *name); +extern void verify_non_filename(const char *prefix, const char *name); #define alloc_nr(x) (((x)+16)*3/2) diff --git a/combine-diff.c b/combine-diff.c index 9445e86c2f..ca36f5d5e7 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -5,6 +5,7 @@ #include "diffcore.h" #include "quote.h" #include "xdiff-interface.h" +#include "log-tree.h" static int uninteresting(struct diff_filepair *p) { @@ -584,10 +585,20 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, sline->p_lno[i] = sline->p_lno[j]; } +static void dump_quoted_path(const char *prefix, const char *path) +{ + fputs(prefix, stdout); + if (quote_c_style(path, NULL, NULL, 0)) + quote_c_style(path, NULL, stdout, 0); + else + printf("%s", path); + putchar('\n'); +} + static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, const char *header, - struct diff_options *opt) + int dense, struct rev_info *rev) { + struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ @@ -688,16 +699,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (show_hunks || mode_differs || working_tree_file) { const char *abb; - if (header) { - shown_header++; - printf("%s%c", header, opt->line_termination); - } - printf("diff --%s ", dense ? "cc" : "combined"); - if (quote_c_style(elem->path, NULL, NULL, 0)) - quote_c_style(elem->path, NULL, stdout, 0); - else - printf("%s", elem->path); - putchar('\n'); + if (rev->loginfo) + show_log(rev, rev->loginfo, "\n"); + dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path); printf("index "); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, @@ -728,6 +732,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } putchar('\n'); } + dump_quoted_path("--- a/", elem->path); + dump_quoted_path("+++ b/", elem->path); dump_sline(sline, cnt, num_parent); } free(result); @@ -749,8 +755,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, #define COLONS "::::::::::::::::::::::::::::::::" -static void show_raw_diff(struct combine_diff_path *p, int num_parent, const char *header, struct diff_options *opt) +static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev) { + struct diff_options *opt = &rev->diffopt; int i, offset; const char *prefix; int line_termination, inter_name_termination; @@ -760,8 +767,8 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha if (!line_termination) inter_name_termination = 0; - if (header) - printf("%s%c", header, line_termination); + if (rev->loginfo) + show_log(rev, rev->loginfo, "\n"); if (opt->output_format == DIFF_FORMAT_RAW) { offset = strlen(COLONS) - num_parent; @@ -802,40 +809,44 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha } } -int show_combined_diff(struct combine_diff_path *p, +void show_combined_diff(struct combine_diff_path *p, int num_parent, int dense, - const char *header, - struct diff_options *opt) + struct rev_info *rev) { + struct diff_options *opt = &rev->diffopt; if (!p->len) - return 0; + return; switch (opt->output_format) { case DIFF_FORMAT_RAW: case DIFF_FORMAT_NAME_STATUS: case DIFF_FORMAT_NAME: - show_raw_diff(p, num_parent, header, opt); - return 1; - - default: + show_raw_diff(p, num_parent, rev); + return; case DIFF_FORMAT_PATCH: - return show_patch_diff(p, num_parent, dense, header, opt); + show_patch_diff(p, num_parent, dense, rev); + return; + default: + return; } } -const char *diff_tree_combined_merge(const unsigned char *sha1, - const char *header, int dense, - struct diff_options *opt) +void diff_tree_combined_merge(const unsigned char *sha1, + int dense, struct rev_info *rev) { + struct diff_options *opt = &rev->diffopt; struct commit *commit = lookup_commit(sha1); struct diff_options diffopts; struct commit_list *parents; struct combine_diff_path *p, *paths = NULL; int num_parent, i, num_paths; + int do_diffstat; + do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT || + opt->with_stat); diffopts = *opt; - diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; diffopts.with_raw = 0; + diffopts.with_stat = 0; diffopts.recursive = 1; /* count parents */ @@ -849,11 +860,24 @@ const char *diff_tree_combined_merge(const unsigned char *sha1, parents; parents = parents->next, i++) { struct commit *parent = parents->item; + /* show stat against the first parent even + * when doing combined diff. + */ + if (i == 0 && do_diffstat) + diffopts.output_format = DIFF_FORMAT_DIFFSTAT; + else + diffopts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_tree_sha1(parent->object.sha1, commit->object.sha1, "", &diffopts); diffcore_std(&diffopts); paths = intersect_paths(paths, i, num_parent); + + if (do_diffstat && rev->loginfo) + show_log(rev, rev->loginfo, + opt->with_stat ? "---\n" : "\n"); diff_flush(&diffopts); + if (opt->with_stat) + putchar('\n'); } /* find out surviving paths */ @@ -866,17 +890,13 @@ const char *diff_tree_combined_merge(const unsigned char *sha1, int saved_format = opt->output_format; opt->output_format = DIFF_FORMAT_RAW; for (p = paths; p; p = p->next) { - if (show_combined_diff(p, num_parent, dense, - header, opt)) - header = NULL; + show_combined_diff(p, num_parent, dense, rev); } opt->output_format = saved_format; putchar(opt->line_termination); } for (p = paths; p; p = p->next) { - if (show_combined_diff(p, num_parent, dense, - header, opt)) - header = NULL; + show_combined_diff(p, num_parent, dense, rev); } } @@ -886,5 +906,4 @@ const char *diff_tree_combined_merge(const unsigned char *sha1, paths = paths->next; free(tmp); } - return header; } diff --git a/commit-tree.c b/commit-tree.c index 2595850970..bad72e89e8 100644 --- a/commit-tree.c +++ b/commit-tree.c @@ -91,7 +91,7 @@ int main(int argc, char **argv) git_config(git_default_config); - if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0) + if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0) usage(commit_tree_usage); check_valid(tree_sha1, tree_type); @@ -45,6 +45,8 @@ enum cmit_fmt { CMIT_FMT_FULL, CMIT_FMT_FULLER, CMIT_FMT_ONELINE, + + CMIT_FMT_UNSPECIFIED, }; extern enum cmit_fmt get_commit_format(const char *arg); @@ -60,6 +60,12 @@ static char *parse_value(void) space = 1; continue; } + if (!quote) { + if (c == ';' || c == '#') { + comment = 1; + continue; + } + } if (space) { if (len) value[len++] = ' '; @@ -93,12 +99,6 @@ static char *parse_value(void) quote = 1-quote; continue; } - if (!quote) { - if (c == ';' || c == '#') { - comment = 1; - continue; - } - } value[len++] = c; } } diff --git a/contrib/colordiff/README b/contrib/colordiff/README new file mode 100644 index 0000000000..2678fdf9c2 --- /dev/null +++ b/contrib/colordiff/README @@ -0,0 +1,2 @@ +This is "colordiff" (http://colordiff.sourceforge.net/) by Dave +Ewart <davee@sungate.co.uk>, modified specifically for git. diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl new file mode 100755 index 0000000000..5789cfb265 --- /dev/null +++ b/contrib/colordiff/colordiff.perl @@ -0,0 +1,196 @@ +#!/usr/bin/perl -w +# +# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $ + +######################################################################## +# # +# ColorDiff - a wrapper/replacment for 'diff' producing # +# colourful output # +# # +# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk) # +# # +######################################################################## +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the Free Software # +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # +# # +######################################################################## + +use strict; +use Getopt::Long qw(:config pass_through); +use IPC::Open2; + +my $app_name = 'colordiff'; +my $version = '1.0.4'; +my $author = 'Dave Ewart'; +my $author_email = 'davee@sungate.co.uk'; +my $app_www = 'http://colordiff.sourceforge.net/'; +my $copyright = '(C)2002-2004'; +my $show_banner = 1; + +# ANSI sequences for colours +my %colour; +$colour{white} = "\033[1;37m"; +$colour{yellow} = "\033[1;33m"; +$colour{green} = "\033[1;32m"; +$colour{blue} = "\033[1;34m"; +$colour{cyan} = "\033[1;36m"; +$colour{red} = "\033[1;31m"; +$colour{magenta} = "\033[1;35m"; +$colour{black} = "\033[1;30m"; +$colour{darkwhite} = "\033[0;37m"; +$colour{darkyellow} = "\033[0;33m"; +$colour{darkgreen} = "\033[0;32m"; +$colour{darkblue} = "\033[0;34m"; +$colour{darkcyan} = "\033[0;36m"; +$colour{darkred} = "\033[0;31m"; +$colour{darkmagenta} = "\033[0;35m"; +$colour{darkblack} = "\033[0;30m"; +$colour{OFF} = "\033[0;0m"; + +# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist +my $plain_text = $colour{OFF}; +my $file_old = $colour{red}; +my $file_new = $colour{blue}; +my $diff_stuff = $colour{magenta}; + +# Locations for personal and system-wide colour configurations +my $HOME = $ENV{HOME}; +my $etcdir = '/etc'; + +my ($setting, $value); +my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc"); +my $config_file; + +foreach $config_file (@config_files) { + if (open(COLORDIFFRC, "<$config_file")) { + while (<COLORDIFFRC>) { + chop; + next if (/^#/ || /^$/); + s/\s+//g; + ($setting, $value) = split ('='); + if ($setting eq 'banner') { + if ($value eq 'no') { + $show_banner = 0; + } + next; + } + if (!defined $colour{$value}) { + print "Invalid colour specification ($value) in $config_file\n"; + next; + } + if ($setting eq 'plain') { + $plain_text = $colour{$value}; + } + elsif ($setting eq 'oldtext') { + $file_old = $colour{$value}; + } + elsif ($setting eq 'newtext') { + $file_new = $colour{$value}; + } + elsif ($setting eq 'diffstuff') { + $diff_stuff = $colour{$value}; + } + else { + print "Unknown option in $etcdir/colordiffrc: $setting\n"; + } + } + close COLORDIFFRC; + } +} + +# colordiff specfic options here. Need to pre-declare if using variables +GetOptions( + "no-banner" => sub { $show_banner = 0 }, + "plain-text=s" => \&set_color, + "file-old=s" => \&set_color, + "file-new=s" => \&set_color, + "diff-stuff=s" => \&set_color +); + +if ($show_banner == 1) { + print STDERR "$app_name $version ($app_www)\n"; + print STDERR "$copyright $author, $author_email\n\n"; +} + +if (defined $ARGV[0]) { + # More reliable way of pulling in arguments + open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV); +} +else { + *INPUTSTREAM = \*STDIN; +} + +my $record; +my $nrecs = 0; +my $inside_file_old = 1; +my $nparents = undef; + +while (<INPUTSTREAM>) { + $nrecs++; + if (/^(\@\@+) -[-+0-9, ]+ \1/) { + print "$diff_stuff"; + $nparents = length($1) - 1; + } + elsif (/^diff -/ || /^index / || + /^old mode / || /^new mode / || + /^deleted file mode / || /^new file mode / || + /^similarity index / || /^dissimilarity index / || + /^copy from / || /^copy to / || + /^rename from / || /^rename to /) { + $nparents = undef; + print "$diff_stuff"; + } + elsif (defined $nparents) { + if ($nparents == 1) { + if (/^\+/) { + print $file_new; + } + elsif (/^-/) { + print $file_old; + } + else { + print $plain_text; + } + } + elsif (/^ {$nparents}/) { + print "$plain_text"; + } + elsif (/^[+ ]{$nparents}/) { + print "$file_new"; + } + elsif (/^[- ]{$nparents}/) { + print "$file_old"; + } + else { + print $plain_text; + } + } + elsif (/^--- / || /^\+\+\+ /) { + print $diff_stuff; + } + else { + print "$plain_text"; + } + s/$/$colour{OFF}/; + print "$_"; +} +close INPUTSTREAM; + +sub set_color { + my ($type, $color) = @_; + + $type =~ s/-/_/; + eval "\$$type = \$colour{$color}"; +} diff --git a/diff-files.c b/diff-files.c index 3e7f5f105b..b9d193d506 100644 --- a/diff-files.c +++ b/diff-files.c @@ -5,209 +5,50 @@ */ #include "cache.h" #include "diff.h" +#include "commit.h" +#include "revision.h" static const char diff_files_usage[] = "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; -static struct diff_options diff_options; -static int silent = 0; -static int diff_unmerged_stage = 2; -static int combine_merges = 0; -static int dense_combined_merges = 0; - -static void show_unmerge(const char *path) -{ - diff_unmerge(&diff_options, path); -} - -static void show_file(int pfx, struct cache_entry *ce) -{ - diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode), - ce->sha1, ce->name, NULL); -} - -static void show_modified(int oldmode, int mode, - const unsigned char *old_sha1, const unsigned char *sha1, - char *path) -{ - diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL); -} - int main(int argc, const char **argv) { - const char **pathspec; - const char *prefix = setup_git_directory(); - int entries, i; + struct rev_info rev; + int silent = 0; git_config(git_diff_config); - diff_setup(&diff_options); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "--")) { - argv++; - argc--; - break; - } - if (!strcmp(argv[1], "-0")) - diff_unmerged_stage = 0; - else if (!strcmp(argv[1], "-1")) - diff_unmerged_stage = 1; - else if (!strcmp(argv[1], "-2")) - diff_unmerged_stage = 2; - else if (!strcmp(argv[1], "-3")) - diff_unmerged_stage = 3; - else if (!strcmp(argv[1], "--base")) - diff_unmerged_stage = 1; + if (!strcmp(argv[1], "--base")) + rev.max_count = 1; else if (!strcmp(argv[1], "--ours")) - diff_unmerged_stage = 2; + rev.max_count = 2; else if (!strcmp(argv[1], "--theirs")) - diff_unmerged_stage = 3; + rev.max_count = 3; else if (!strcmp(argv[1], "-q")) silent = 1; - else if (!strcmp(argv[1], "-r")) - ; /* no-op */ - else if (!strcmp(argv[1], "-s")) - ; /* no-op */ - else if (!strcmp(argv[1], "-c")) - combine_merges = 1; - else if (!strcmp(argv[1], "--cc")) - dense_combined_merges = combine_merges = 1; - else { - int diff_opt_cnt; - diff_opt_cnt = diff_opt_parse(&diff_options, - argv+1, argc-1); - if (diff_opt_cnt < 0) - usage(diff_files_usage); - else if (diff_opt_cnt) { - argv += diff_opt_cnt; - argc -= diff_opt_cnt; - continue; - } - else - usage(diff_files_usage); - } + else + usage(diff_files_usage); argv++; argc--; } - if (dense_combined_merges) - diff_options.output_format = DIFF_FORMAT_PATCH; - - /* Find the directory, and set up the pathspec */ - pathspec = get_pathspec(prefix, argv + 1); - entries = read_cache(); - - if (diff_setup_done(&diff_options) < 0) + /* + * Make sure there are NO revision (i.e. pending object) parameter, + * rev.max_count is reasonable (0 <= n <= 3), + * there is no other revision filtering parameters. + */ + if (rev.pending_objects || + rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - - /* At this point, if argc == 1, then we are doing everything. - * Otherwise argv[1] .. argv[argc-1] have the explicit paths. + /* + * Backward compatibility wart - "diff-files -s" used to + * defeat the common diff option "-s" which asked for + * DIFF_FORMAT_NO_OUTPUT. */ - if (entries < 0) { - perror("read_cache"); - exit(1); - } - - for (i = 0; i < entries; i++) { - struct stat st; - unsigned int oldmode, newmode; - struct cache_entry *ce = active_cache[i]; - int changed; - - if (!ce_path_match(ce, pathspec)) - continue; - - if (ce_stage(ce)) { - struct { - struct combine_diff_path p; - struct combine_diff_parent filler[5]; - } combine; - int num_compare_stages = 0; - - combine.p.next = NULL; - combine.p.len = ce_namelen(ce); - combine.p.path = xmalloc(combine.p.len + 1); - memcpy(combine.p.path, ce->name, combine.p.len); - combine.p.path[combine.p.len] = 0; - combine.p.mode = 0; - memset(combine.p.sha1, 0, 20); - memset(&combine.p.parent[0], 0, - sizeof(combine.filler)); - - while (i < entries) { - struct cache_entry *nce = active_cache[i]; - int stage; - - if (strcmp(ce->name, nce->name)) - break; - - /* Stage #2 (ours) is the first parent, - * stage #3 (theirs) is the second. - */ - stage = ce_stage(nce); - if (2 <= stage) { - int mode = ntohl(nce->ce_mode); - num_compare_stages++; - memcpy(combine.p.parent[stage-2].sha1, - nce->sha1, 20); - combine.p.parent[stage-2].mode = - canon_mode(mode); - combine.p.parent[stage-2].status = - DIFF_STATUS_MODIFIED; - } - - /* diff against the proper unmerged stage */ - if (stage == diff_unmerged_stage) - ce = nce; - i++; - } - /* - * Compensate for loop update - */ - i--; - - if (combine_merges && num_compare_stages == 2) { - show_combined_diff(&combine.p, 2, - dense_combined_merges, - NULL, - &diff_options); - free(combine.p.path); - continue; - } - free(combine.p.path); - - /* - * Show the diff for the 'ce' if we found the one - * from the desired stage. - */ - show_unmerge(ce->name); - if (ce_stage(ce) != diff_unmerged_stage) - continue; - } - - if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT && errno != ENOTDIR) { - perror(ce->name); - continue; - } - if (silent) - continue; - show_file('-', ce); - continue; - } - changed = ce_match_stat(ce, &st, 0); - if (!changed && !diff_options.find_copies_harder) - continue; - oldmode = ntohl(ce->ce_mode); - - newmode = canon_mode(st.st_mode); - if (!trust_executable_bit && - S_ISREG(newmode) && S_ISREG(oldmode) && - ((newmode ^ oldmode) == 0111)) - newmode = oldmode; - show_modified(oldmode, newmode, - ce->sha1, (changed ? null_sha1 : ce->sha1), - ce->name); - } - diffcore_std(&diff_options); - diff_flush(&diff_options); - return 0; + if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) + rev.diffopt.output_format = DIFF_FORMAT_RAW; + return run_diff_files(&rev, silent); } diff --git a/diff-index.c b/diff-index.c index e376d65f80..8c9f60173b 100644 --- a/diff-index.c +++ b/diff-index.c @@ -1,166 +1,7 @@ #include "cache.h" -#include "tree.h" #include "diff.h" - -static int cached_only = 0; -static int match_nonexisting = 0; -static struct diff_options diff_options; - -/* A file entry went away or appeared */ -static void show_file(const char *prefix, - struct cache_entry *ce, - unsigned char *sha1, unsigned int mode) -{ - diff_addremove(&diff_options, prefix[0], ntohl(mode), - sha1, ce->name, NULL); -} - -static int get_stat_data(struct cache_entry *ce, - unsigned char ** sha1p, unsigned int *modep) -{ - unsigned char *sha1 = ce->sha1; - unsigned int mode = ce->ce_mode; - - if (!cached_only) { - static unsigned char no_sha1[20]; - int changed; - struct stat st; - if (lstat(ce->name, &st) < 0) { - if (errno == ENOENT && match_nonexisting) { - *sha1p = sha1; - *modep = mode; - return 0; - } - return -1; - } - changed = ce_match_stat(ce, &st, 0); - if (changed) { - mode = create_ce_mode(st.st_mode); - if (!trust_executable_bit && S_ISREG(st.st_mode)) - mode = ce->ce_mode; - sha1 = no_sha1; - } - } - - *sha1p = sha1; - *modep = mode; - return 0; -} - -static void show_new_file(struct cache_entry *new) -{ - unsigned char *sha1; - unsigned int mode; - - /* New file in the index: it might actually be different in - * the working copy. - */ - if (get_stat_data(new, &sha1, &mode) < 0) - return; - - show_file("+", new, sha1, mode); -} - -static int show_modified(struct cache_entry *old, - struct cache_entry *new, - int report_missing) -{ - unsigned int mode, oldmode; - unsigned char *sha1; - - if (get_stat_data(new, &sha1, &mode) < 0) { - if (report_missing) - show_file("-", old, old->sha1, old->ce_mode); - return -1; - } - - oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && - !diff_options.find_copies_harder) - return 0; - - mode = ntohl(mode); - oldmode = ntohl(oldmode); - - diff_change(&diff_options, oldmode, mode, - old->sha1, sha1, old->name, NULL); - return 0; -} - -static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec) -{ - while (entries) { - struct cache_entry *ce = *ac; - int same = (entries > 1) && ce_same_name(ce, ac[1]); - - if (!ce_path_match(ce, pathspec)) - goto skip_entry; - - switch (ce_stage(ce)) { - case 0: - /* No stage 1 entry? That means it's a new file */ - if (!same) { - show_new_file(ce); - break; - } - /* Show difference between old and new */ - show_modified(ac[1], ce, 1); - break; - case 1: - /* No stage 3 (merge) entry? That means it's been deleted */ - if (!same) { - show_file("-", ce, ce->sha1, ce->ce_mode); - break; - } - /* We come here with ce pointing at stage 1 - * (original tree) and ac[1] pointing at stage - * 3 (unmerged). show-modified with - * report-missing set to false does not say the - * file is deleted but reports true if work - * tree does not have it, in which case we - * fall through to report the unmerged state. - * Otherwise, we show the differences between - * the original tree and the work tree. - */ - if (!cached_only && !show_modified(ce, ac[1], 0)) - break; - /* fallthru */ - case 3: - diff_unmerge(&diff_options, ce->name); - break; - - default: - die("impossible cache entry stage"); - } - -skip_entry: - /* - * Ignore all the different stages for this file, - * we've handled the relevant cases now. - */ - do { - ac++; - entries--; - } while (entries && ce_same_name(ce, ac[0])); - } - return 0; -} - -/* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= htons(CE_STAGEMASK); - } -} +#include "commit.h" +#include "revision.h" static const char diff_cache_usage[] = "git-diff-index [-m] [--cached] " @@ -169,85 +10,29 @@ COMMON_DIFF_OPTIONS_HELP; int main(int argc, const char **argv) { - const char *tree_name = NULL; - unsigned char sha1[20]; - const char *prefix = setup_git_directory(); - const char **pathspec = NULL; - struct tree *tree; - int ret; - int allow_options = 1; + struct rev_info rev; + int cached = 0; int i; git_config(git_diff_config); - diff_setup(&diff_options); + init_revisions(&rev); + rev.abbrev = 0; + + argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { const char *arg = argv[i]; - int diff_opt_cnt; - - if (!allow_options || *arg != '-') { - if (tree_name) - break; - tree_name = arg; - continue; - } - if (!strcmp(arg, "--")) { - allow_options = 0; - continue; - } - if (!strcmp(arg, "-r")) { - /* We accept the -r flag just to look like git-diff-tree */ - continue; - } - if (!strcmp(arg, "--cc")) - /* - * I _think_ "diff-index --cached HEAD" with an - * unmerged index could show something else - * later, but pretend --cc is the same as -p for - * now. "git diff" uses --cc by default. - */ - argv[i] = arg = "-p"; - diff_opt_cnt = diff_opt_parse(&diff_options, argv + i, - argc - i); - if (diff_opt_cnt < 0) + if (!strcmp(arg, "--cached")) + cached = 1; + else usage(diff_cache_usage); - else if (diff_opt_cnt) { - i += diff_opt_cnt - 1; - continue; - } - - if (!strcmp(arg, "-m")) { - match_nonexisting = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - cached_only = 1; - continue; - } - usage(diff_cache_usage); } - - pathspec = get_pathspec(prefix, argv + i); - - if (diff_setup_done(&diff_options) < 0) - usage(diff_cache_usage); - - if (!tree_name || get_sha1(tree_name, sha1)) + /* + * Make sure there is one revision (i.e. pending object), + * and there is no revision filtering parameters. + */ + if (!rev.pending_objects || rev.pending_objects->next || + rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - - read_cache(); - - mark_merge_entries(); - - tree = parse_tree_indirect(sha1); - if (!tree) - die("bad tree object %s", tree_name); - if (read_tree(tree, 1, pathspec)) - die("unable to read tree object %s", tree_name); - - ret = diff_cache(active_cache, active_nr, pathspec); - - diffcore_std(&diff_options); - diff_flush(&diff_options); - return ret; + return run_diff_index(&rev, cached); } diff --git a/diff-lib.c b/diff-lib.c new file mode 100644 index 0000000000..2183b41b03 --- /dev/null +++ b/diff-lib.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005 Junio C Hamano + */ +#include "cache.h" +#include "quote.h" +#include "commit.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" + +/* + * diff-files + */ + +int run_diff_files(struct rev_info *revs, int silent_on_removed) +{ + int entries, i; + int diff_unmerged_stage = revs->max_count; + + if (diff_unmerged_stage < 0) + diff_unmerged_stage = 2; + entries = read_cache(); + if (entries < 0) { + perror("read_cache"); + return -1; + } + for (i = 0; i < entries; i++) { + struct stat st; + unsigned int oldmode, newmode; + struct cache_entry *ce = active_cache[i]; + int changed; + + if (!ce_path_match(ce, revs->prune_data)) + continue; + + if (ce_stage(ce)) { + struct { + struct combine_diff_path p; + struct combine_diff_parent filler[5]; + } combine; + int num_compare_stages = 0; + + combine.p.next = NULL; + combine.p.len = ce_namelen(ce); + combine.p.path = xmalloc(combine.p.len + 1); + memcpy(combine.p.path, ce->name, combine.p.len); + combine.p.path[combine.p.len] = 0; + combine.p.mode = 0; + memset(combine.p.sha1, 0, 20); + memset(&combine.p.parent[0], 0, + sizeof(combine.filler)); + + while (i < entries) { + struct cache_entry *nce = active_cache[i]; + int stage; + + if (strcmp(ce->name, nce->name)) + break; + + /* Stage #2 (ours) is the first parent, + * stage #3 (theirs) is the second. + */ + stage = ce_stage(nce); + if (2 <= stage) { + int mode = ntohl(nce->ce_mode); + num_compare_stages++; + memcpy(combine.p.parent[stage-2].sha1, + nce->sha1, 20); + combine.p.parent[stage-2].mode = + canon_mode(mode); + combine.p.parent[stage-2].status = + DIFF_STATUS_MODIFIED; + } + + /* diff against the proper unmerged stage */ + if (stage == diff_unmerged_stage) + ce = nce; + i++; + } + /* + * Compensate for loop update + */ + i--; + + if (revs->combine_merges && num_compare_stages == 2) { + show_combined_diff(&combine.p, 2, + revs->dense_combined_merges, + revs); + free(combine.p.path); + continue; + } + free(combine.p.path); + + /* + * Show the diff for the 'ce' if we found the one + * from the desired stage. + */ + diff_unmerge(&revs->diffopt, ce->name); + if (ce_stage(ce) != diff_unmerged_stage) + continue; + } + + if (lstat(ce->name, &st) < 0) { + if (errno != ENOENT && errno != ENOTDIR) { + perror(ce->name); + continue; + } + if (silent_on_removed) + continue; + diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode), + ce->sha1, ce->name, NULL); + continue; + } + changed = ce_match_stat(ce, &st, 0); + if (!changed && !revs->diffopt.find_copies_harder) + continue; + oldmode = ntohl(ce->ce_mode); + + newmode = canon_mode(st.st_mode); + if (!trust_executable_bit && + S_ISREG(newmode) && S_ISREG(oldmode) && + ((newmode ^ oldmode) == 0111)) + newmode = oldmode; + diff_change(&revs->diffopt, oldmode, newmode, + ce->sha1, (changed ? null_sha1 : ce->sha1), + ce->name, NULL); + + } + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); + return 0; +} + +/* + * diff-index + */ + +/* A file entry went away or appeared */ +static void diff_index_show_file(struct rev_info *revs, + const char *prefix, + struct cache_entry *ce, + unsigned char *sha1, unsigned int mode) +{ + diff_addremove(&revs->diffopt, prefix[0], ntohl(mode), + sha1, ce->name, NULL); +} + +static int get_stat_data(struct cache_entry *ce, + unsigned char **sha1p, + unsigned int *modep, + int cached, int match_missing) +{ + unsigned char *sha1 = ce->sha1; + unsigned int mode = ce->ce_mode; + + if (!cached) { + static unsigned char no_sha1[20]; + int changed; + struct stat st; + if (lstat(ce->name, &st) < 0) { + if (errno == ENOENT && match_missing) { + *sha1p = sha1; + *modep = mode; + return 0; + } + return -1; + } + changed = ce_match_stat(ce, &st, 0); + if (changed) { + mode = create_ce_mode(st.st_mode); + if (!trust_executable_bit && S_ISREG(st.st_mode)) + mode = ce->ce_mode; + sha1 = no_sha1; + } + } + + *sha1p = sha1; + *modep = mode; + return 0; +} + +static void show_new_file(struct rev_info *revs, + struct cache_entry *new, + int cached, int match_missing) +{ + unsigned char *sha1; + unsigned int mode; + + /* New file in the index: it might actually be different in + * the working copy. + */ + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) + return; + + diff_index_show_file(revs, "+", new, sha1, mode); +} + +static int show_modified(struct rev_info *revs, + struct cache_entry *old, + struct cache_entry *new, + int report_missing, + int cached, int match_missing) +{ + unsigned int mode, oldmode; + unsigned char *sha1; + + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { + if (report_missing) + diff_index_show_file(revs, "-", old, + old->sha1, old->ce_mode); + return -1; + } + + oldmode = old->ce_mode; + if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + !revs->diffopt.find_copies_harder) + return 0; + + mode = ntohl(mode); + oldmode = ntohl(oldmode); + + diff_change(&revs->diffopt, oldmode, mode, + old->sha1, sha1, old->name, NULL); + return 0; +} + +static int diff_cache(struct rev_info *revs, + struct cache_entry **ac, int entries, + const char **pathspec, + int cached, int match_missing) +{ + while (entries) { + struct cache_entry *ce = *ac; + int same = (entries > 1) && ce_same_name(ce, ac[1]); + + if (!ce_path_match(ce, pathspec)) + goto skip_entry; + + switch (ce_stage(ce)) { + case 0: + /* No stage 1 entry? That means it's a new file */ + if (!same) { + show_new_file(revs, ce, cached, match_missing); + break; + } + /* Show difference between old and new */ + show_modified(revs,ac[1], ce, 1, + cached, match_missing); + break; + case 1: + /* No stage 3 (merge) entry? + * That means it's been deleted. + */ + if (!same) { + diff_index_show_file(revs, "-", ce, + ce->sha1, ce->ce_mode); + break; + } + /* We come here with ce pointing at stage 1 + * (original tree) and ac[1] pointing at stage + * 3 (unmerged). show-modified with + * report-missing set to false does not say the + * file is deleted but reports true if work + * tree does not have it, in which case we + * fall through to report the unmerged state. + * Otherwise, we show the differences between + * the original tree and the work tree. + */ + if (!cached && + !show_modified(revs, ce, ac[1], 0, + cached, match_missing)) + break; + /* fallthru */ + case 3: + diff_unmerge(&revs->diffopt, ce->name); + break; + + default: + die("impossible cache entry stage"); + } + +skip_entry: + /* + * Ignore all the different stages for this file, + * we've handled the relevant cases now. + */ + do { + ac++; + entries--; + } while (entries && ce_same_name(ce, ac[0])); + } + return 0; +} + +/* + * This turns all merge entries into "stage 3". That guarantees that + * when we read in the new tree (into "stage 1"), we won't lose sight + * of the fact that we had unmerged entries. + */ +static void mark_merge_entries(void) +{ + int i; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + ce->ce_flags |= htons(CE_STAGEMASK); + } +} + +int run_diff_index(struct rev_info *revs, int cached) +{ + int ret; + struct object *ent; + struct tree *tree; + const char *tree_name; + int match_missing = 0; + + /* + * Backward compatibility wart - "diff-index -m" does + * not mean "do not ignore merges", but totally different. + */ + if (!revs->ignore_merges) + match_missing = 1; + + if (read_cache() < 0) { + perror("read_cache"); + return -1; + } + mark_merge_entries(); + + ent = revs->pending_objects->item; + tree_name = revs->pending_objects->name; + tree = parse_tree_indirect(ent->sha1); + if (!tree) + return error("bad tree object %s", tree_name); + if (read_tree(tree, 1, revs->prune_data)) + return error("unable to read tree object %s", tree_name); + ret = diff_cache(revs, active_cache, active_nr, revs->prune_data, + cached, match_missing); + diffcore_std(&revs->diffopt); + diff_flush(&revs->diffopt); + return ret; +} diff --git a/diff-tree.c b/diff-tree.c index d1c61c8515..7207867a74 100644 --- a/diff-tree.c +++ b/diff-tree.c @@ -3,7 +3,7 @@ #include "commit.h" #include "log-tree.h" -static struct log_tree_opt log_tree_opt; +static struct rev_info log_tree_opt; static int diff_tree_commit_sha1(const unsigned char *sha1) { @@ -62,47 +62,21 @@ int main(int argc, const char **argv) { int nr_sha1; char line[1000]; - unsigned char sha1[2][20]; - const char *prefix = setup_git_directory(); - static struct log_tree_opt *opt = &log_tree_opt; + struct object *tree1, *tree2; + static struct rev_info *opt = &log_tree_opt; + struct object_list *list; int read_stdin = 0; git_config(git_diff_config); nr_sha1 = 0; - init_log_tree_opt(opt); + init_revisions(opt); + opt->abbrev = 0; + opt->diff = 1; + argc = setup_revisions(argc, argv, opt, NULL); - for (;;) { - int opt_cnt; - const char *arg; + while (--argc > 0) { + const char *arg = *++argv; - argv++; - argc--; - arg = *argv; - if (!arg) - break; - - if (*arg != '-') { - if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) { - nr_sha1++; - continue; - } - break; - } - - opt_cnt = log_tree_opt_parse(opt, argv, argc); - if (opt_cnt < 0) - usage(diff_tree_usage); - else if (opt_cnt) { - argv += opt_cnt - 1; - argc -= opt_cnt - 1; - continue; - } - - if (!strcmp(arg, "--")) { - argv++; - argc--; - break; - } if (!strcmp(arg, "--stdin")) { read_stdin = 1; continue; @@ -110,15 +84,36 @@ int main(int argc, const char **argv) usage(diff_tree_usage); } - if (opt->combine_merges) - opt->ignore_merges = 0; - - /* We can only do dense combined merges with diff output */ - if (opt->dense_combined_merges) - opt->diffopt.output_format = DIFF_FORMAT_PATCH; - - diff_tree_setup_paths(get_pathspec(prefix, argv), &opt->diffopt); - diff_setup_done(&opt->diffopt); + /* + * NOTE! "setup_revisions()" will have inserted the revisions + * it parsed in reverse order. So if you do + * + * git-diff-tree a b + * + * the commit list will be "b" -> "a" -> NULL, so we reverse + * the order of the objects if the first one is not marked + * UNINTERESTING. + */ + nr_sha1 = 0; + list = opt->pending_objects; + if (list) { + nr_sha1++; + tree1 = list->item; + list = list->next; + if (list) { + nr_sha1++; + tree2 = tree1; + tree1 = list->item; + if (list->next) + usage(diff_tree_usage); + /* Switch them around if the second one was uninteresting.. */ + if (tree2->flags & UNINTERESTING) { + struct object *tmp = tree2; + tree2 = tree1; + tree1 = tmp; + } + } + } switch (nr_sha1) { case 0: @@ -126,10 +121,12 @@ int main(int argc, const char **argv) usage(diff_tree_usage); break; case 1: - diff_tree_commit_sha1(sha1[0]); + diff_tree_commit_sha1(tree1->sha1); break; case 2: - diff_tree_sha1(sha1[0], sha1[1], "", &opt->diffopt); + diff_tree_sha1(tree1->sha1, + tree2->sha1, + "", &opt->diffopt); log_tree_diff_flush(opt); break; } @@ -195,6 +195,56 @@ static int fn_out(void *priv, mmbuffer_t *mb, int nbuf) return 0; } +static char *pprint_rename(const char *a, const char *b) +{ + const char *old = a; + const char *new = b; + char *name = NULL; + int pfx_length, sfx_length; + int len_a = strlen(a); + int len_b = strlen(b); + + /* Find common prefix */ + pfx_length = 0; + while (*old && *new && *old == *new) { + if (*old == '/') + pfx_length = old - a + 1; + old++; + new++; + } + + /* Find common suffix */ + old = a + len_a; + new = b + len_b; + sfx_length = 0; + while (a <= old && b <= new && *old == *new) { + if (*old == '/') + sfx_length = len_a - (old - a); + old--; + new--; + } + + /* + * pfx{mid-a => mid-b}sfx + * {pfx-a => pfx-b}sfx + * pfx{sfx-a => sfx-b} + * name-a => name-b + */ + if (pfx_length + sfx_length) { + name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7); + sprintf(name, "%.*s{%.*s => %.*s}%s", + pfx_length, a, + len_a - pfx_length - sfx_length, a + pfx_length, + len_b - pfx_length - sfx_length, b + pfx_length, + a + len_a - sfx_length); + } + else { + name = xmalloc(len_a + len_b + 5); + sprintf(name, "%s => %s", a, b); + } + return name; +} + struct diffstat_t { struct xdiff_emit_state xm; @@ -204,12 +254,14 @@ struct diffstat_t { char *name; unsigned is_unmerged:1; unsigned is_binary:1; + unsigned is_renamed:1; unsigned int added, deleted; } **files; }; static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, - const char *name) + const char *name_a, + const char *name_b) { struct diffstat_file *x; x = xcalloc(sizeof (*x), 1); @@ -219,7 +271,12 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, diffstat->alloc * sizeof(x)); } diffstat->files[diffstat->nr++] = x; - x->name = strdup(name); + if (name_b) { + x->name = pprint_rename(name_a, name_b); + x->is_renamed = 1; + } + else + x->name = strdup(name_a); return x; } @@ -305,7 +362,8 @@ static void show_stats(struct diffstat_t* data) printf(" %s%-*s | Unmerged\n", prefix, len, name); goto free_diffstat_file; } - else if (added + deleted == 0) { + else if (!data->files[i]->is_renamed && + (added + deleted == 0)) { total_files--; goto free_diffstat_file; } @@ -425,19 +483,27 @@ static void builtin_diff(const char *name_a, } static void builtin_diffstat(const char *name_a, const char *name_b, - struct diff_filespec *one, struct diff_filespec *two, - struct diffstat_t *diffstat) + struct diff_filespec *one, + struct diff_filespec *two, + struct diffstat_t *diffstat, + int complete_rewrite) { mmfile_t mf1, mf2; struct diffstat_file *data; - data = diffstat_add(diffstat, name_a ? name_a : name_b); + data = diffstat_add(diffstat, name_a, name_b); if (!one || !two) { data->is_unmerged = 1; return; } - + if (complete_rewrite) { + diff_populate_filespec(one, 0); + diff_populate_filespec(two, 0); + data->deleted = count_lines(one->data, one->size); + data->added = count_lines(two->data, two->size); + return; + } if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -992,14 +1058,15 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) } static void run_diffstat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) + struct diffstat_t *diffstat) { const char *name; const char *other; + int complete_rewrite = 0; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat); + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0); return; } @@ -1009,7 +1076,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - builtin_diffstat(name, other, p->one, p->two, diffstat); + if (p->status == DIFF_STATUS_MODIFIED && p->score) + complete_rewrite = 1; + builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite); } void diff_setup(struct diff_options *options) @@ -1036,8 +1105,7 @@ int diff_setup_done(struct diff_options *options) * recursive bits for other formats here. */ if ((options->output_format == DIFF_FORMAT_PATCH) || - (options->output_format == DIFF_FORMAT_DIFFSTAT) || - (options->with_stat)) + (options->output_format == DIFF_FORMAT_DIFFSTAT)) options->recursive = 1; if (options->detect_rename && options->rename_limit < 0) @@ -1375,7 +1443,7 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) } static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, - struct diffstat_t *diffstat) + struct diffstat_t *diffstat) { if (diff_unmodified_pair(p)) return; @@ -1560,7 +1628,7 @@ void diff_flush(struct diff_options *options) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options, - diffstat); + diffstat); } show_stats(diffstat); free(diffstat); @@ -6,6 +6,7 @@ #include "tree-walk.h" +struct rev_info; struct diff_options; typedef void (*change_fn_t)(struct diff_options *options, @@ -27,10 +28,11 @@ struct diff_options { with_raw:1, with_stat:1, tree_in_recursive:1, - full_index:1; + full_index:1, + silent_on_remove:1, + find_copies_harder:1; int break_opt; int detect_rename; - int find_copies_harder; int line_termination; int output_format; int pickaxe_opts; @@ -70,11 +72,10 @@ struct combine_diff_path { (sizeof(struct combine_diff_path) + \ sizeof(struct combine_diff_parent) * (n) + (l) + 1) -extern int show_combined_diff(struct combine_diff_path *elem, int num_parent, - int dense, const char *header, - struct diff_options *); +extern void show_combined_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *); -extern const char *diff_tree_combined_merge(const unsigned char *sha1, const char *, int, struct diff_options *opt); +extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *); extern void diff_addremove(struct diff_options *, int addremove, @@ -168,4 +169,8 @@ extern void diff_flush(struct diff_options*); extern const char *diff_unique_abbrev(const unsigned char *, int); +extern int run_diff_files(struct rev_info *revs, int silent_on_removed); + +extern int run_diff_index(struct rev_info *revs, int cached); + #endif /* DIFF_H */ diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 7d3f78e375..11d153c4cd 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -88,7 +88,7 @@ my $TEMP_DIR = tempdir( CLEANUP => 1 ); $log->debug("Temporary directory is '$TEMP_DIR'"); # if we are called with a pserver argument, -# deal with the authentication cat before entereing the +# deal with the authentication cat before entering the # main loop if (@ARGV && $ARGV[0] eq 'pserver') { my $line = <STDIN>; chomp $line; @@ -117,7 +117,7 @@ while (<STDIN>) { chomp; - # Check to see if we've seen this method, and call appropiate function. + # Check to see if we've seen this method, and call appropriate function. if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) ) { # use the $methods hash to call the appropriate sub for this command @@ -171,11 +171,11 @@ sub req_Root return 0; } - my @gitvars = `git-var -l`; + my @gitvars = `git-repo-config -l`; if ($?) { - print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n"; + print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n"; print "E \n"; - print "error 1 - problem executing git-var\n"; + print "error 1 - problem executing git-repo-config\n"; return 0; } foreach my $line ( @gitvars ) @@ -224,7 +224,7 @@ sub req_Globaloption sub req_Validresponses { my ( $cmd, $data ) = @_; - $log->debug("req_Validrepsonses : $data"); + $log->debug("req_Validresponses : $data"); # TODO : re-enable this, currently it's not particularly useful #$state->{validresponses} = [ split /\s+/, $data ]; @@ -733,7 +733,7 @@ sub req_update argsplit("update"); # - # It may just be a client exploring the available heads/modukles + # It may just be a client exploring the available heads/modules # in that case, list them as top level directories and leave it # at that. Eclipse uses this technique to offer you a list of # projects (heads in this case) to checkout. @@ -1731,7 +1731,7 @@ sub transmitfile } # This method takes a file name, and returns ( $dirpart, $filepart ) which -# refers to the directory porition and the file portion of the filename +# refers to the directory portion and the file portion of the filename # respectively sub filenamesplit { @@ -1790,7 +1790,7 @@ Log::Log4perl =head2 new Creates a new log object, optionally you can specify a filename here to -indicate the file to log to. If no log file is specified, you can specifiy one +indicate the file to log to. If no log file is specified, you can specify one later with method setfile, or indicate you no longer want logging with method nofile. @@ -2595,7 +2595,7 @@ sub in_array =head2 safe_pipe_capture -an alterative to `command` that allows input to be passed as an array +an alternative to `command` that allows input to be passed as an array to work around shell problems with weird characters in arguments =cut diff --git a/git-fetch.sh b/git-fetch.sh index 83143f82cf..280f62e4b7 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -270,14 +270,22 @@ fetch_main () { if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" fi - remote_name_quoted=$(perl -e ' + max_depth=5 + depth=0 + head="ref: $remote_name" + while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null + do + remote_name_quoted=$(perl -e ' my $u = $ARGV[0]; + $u =~ s/^ref:\s*//; $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; print "$u"; - ' "$remote_name") - head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") && + ' "$head") + head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") + depth=$( expr \( $depth + 1 \) ) + done expr "z$head" : "z$_x40\$" >/dev/null || - die "Failed to fetch $remote_name from $remote" + die "Failed to fetch $remote_name from $remote" echo >&2 Fetching "$remote_name from $remote" using http git-http-fetch -v -a "$head" "$remote/" || exit ;; diff --git a/git-rebase.sh b/git-rebase.sh index f7b2b9401a..9e259028e0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -4,37 +4,51 @@ # USAGE='[--onto <newbase>] <upstream> [<branch>]' -LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits -from <branch> that do not appear in <upstream>. When <branch> is not -specified it defaults to the current branch (HEAD). - -When git-rebase is complete, <branch> will be updated to point to the -newly created line of commit objects, so the previous line will not be -accessible unless there are other references to it already. - -Assuming the following history: - - A---B---C topic - / - D---E---F---G master - -The result of the following command: - - git-rebase --onto master~1 master topic - - would be: - - A'\''--B'\''--C'\'' topic - / - D---E---F---G master +LONG_USAGE='git-rebase replaces <branch> with a new branch of the +same name. When the --onto option is provided the new branch starts +out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> +It then attempts to create a new commit for each commit from the original +<branch> that does not exist in the <upstream> branch. + +It is possible that a merge failure will prevent this process from being +completely automatic. You will have to resolve any such merge failure +and run git-rebase --continue. If you can not resolve the merge failure, +running git-rebase --abort will restore the original <branch> and remove +the working files found in the .dotest directory. + +Note that if <branch> is not specified on the command line, the +currently checked out branch is used. You must be in the top +directory of your project to start (or continue) a rebase. + +Example: git-rebase master~1 topic + + A---B---C topic A'\''--B'\''--C'\'' topic + / --> / + D---E---F---G master D---E---F---G master ' - . git-sh-setup unset newbase while case "$#" in 0) break ;; esac do case "$1" in + --continue) + diff=$(git-diff-files) + case "$diff" in + ?*) echo "You must edit all merge conflicts and then" + echo "mark them as resolved using git update-index" + exit 1 + ;; + esac + git am --resolved --3way + exit + ;; + --abort) + [ -d .dotest ] || die "No rebase in progress?" + git reset --hard ORIG_HEAD + rm -r .dotest + exit + ;; --onto) test 2 -le "$#" || usage newbase="$2" diff --git a/git-repack.sh b/git-repack.sh index a5d349fd09..e0c9f323c3 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -5,9 +5,9 @@ USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' . git-sh-setup - + no_update_info= all_into_one= remove_redundant= -local= quiet= no_reuse_delta= +local= quiet= no_reuse_delta= extra= while case "$#" in 0) break ;; esac do case "$1" in @@ -17,6 +17,8 @@ do -q) quiet=-q ;; -f) no_reuse_delta=--no-reuse-delta ;; -l) local=--local ;; + --window=*) extra="$extra $1" ;; + --depth=*) extra="$extra $1" ;; *) usage ;; esac shift @@ -40,7 +42,7 @@ case ",$all_into_one," in find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac -pack_objects="$pack_objects $local $quiet $no_reuse_delta" +pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" name=$(git-rev-list --objects --all $rev_list 2>&1 | git-pack-objects --non-empty $pack_objects .tmp-pack) || exit 1 @@ -8,218 +8,10 @@ #include <errno.h> #include <limits.h> #include <stdarg.h> -#include <sys/ioctl.h> #include "git-compat-util.h" #include "exec_cmd.h" -#include "common-cmds.h" -#include "cache.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "log-tree.h" - -#ifndef PATH_MAX -# define PATH_MAX 4096 -#endif - -static const char git_usage[] = - "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]"; - -/* most gui terms set COLUMNS (although some don't export it) */ -static int term_columns(void) -{ - char *col_string = getenv("COLUMNS"); - int n_cols = 0; - - if (col_string && (n_cols = atoi(col_string)) > 0) - return n_cols; - -#ifdef TIOCGWINSZ - { - struct winsize ws; - if (!ioctl(1, TIOCGWINSZ, &ws)) { - if (ws.ws_col) - return ws.ws_col; - } - } -#endif - - return 80; -} - -static void oom(void) -{ - fprintf(stderr, "git: out of memory\n"); - exit(1); -} - -static inline void mput_char(char c, unsigned int num) -{ - while(num--) - putchar(c); -} - -static struct cmdname { - size_t len; - char name[1]; -} **cmdname; -static int cmdname_alloc, cmdname_cnt; - -static void add_cmdname(const char *name, int len) -{ - struct cmdname *ent; - if (cmdname_alloc <= cmdname_cnt) { - cmdname_alloc = cmdname_alloc + 200; - cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname)); - if (!cmdname) - oom(); - } - ent = malloc(sizeof(*ent) + len); - if (!ent) - oom(); - ent->len = len; - memcpy(ent->name, name, len); - ent->name[len] = 0; - cmdname[cmdname_cnt++] = ent; -} - -static int cmdname_compare(const void *a_, const void *b_) -{ - struct cmdname *a = *(struct cmdname **)a_; - struct cmdname *b = *(struct cmdname **)b_; - return strcmp(a->name, b->name); -} - -static void pretty_print_string_list(struct cmdname **cmdname, int longest) -{ - int cols = 1, rows; - int space = longest + 1; /* min 1 SP between words */ - int max_cols = term_columns() - 1; /* don't print *on* the edge */ - int i, j; - - if (space < max_cols) - cols = max_cols / space; - rows = (cmdname_cnt + cols - 1) / cols; - - qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare); - - for (i = 0; i < rows; i++) { - printf(" "); - - for (j = 0; j < cols; j++) { - int n = j * rows + i; - int size = space; - if (n >= cmdname_cnt) - break; - if (j == cols-1 || n + rows >= cmdname_cnt) - size = 1; - printf("%-*s", size, cmdname[n]->name); - } - putchar('\n'); - } -} - -static void list_commands(const char *exec_path, const char *pattern) -{ - unsigned int longest = 0; - char path[PATH_MAX]; - int dirlen; - DIR *dir = opendir(exec_path); - struct dirent *de; - - if (!dir) { - fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno)); - exit(1); - } - - dirlen = strlen(exec_path); - if (PATH_MAX - 20 < dirlen) { - fprintf(stderr, "git: insanely long exec-path '%s'\n", - exec_path); - exit(1); - } - - memcpy(path, exec_path, dirlen); - path[dirlen++] = '/'; - - while ((de = readdir(dir)) != NULL) { - struct stat st; - int entlen; - - if (strncmp(de->d_name, "git-", 4)) - continue; - strcpy(path+dirlen, de->d_name); - if (stat(path, &st) || /* stat, not lstat */ - !S_ISREG(st.st_mode) || - !(st.st_mode & S_IXUSR)) - continue; - - entlen = strlen(de->d_name); - if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe")) - entlen -= 4; - - if (longest < entlen) - longest = entlen; - - add_cmdname(de->d_name + 4, entlen-4); - } - closedir(dir); - - printf("git commands available in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); - putchar('\n'); - pretty_print_string_list(cmdname, longest - 4); - putchar('\n'); -} - -static void list_common_cmds_help(void) -{ - int i, longest = 0; - - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - if (longest < strlen(common_cmds[i].name)) - longest = strlen(common_cmds[i].name); - } - - puts("The most commonly used git commands are:"); - for (i = 0; i < ARRAY_SIZE(common_cmds); i++) { - printf(" %s", common_cmds[i].name); - mput_char(' ', longest - strlen(common_cmds[i].name) + 4); - puts(common_cmds[i].help); - } - puts("(use 'git help -a' to get a list of all installed git commands)"); -} - -#ifdef __GNUC__ -static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) - __attribute__((__format__(__printf__, 3, 4), __noreturn__)); -#endif -static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...) -{ - if (fmt) { - va_list ap; - - va_start(ap, fmt); - printf("git: "); - vprintf(fmt, ap); - va_end(ap); - putchar('\n'); - } - else - puts(git_usage); - - if (exec_path) { - putchar('\n'); - if (show_all) - list_commands(exec_path, "git-*"); - else - list_common_cmds_help(); - } - - exit(1); -} +#include "builtin.h" static void prepend_to_path(const char *dir, int len) { @@ -240,163 +32,7 @@ static void prepend_to_path(const char *dir, int len) setenv("PATH", path, 1); } -static void show_man_page(const char *git_cmd) -{ - const char *page; - - if (!strncmp(git_cmd, "git", 3)) - page = git_cmd; - else { - int page_len = strlen(git_cmd) + 4; - char *p = malloc(page_len + 1); - strcpy(p, "git-"); - strcpy(p + 4, git_cmd); - p[page_len] = 0; - page = p; - } - - execlp("man", "man", page, NULL); -} - -static int cmd_version(int argc, const char **argv, char **envp) -{ - printf("git version %s\n", GIT_VERSION); - return 0; -} - -static int cmd_help(int argc, const char **argv, char **envp) -{ - const char *help_cmd = argv[1]; - if (!help_cmd) - cmd_usage(0, git_exec_path(), NULL); - else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) - cmd_usage(1, git_exec_path(), NULL); - else - show_man_page(help_cmd); - return 0; -} - -#define LOGSIZE (65536) - -static int cmd_log(int argc, const char **argv, char **envp) -{ - struct rev_info rev; - struct commit *commit; - char *buf = xmalloc(LOGSIZE); - static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT; - int abbrev = DEFAULT_ABBREV; - int abbrev_commit = 0; - const char *commit_prefix = "commit "; - struct log_tree_opt opt; - int shown = 0; - int do_diff = 0; - int full_diff = 0; - - init_log_tree_opt(&opt); - argc = setup_revisions(argc, argv, &rev, "HEAD"); - while (1 < argc) { - const char *arg = argv[1]; - if (!strncmp(arg, "--pretty", 8)) { - commit_format = get_commit_format(arg + 8); - if (commit_format == CMIT_FMT_ONELINE) - commit_prefix = ""; - } - else if (!strcmp(arg, "--no-abbrev")) { - abbrev = 0; - } - else if (!strcmp(arg, "--abbrev")) { - abbrev = DEFAULT_ABBREV; - } - else if (!strcmp(arg, "--abbrev-commit")) { - abbrev_commit = 1; - } - else if (!strncmp(arg, "--abbrev=", 9)) { - abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (40 < abbrev) - abbrev = 40; - } - else if (!strcmp(arg, "--full-diff")) { - do_diff = 1; - full_diff = 1; - } - else { - int cnt = log_tree_opt_parse(&opt, argv+1, argc-1); - if (0 < cnt) { - do_diff = 1; - argv += cnt; - argc -= cnt; - continue; - } - die("unrecognized argument: %s", arg); - } - - argc--; argv++; - } - - if (do_diff) { - opt.diffopt.abbrev = abbrev; - opt.verbose_header = 0; - opt.always_show_header = 0; - opt.no_commit_id = 1; - if (opt.combine_merges) - opt.ignore_merges = 0; - if (opt.dense_combined_merges) - opt.diffopt.output_format = DIFF_FORMAT_PATCH; - if (!full_diff && rev.prune_data) - diff_tree_setup_paths(rev.prune_data, &opt.diffopt); - diff_setup_done(&opt.diffopt); - } - - prepare_revision_walk(&rev); - setup_pager(); - while ((commit = get_revision(&rev)) != NULL) { - if (shown && do_diff && commit_format != CMIT_FMT_ONELINE) - putchar('\n'); - fputs(commit_prefix, stdout); - if (abbrev_commit && abbrev) - fputs(find_unique_abbrev(commit->object.sha1, abbrev), - stdout); - else - fputs(sha1_to_hex(commit->object.sha1), stdout); - if (rev.parents) { - struct commit_list *parents = commit->parents; - while (parents) { - struct object *o = &(parents->item->object); - parents = parents->next; - if (o->flags & TMP_MARK) - continue; - printf(" %s", sha1_to_hex(o->sha1)); - o->flags |= TMP_MARK; - } - /* TMP_MARK is a general purpose flag that can - * be used locally, but the user should clean - * things up after it is done with them. - */ - for (parents = commit->parents; - parents; - parents = parents->next) - parents->item->object.flags &= ~TMP_MARK; - } - if (commit_format == CMIT_FMT_ONELINE) - putchar(' '); - else - putchar('\n'); - pretty_print_commit(commit_format, commit, ~0, buf, - LOGSIZE, abbrev); - printf("%s\n", buf); - if (do_diff) { - printf("---\n"); - log_tree_commit(&opt, commit); - } - shown = 1; - free(commit->buffer); - commit->buffer = NULL; - } - free(buf); - return 0; -} +const char git_version_string[] = GIT_VERSION; static void handle_internal_command(int argc, const char **argv, char **envp) { @@ -408,6 +44,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "version", cmd_version }, { "help", cmd_help }, { "log", cmd_log }, + { "whatchanged", cmd_whatchanged }, + { "show", cmd_show }, }; int i; @@ -16,22 +16,6 @@ proc gitdir {} { } } -proc parse_args {rargs} { - global parsed_args - - if {[catch { - set parse_args [concat --default HEAD $rargs] - set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"] - }]} { - # if git-rev-parse failed for some reason... - if {$rargs == {}} { - set rargs HEAD - } - set parsed_args $rargs - } - return $parsed_args -} - proc start_rev_list {rlargs} { global startmsecs nextupdate ncmupdate global commfd leftover tclencoding datemode @@ -46,7 +30,7 @@ proc start_rev_list {rlargs} { } if {[catch { set commfd [open [concat | git-rev-list --header $order \ - --parents --boundary $rlargs] r] + --parents --boundary --default HEAD $rlargs] r] } err]} { puts stderr "Error executing git-rev-list: $err" exit 1 @@ -65,7 +49,7 @@ proc getcommits {rargs} { global phase canv mainfont set phase getcommits - start_rev_list [parse_args $rargs] + start_rev_list $rargs $canv delete all $canv create text 3 3 -anchor nw -text "Reading commits..." \ -font $mainfont -tags textitems diff --git a/http-push.c b/http-push.c index 114d01bced..b4327d9243 100644 --- a/http-push.c +++ b/http-push.c @@ -2498,6 +2498,7 @@ int main(int argc, char **argv) commit_argv[3] = old_sha1_hex; commit_argc++; } + init_revisions(&revs); setup_revisions(commit_argc, commit_argv, &revs, NULL); free(new_sha1_hex); if (old_sha1_hex) { diff --git a/log-tree.c b/log-tree.c index 3d404824a1..9634c4677f 100644 --- a/log-tree.c +++ b/log-tree.c @@ -3,59 +3,58 @@ #include "commit.h" #include "log-tree.h" -void init_log_tree_opt(struct log_tree_opt *opt) +void show_log(struct rev_info *opt, struct log_info *log, const char *sep) { - memset(opt, 0, sizeof *opt); - opt->ignore_merges = 1; - opt->header_prefix = ""; - opt->commit_format = CMIT_FMT_RAW; - diff_setup(&opt->diffopt); -} - -int log_tree_opt_parse(struct log_tree_opt *opt, const char **av, int ac) -{ - const char *arg; - int cnt = diff_opt_parse(&opt->diffopt, av, ac); - if (0 < cnt) - return cnt; - arg = *av; - if (!strcmp(arg, "-r")) - opt->diffopt.recursive = 1; - else if (!strcmp(arg, "-t")) { - opt->diffopt.recursive = 1; - opt->diffopt.tree_in_recursive = 1; - } - else if (!strcmp(arg, "-m")) - opt->ignore_merges = 0; - else if (!strcmp(arg, "-c")) - opt->combine_merges = 1; - else if (!strcmp(arg, "--cc")) { - opt->dense_combined_merges = 1; - opt->combine_merges = 1; - } - else if (!strcmp(arg, "-v")) { - opt->verbose_header = 1; - opt->header_prefix = "diff-tree "; - } - else if (!strncmp(arg, "--pretty", 8)) { - opt->verbose_header = 1; - opt->header_prefix = "diff-tree "; - opt->commit_format = get_commit_format(arg+8); + static char this_header[16384]; + struct commit *commit = log->commit, *parent = log->parent; + int abbrev = opt->diffopt.abbrev; + int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40; + const char *extra; + int len; + + opt->loginfo = NULL; + if (!opt->verbose_header) { + puts(sha1_to_hex(commit->object.sha1)); + return; } - else if (!strcmp(arg, "--root")) - opt->show_root_diff = 1; - else if (!strcmp(arg, "--no-commit-id")) - opt->no_commit_id = 1; - else if (!strcmp(arg, "--always")) - opt->always_show_header = 1; - else - return 0; - return 1; + + /* + * The "oneline" format has several special cases: + * - The pretty-printed commit lacks a newline at the end + * of the buffer, but we do want to make sure that we + * have a newline there. If the separator isn't already + * a newline, add an extra one. + * - unlike other log messages, the one-line format does + * not have an empty line between entries. + */ + extra = ""; + if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE) + extra = "\n"; + if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE) + putchar('\n'); + opt->shown_one = 1; + + /* + * Print header line of header.. + */ + printf("%s%s", + opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ", + diff_unique_abbrev(commit->object.sha1, abbrev_commit)); + if (parent) + printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit)); + putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n'); + + /* + * And then the pretty-printed message itself + */ + len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev); + printf("%s%s%s", this_header, extra, sep); } -int log_tree_diff_flush(struct log_tree_opt *opt) +int log_tree_diff_flush(struct rev_info *opt) { diffcore_std(&opt->diffopt); + if (diff_queue_is_empty()) { int saved_fmt = opt->diffopt.output_format; opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -63,17 +62,14 @@ int log_tree_diff_flush(struct log_tree_opt *opt) opt->diffopt.output_format = saved_fmt; return 0; } - if (opt->header) { - if (!opt->no_commit_id) - printf("%s%c", opt->header, - opt->diffopt.line_termination); - opt->header = NULL; - } + + if (opt->loginfo && !opt->no_commit_id) + show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n"); diff_flush(&opt->diffopt); return 1; } -static int diff_root_tree(struct log_tree_opt *opt, +static int diff_root_tree(struct rev_info *opt, const unsigned char *new, const char *base) { int retval; @@ -93,83 +89,78 @@ static int diff_root_tree(struct log_tree_opt *opt, return retval; } -static const char *generate_header(struct log_tree_opt *opt, - const unsigned char *commit_sha1, - const unsigned char *parent_sha1, - const struct commit *commit) -{ - static char this_header[16384]; - int offset; - unsigned long len; - int abbrev = opt->diffopt.abbrev; - const char *msg = commit->buffer; - - if (!opt->verbose_header) - return sha1_to_hex(commit_sha1); - - len = strlen(msg); - - offset = sprintf(this_header, "%s%s ", - opt->header_prefix, - diff_unique_abbrev(commit_sha1, abbrev)); - if (commit_sha1 != parent_sha1) - offset += sprintf(this_header + offset, "(from %s)\n", - parent_sha1 - ? diff_unique_abbrev(parent_sha1, abbrev) - : "root"); - else - offset += sprintf(this_header + offset, "(from parents)\n"); - offset += pretty_print_commit(opt->commit_format, commit, len, - this_header + offset, - sizeof(this_header) - offset, abbrev); - if (opt->always_show_header) { - puts(this_header); - return NULL; - } - return this_header; -} - -static int do_diff_combined(struct log_tree_opt *opt, struct commit *commit) +static int do_diff_combined(struct rev_info *opt, struct commit *commit) { unsigned const char *sha1 = commit->object.sha1; - opt->header = generate_header(opt, sha1, sha1, commit); - opt->header = diff_tree_combined_merge(sha1, opt->header, - opt->dense_combined_merges, - &opt->diffopt); - if (!opt->header && opt->verbose_header) - opt->header_prefix = "\ndiff-tree "; - return 0; + diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt); + return !opt->loginfo; } -int log_tree_commit(struct log_tree_opt *opt, struct commit *commit) +/* + * Show the diff of a commit. + * + * Return true if we printed any log info messages + */ +static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log) { + int showed_log; struct commit_list *parents; unsigned const char *sha1 = commit->object.sha1; + if (!opt->diff) + return 0; + /* Root commit? */ - if (opt->show_root_diff && !commit->parents) { - opt->header = generate_header(opt, sha1, NULL, commit); - diff_root_tree(opt, sha1, ""); + parents = commit->parents; + if (!parents) { + if (opt->show_root_diff) + diff_root_tree(opt, sha1, ""); + return !opt->loginfo; } /* More than one parent? */ - if (commit->parents && commit->parents->next) { + if (parents && parents->next) { if (opt->ignore_merges) return 0; else if (opt->combine_merges) return do_diff_combined(opt, commit); + + /* If we show individual diffs, show the parent info */ + log->parent = parents->item; } - for (parents = commit->parents; parents; parents = parents->next) { + showed_log = 0; + for (;;) { struct commit *parent = parents->item; - unsigned const char *psha1 = parent->object.sha1; - opt->header = generate_header(opt, sha1, psha1, commit); - diff_tree_sha1(psha1, sha1, "", &opt->diffopt); - log_tree_diff_flush(opt); - if (!opt->header && opt->verbose_header) - opt->header_prefix = "\ndiff-tree "; + diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt); + log_tree_diff_flush(opt); + + showed_log |= !opt->loginfo; + + /* Set up the log info for the next parent, if any.. */ + parents = parents->next; + if (!parents) + break; + log->parent = parents->item; + opt->loginfo = log; + } + return showed_log; +} + +int log_tree_commit(struct rev_info *opt, struct commit *commit) +{ + struct log_info log; + + log.commit = commit; + log.parent = NULL; + opt->loginfo = &log; + + if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) { + log.parent = NULL; + show_log(opt, opt->loginfo, ""); } + opt->loginfo = NULL; return 0; } diff --git a/log-tree.h b/log-tree.h index da166c6f2c..a26e4841ff 100644 --- a/log-tree.h +++ b/log-tree.h @@ -1,23 +1,16 @@ #ifndef LOG_TREE_H #define LOG_TREE_H -struct log_tree_opt { - struct diff_options diffopt; - int show_root_diff; - int no_commit_id; - int verbose_header; - int ignore_merges; - int combine_merges; - int dense_combined_merges; - int always_show_header; - const char *header_prefix; - const char *header; - enum cmit_fmt commit_format; +#include "revision.h" + +struct log_info { + struct commit *commit, *parent; }; -void init_log_tree_opt(struct log_tree_opt *); -int log_tree_diff_flush(struct log_tree_opt *); -int log_tree_commit(struct log_tree_opt *, struct commit *); -int log_tree_opt_parse(struct log_tree_opt *, const char **, int); +void init_log_tree_opt(struct rev_info *); +int log_tree_diff_flush(struct rev_info *); +int log_tree_commit(struct rev_info *, struct commit *); +int log_tree_opt_parse(struct rev_info *, const char **, int); +void show_log(struct rev_info *opt, struct log_info *log, const char *sep); #endif diff --git a/pack-objects.c b/pack-objects.c index c0acc460bb..6604338131 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -1032,12 +1032,6 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de max_depth -= cur_entry->delta_limit; } - size = cur_entry->size; - oldsize = old_entry->size; - sizediff = oldsize > size ? oldsize - size : size - oldsize; - - if (size < 50) - return -1; if (old_entry->depth >= max_depth) return 0; @@ -1048,9 +1042,12 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de * more space-efficient (deletes don't have to say _what_ they * delete). */ + size = cur_entry->size; max_size = size / 2 - 20; if (cur_entry->delta) max_size = cur_entry->delta_size-1; + oldsize = old_entry->size; + sizediff = oldsize < size ? size - oldsize : 0; if (sizediff >= max_size) return 0; delta_buf = diff_delta(old->data, oldsize, @@ -1109,6 +1106,9 @@ static void find_deltas(struct object_entry **list, int window, int depth) */ continue; + if (entry->size < 50) + continue; + free(n->data); n->entry = entry; n->data = read_sha1_file(entry->sha1, type, &size); @@ -21,7 +21,7 @@ void setup_pager(void) return; if (!pager) pager = "less"; - else if (!*pager) + else if (!*pager || !strcmp(pager, "cat")) return; if (pipe(fd) < 0) diff --git a/repo-config.c b/repo-config.c index c5ebb7668a..e35063034f 100644 --- a/repo-config.c +++ b/repo-config.c @@ -2,7 +2,7 @@ #include <regex.h> static const char git_config_set_usage[] = -"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]]"; +"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; static char* key = NULL; static char* value = NULL; @@ -12,6 +12,15 @@ static int do_not_match = 0; static int seen = 0; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; +static int show_all_config(const char *key_, const char *value_) +{ + if (value_) + printf("%s=%s\n", key_, value_); + else + printf("%s\n", key_); + return 0; +} + static int show_config(const char* key_, const char* value_) { if (value_ == NULL) @@ -67,7 +76,7 @@ static int get_value(const char* key_, const char* regex_) } } - i = git_config(show_config); + git_config(show_config); if (value) { printf("%s\n", value); free(value); @@ -93,6 +102,8 @@ int main(int argc, const char **argv) type = T_INT; else if (!strcmp(argv[1], "--bool")) type = T_BOOL; + else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) + return git_config(show_all_config); else break; argc--; diff --git a/rev-list.c b/rev-list.c index a8fe83c5d8..8b0ec388fa 100644 --- a/rev-list.c +++ b/rev-list.c @@ -39,24 +39,21 @@ static const char rev_list_usage[] = struct rev_info revs; static int bisect_list = 0; -static int verbose_header = 0; -static int abbrev = DEFAULT_ABBREV; -static int abbrev_commit = 0; static int show_timestamp = 0; static int hdr_termination = 0; -static const char *commit_prefix = ""; -static enum cmit_fmt commit_format = CMIT_FMT_RAW; +static const char *header_prefix; static void show_commit(struct commit *commit) { if (show_timestamp) printf("%lu ", commit->date); - if (commit_prefix[0]) - fputs(commit_prefix, stdout); + if (header_prefix) + fputs(header_prefix, stdout); if (commit->object.flags & BOUNDARY) putchar('-'); - if (abbrev_commit && abbrev) - fputs(find_unique_abbrev(commit->object.sha1, abbrev), stdout); + if (revs.abbrev_commit && revs.abbrev) + fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), + stdout); else fputs(sha1_to_hex(commit->object.sha1), stdout); if (revs.parents) { @@ -78,14 +75,16 @@ static void show_commit(struct commit *commit) parents = parents->next) parents->item->object.flags &= ~TMP_MARK; } - if (commit_format == CMIT_FMT_ONELINE) + if (revs.commit_format == CMIT_FMT_ONELINE) putchar(' '); else putchar('\n'); - if (verbose_header) { + if (revs.verbose_header) { static char pretty_header[16384]; - pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev); + pretty_print_commit(revs.commit_format, commit, ~0, + pretty_header, sizeof(pretty_header), + revs.abbrev); printf("%s%c", pretty_header, hdr_termination); } fflush(stdout); @@ -297,58 +296,16 @@ int main(int argc, const char **argv) struct commit_list *list; int i; + init_revisions(&revs); + revs.abbrev = 0; + revs.commit_format = CMIT_FMT_UNSPECIFIED; argc = setup_revisions(argc, argv, &revs, NULL); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; - /* accept -<digit>, like traditilnal "head" */ - if ((*arg == '-') && isdigit(arg[1])) { - revs.max_count = atoi(arg + 1); - continue; - } - if (!strcmp(arg, "-n")) { - if (++i >= argc) - die("-n requires an argument"); - revs.max_count = atoi(argv[i]); - continue; - } - if (!strncmp(arg,"-n",2)) { - revs.max_count = atoi(arg + 2); - continue; - } if (!strcmp(arg, "--header")) { - verbose_header = 1; - continue; - } - if (!strcmp(arg, "--no-abbrev")) { - abbrev = 0; - continue; - } - if (!strcmp(arg, "--abbrev")) { - abbrev = DEFAULT_ABBREV; - continue; - } - if (!strcmp(arg, "--abbrev-commit")) { - abbrev_commit = 1; - continue; - } - if (!strncmp(arg, "--abbrev=", 9)) { - abbrev = strtoul(arg + 9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (40 < abbrev) - abbrev = 40; - continue; - } - if (!strncmp(arg, "--pretty", 8)) { - commit_format = get_commit_format(arg+8); - verbose_header = 1; - hdr_termination = '\n'; - if (commit_format == CMIT_FMT_ONELINE) - commit_prefix = ""; - else - commit_prefix = "commit "; + revs.verbose_header = 1; continue; } if (!strcmp(arg, "--timestamp")) { @@ -362,14 +319,27 @@ int main(int argc, const char **argv) usage(rev_list_usage); } + if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { + /* The command line has a --pretty */ + hdr_termination = '\n'; + if (revs.commit_format == CMIT_FMT_ONELINE) + header_prefix = ""; + else + header_prefix = "commit "; + } + else if (revs.verbose_header) + /* Only --header was specified */ + revs.commit_format = CMIT_FMT_RAW; list = revs.commits; - if (!list && - (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects)) + if ((!list && + (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && + !revs.pending_objects)) || + revs.diff) usage(rev_list_usage); - save_commit_buffer = verbose_header; + save_commit_buffer = revs.verbose_header; track_object_refs = 0; if (bisect_list) revs.limited = 1; diff --git a/revision.c b/revision.c index 03dd238939..ad78efda51 100644 --- a/revision.c +++ b/revision.c @@ -116,21 +116,27 @@ static void add_pending_object(struct rev_info *revs, struct object *obj, const add_object(obj, &revs->pending_objects, NULL, name); } -static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags) +static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags) { struct object *object; object = parse_object(sha1); if (!object) die("bad object %s", name); + object->flags |= flags; + return object; +} + +static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name) +{ + unsigned long flags = object->flags; /* * Tag object? Look what it points to.. */ while (object->type == tag_type) { struct tag *tag = (struct tag *) object; - object->flags |= flags; - if (revs->tag_objects && !(object->flags & UNINTERESTING)) + if (revs->tag_objects && !(flags & UNINTERESTING)) add_pending_object(revs, object, tag->tag); object = parse_object(tag->tagged->sha1); if (!object) @@ -143,10 +149,10 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na */ if (object->type == commit_type) { struct commit *commit = (struct commit *)object; - object->flags |= flags; if (parse_commit(commit) < 0) die("unable to parse commit %s", name); if (flags & UNINTERESTING) { + commit->object.flags |= UNINTERESTING; mark_parents_uninteresting(commit); revs->limited = 1; } @@ -241,7 +247,7 @@ int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2) return REV_TREE_DIFFERENT; tree_difference = REV_TREE_SAME; if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", - &revs->diffopt) < 0) + &revs->pruning) < 0) return REV_TREE_DIFFERENT; return tree_difference; } @@ -264,7 +270,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1) empty.size = 0; tree_difference = 0; - retval = diff_tree(&empty, &real, "", &revs->diffopt); + retval = diff_tree(&empty, &real, "", &revs->pruning); free(tree); return retval >= 0 && !tree_difference; @@ -375,6 +381,9 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st if (revs->prune_fn) revs->prune_fn(revs, commit); + if (revs->no_walk) + return; + parent = commit->parents; while (parent) { struct commit *p = parent->item; @@ -451,21 +460,13 @@ static void limit_list(struct rev_info *revs) revs->commits = newlist; } -static void add_one_commit(struct commit *commit, struct rev_info *revs) -{ - if (!commit || (commit->object.flags & SEEN)) - return; - commit->object.flags |= SEEN; - commit_list_insert(commit, &revs->commits); -} - static int all_flags; static struct rev_info *all_revs; static int handle_one_ref(const char *path, const unsigned char *sha1) { - struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags); - add_one_commit(commit, all_revs); + struct object *object = get_reference(all_revs, path, sha1, all_flags); + add_pending_object(all_revs, object, ""); return 0; } @@ -476,12 +477,45 @@ static void handle_all(struct rev_info *revs, unsigned flags) for_each_ref(handle_one_ref); } +static int add_parents_only(struct rev_info *revs, const char *arg, int flags) +{ + unsigned char sha1[20]; + struct object *it; + struct commit *commit; + struct commit_list *parents; + + if (*arg == '^') { + flags ^= UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1)) + return 0; + while (1) { + it = get_reference(revs, arg, sha1, 0); + if (strcmp(it->type, tag_type)) + break; + memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20); + } + if (strcmp(it->type, commit_type)) + return 0; + commit = (struct commit *)it; + for (parents = commit->parents; parents; parents = parents->next) { + it = &parents->item->object; + it->flags |= flags; + add_pending_object(revs, it, arg); + } + return 1; +} + void init_revisions(struct rev_info *revs) { memset(revs, 0, sizeof(*revs)); - revs->diffopt.recursive = 1; - revs->diffopt.add_remove = file_add_remove; - revs->diffopt.change = file_change; + + revs->abbrev = DEFAULT_ABBREV; + revs->ignore_merges = 1; + revs->pruning.recursive = 1; + revs->pruning.add_remove = file_add_remove; + revs->pruning.change = file_change; revs->lifo = 1; revs->dense = 1; revs->prefix = setup_git_directory(); @@ -494,6 +528,10 @@ void init_revisions(struct rev_info *revs) revs->topo_setter = topo_sort_default_setter; revs->topo_getter = topo_sort_default_getter; + + revs->commit_format = CMIT_FMT_DEFAULT; + + diff_setup(&revs->diffopt); } /* @@ -509,8 +547,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch const char **unrecognized = argv + 1; int left = 1; - init_revisions(revs); - /* First, search for "--" */ seen_dashdash = 0; for (i = 1; i < argc; i++) { @@ -526,13 +562,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch flags = 0; for (i = 1; i < argc; i++) { - struct commit *commit; + struct object *object; const char *arg = argv[i]; unsigned char sha1[20]; char *dotdot; int local_flags; if (*arg == '-') { + int opts; if (!strncmp(arg, "--max-count=", 12)) { revs->max_count = atoi(arg + 12); continue; @@ -640,6 +677,76 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->unpacked = 1; continue; } + if (!strcmp(arg, "-r")) { + revs->diff = 1; + revs->diffopt.recursive = 1; + continue; + } + if (!strcmp(arg, "-t")) { + revs->diff = 1; + revs->diffopt.recursive = 1; + revs->diffopt.tree_in_recursive = 1; + continue; + } + if (!strcmp(arg, "-m")) { + revs->ignore_merges = 0; + continue; + } + if (!strcmp(arg, "-c")) { + revs->diff = 1; + revs->combine_merges = 1; + continue; + } + if (!strcmp(arg, "--cc")) { + revs->diff = 1; + revs->dense_combined_merges = 1; + revs->combine_merges = 1; + continue; + } + if (!strcmp(arg, "-v")) { + revs->verbose_header = 1; + continue; + } + if (!strncmp(arg, "--pretty", 8)) { + revs->verbose_header = 1; + revs->commit_format = get_commit_format(arg+8); + continue; + } + if (!strcmp(arg, "--root")) { + revs->show_root_diff = 1; + continue; + } + if (!strcmp(arg, "--no-commit-id")) { + revs->no_commit_id = 1; + continue; + } + if (!strcmp(arg, "--always")) { + revs->always_show_header = 1; + continue; + } + if (!strcmp(arg, "--no-abbrev")) { + revs->abbrev = 0; + continue; + } + if (!strcmp(arg, "--abbrev")) { + revs->abbrev = DEFAULT_ABBREV; + continue; + } + if (!strcmp(arg, "--abbrev-commit")) { + revs->abbrev_commit = 1; + continue; + } + if (!strcmp(arg, "--full-diff")) { + revs->diff = 1; + revs->full_diff = 1; + continue; + } + opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i); + if (opts > 0) { + revs->diff = 1; + i += opts - 1; + continue; + } *unrecognized++ = arg; left++; continue; @@ -656,19 +763,31 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch this = "HEAD"; if (!get_sha1(this, from_sha1) && !get_sha1(next, sha1)) { - struct commit *exclude; - struct commit *include; + struct object *exclude; + struct object *include; - exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING); - include = get_commit_reference(revs, next, sha1, flags); + exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING); + include = get_reference(revs, next, sha1, flags); if (!exclude || !include) die("Invalid revision range %s..%s", arg, next); - add_one_commit(exclude, revs); - add_one_commit(include, revs); + + if (!seen_dashdash) { + *dotdot = '.'; + verify_non_filename(revs->prefix, arg); + } + add_pending_object(revs, exclude, this); + add_pending_object(revs, include, next); continue; } *dotdot = '.'; } + dotdot = strstr(arg, "^@"); + if (dotdot && !dotdot[2]) { + *dotdot = 0; + if (add_parents_only(revs, arg, flags)) + continue; + *dotdot = '^'; + } local_flags = 0; if (*arg == '^') { local_flags = UNINTERESTING; @@ -680,39 +799,72 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (seen_dashdash || local_flags) die("bad revision '%s'", arg); - /* If we didn't have a "--", all filenames must exist */ + /* If we didn't have a "--": + * (1) all filenames must exist; + * (2) all rev-args must not be interpretable + * as a valid filename. + * but the latter we have checked in the main loop. + */ for (j = i; j < argc; j++) verify_filename(revs->prefix, argv[j]); revs->prune_data = get_pathspec(revs->prefix, argv + i); break; } - commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags); - add_one_commit(commit, revs); + if (!seen_dashdash) + verify_non_filename(revs->prefix, arg); + object = get_reference(revs, arg, sha1, flags ^ local_flags); + add_pending_object(revs, object, arg); } - if (def && !revs->commits) { + if (def && !revs->pending_objects) { unsigned char sha1[20]; - struct commit *commit; + struct object *object; if (get_sha1(def, sha1) < 0) die("bad default revision '%s'", def); - commit = get_commit_reference(revs, def, sha1, 0); - add_one_commit(commit, revs); + object = get_reference(revs, def, sha1, 0); + add_pending_object(revs, object, def); } if (revs->topo_order || revs->unpacked) revs->limited = 1; if (revs->prune_data) { - diff_tree_setup_paths(revs->prune_data, &revs->diffopt); + diff_tree_setup_paths(revs->prune_data, &revs->pruning); revs->prune_fn = try_to_simplify_commit; + if (!revs->full_diff) + diff_tree_setup_paths(revs->prune_data, &revs->diffopt); + } + if (revs->combine_merges) { + revs->ignore_merges = 0; + if (revs->dense_combined_merges && + (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT)) + revs->diffopt.output_format = DIFF_FORMAT_PATCH; } + revs->diffopt.abbrev = revs->abbrev; + diff_setup_done(&revs->diffopt); return left; } void prepare_revision_walk(struct rev_info *revs) { - sort_by_date(&revs->commits); + struct object_list *list; + + list = revs->pending_objects; + revs->pending_objects = NULL; + while (list) { + struct commit *commit = handle_commit(revs, list->item, list->name); + if (commit) { + if (!(commit->object.flags & SEEN)) { + commit->object.flags |= SEEN; + insert_by_date(commit, &revs->commits); + } + } + list = list->next; + } + + if (revs->no_walk) + return; if (revs->limited) limit_list(revs); if (revs->topo_order) diff --git a/revision.h b/revision.h index 4b27043510..48d7b4ca94 100644 --- a/revision.h +++ b/revision.h @@ -11,6 +11,7 @@ #define ADDED (1u<<7) /* Parents already parsed and added? */ struct rev_info; +struct log_info; typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); @@ -27,6 +28,7 @@ struct rev_info { /* Traversal flags */ unsigned int dense:1, no_merges:1, + no_walk:1, remove_empty_trees:1, lifo:1, topo_order:1, @@ -39,13 +41,32 @@ struct rev_info { boundary:1, parents:1; + /* Diff flags */ + unsigned int diff:1, + full_diff:1, + show_root_diff:1, + no_commit_id:1, + verbose_header:1, + ignore_merges:1, + combine_merges:1, + dense_combined_merges:1, + always_show_header:1; + + /* Format info */ + unsigned int shown_one:1, + abbrev_commit:1; + unsigned int abbrev; + enum cmit_fmt commit_format; + struct log_info *loginfo; + /* special limits */ int max_count; unsigned long max_age; unsigned long min_age; - /* paths limiting */ + /* diff info for patches and for paths limiting */ struct diff_options diffopt; + struct diff_options pruning; topo_sort_set_fn_t topo_setter; topo_sort_get_fn_t topo_getter; @@ -80,11 +80,31 @@ void verify_filename(const char *prefix, const char *arg) if (!lstat(name, &st)) return; if (errno == ENOENT) - die("ambiguous argument '%s': unknown revision or filename\n" - "Use '--' to separate filenames from revisions", arg); + die("ambiguous argument '%s': unknown revision or path not in the working tree.\n" + "Use '--' to separate paths from revisions", arg); die("'%s': %s", arg, strerror(errno)); } +/* + * Opposite of the above: the command line did not have -- marker + * and we parsed the arg as a refname. It should not be interpretable + * as a filename. + */ +void verify_non_filename(const char *prefix, const char *arg) +{ + const char *name; + struct stat st; + + if (*arg == '-') + return; /* flag */ + name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg; + if (!lstat(name, &st)) + die("ambiguous argument '%s': both revision and filename\n" + "Use '--' to separate filenames from revisions", arg); + if (errno != ENOENT) + die("'%s': %s", arg, strerror(errno)); +} + const char **get_pathspec(const char *prefix, const char **pathspec) { const char *entry = *pathspec; diff --git a/sha1_name.c b/sha1_name.c index 4f92e12a8d..345935bb2b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -3,6 +3,7 @@ #include "commit.h" #include "tree.h" #include "blob.h" +#include "tree-walk.h" static int find_short_object_filename(int len, const char *name, unsigned char *sha1) { @@ -455,6 +456,19 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) */ int get_sha1(const char *name, unsigned char *sha1) { + int ret; + unsigned unused; + prepare_alt_odb(); - return get_sha1_1(name, strlen(name), sha1); + ret = get_sha1_1(name, strlen(name), sha1); + if (ret < 0) { + const char *cp = strchr(name, ':'); + if (cp) { + unsigned char tree_sha1[20]; + if (!get_sha1_1(name, cp-name, tree_sha1)) + return get_tree_entry(tree_sha1, cp+1, sha1, + &unused); + } + } + return ret; } diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6729a18266..cf33989b56 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -174,6 +174,27 @@ test_expect_success \ 'git-ls-tree -r output for a known tree.' \ 'diff current expected' +# But with -r -t we can have both. +test_expect_success \ + 'showing tree with git-ls-tree -r -t' \ + 'git-ls-tree -r -t $tree >current' +cat >expected <<\EOF +100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 +120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym +040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 +100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 +120000 blob d8ce161addc5173867a3c3c730924388daedbc38 path2/file2sym +040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 +100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376 path3/file3 +120000 blob 8599103969b43aff7e430efea79ca4636466794f path3/file3sym +040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 path3/subp3 +100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 +120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym +EOF +test_expect_success \ + 'git-ls-tree -r output for a known tree.' \ + 'diff current expected' + ################################################################ rm .git/index test_expect_success \ @@ -205,4 +226,32 @@ test_expect_success \ 'no diff after checkout and git-update-index --refresh.' \ 'git-diff-files >current && cmp -s current /dev/null' +################################################################ +P=087704a96baf1c2d1c869a8b084481e121c88b5b +test_expect_success \ + 'git-commit-tree records the correct tree in a commit.' \ + 'commit0=$(echo NO | git-commit-tree $P) && + tree=$(git show --pretty=raw $commit0 | + sed -n -e "s/^tree //p" -e "/^author /q") && + test "z$tree" = "z$P"' + +test_expect_success \ + 'git-commit-tree records the correct parent in a commit.' \ + 'commit1=$(echo NO | git-commit-tree $P -p $commit0) && + parent=$(git show --pretty=raw $commit1 | + sed -n -e "s/^parent //p" -e "/^author /q") && + test "z$commit0" = "z$parent"' + +test_expect_success \ + 'git-commit-tree omits duplicated parent in a commit.' \ + 'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) && + parent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + sort -u) && + test "z$commit0" = "z$parent" && + numparent=$(git show --pretty=raw $commit2 | + sed -n -e "s/^parent //p" -e "/^author /q" | + wc -l) && + test $numparent = 1' + test_done diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index d0ed24275e..75e4c9a886 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -37,7 +37,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 861ef4c0c6..4d175d8ea1 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -20,7 +20,7 @@ compare_change () { } check_cache_at () { - clean_if_empty=`git-diff-files "$1"` + clean_if_empty=`git-diff-files -- "$1"` case "$clean_if_empty" in '') echo "$1: clean" ;; ?*) echo "$1: dirty" ;; diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index f4d53c078a..c7db20e7f3 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -49,7 +49,7 @@ test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output' #test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\"" cat > whatchanged.expect << EOF -diff-tree VARIABLE (from root) +commit VARIABLE Author: VARIABLE Date: VARIABLE @@ -72,7 +72,7 @@ index 0000000..557db03 EOF git-whatchanged -p --root | \ - sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \ + sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \ -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \ > whatchanged.output test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output' diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 8db329d7ff..9e1544df9d 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -28,7 +28,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path should show nothing' \ - 'git-diff-index --cached $tree path >current && + 'git-diff-index --cached $tree -- path >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -36,7 +36,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1 should show path1/file1' \ - 'git-diff-index --cached $tree path1 >current && + 'git-diff-index --cached $tree -- path1 >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -44,7 +44,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to path1/ should show path1/file1' \ - 'git-diff-index --cached $tree path1/ >current && + 'git-diff-index --cached $tree -- path1/ >current && compare_diff_raw current expected' cat >expected <<\EOF @@ -52,14 +52,14 @@ cat >expected <<\EOF EOF test_expect_success \ 'limit to file0 should show file0' \ - 'git-diff-index --cached $tree file0 >current && + 'git-diff-index --cached $tree -- file0 >current && compare_diff_raw current expected' cat >expected <<\EOF EOF test_expect_success \ 'limit to file0/ should emit nothing.' \ - 'git-diff-index --cached $tree file0/ >current && + 'git-diff-index --cached $tree -- file0/ >current && compare_diff_raw current expected' test_done diff --git a/tree-walk.c b/tree-walk.c index bf8bfdfdf8..9f7abb7cb3 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -115,3 +115,53 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb free(entry); } +static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) +{ + int namelen = strlen(name); + while (t->size) { + const char *entry; + const unsigned char *sha1; + int entrylen, cmp; + + sha1 = tree_entry_extract(t, &entry, mode); + update_tree_entry(t); + entrylen = strlen(entry); + if (entrylen > namelen) + continue; + cmp = memcmp(name, entry, entrylen); + if (cmp > 0) + continue; + if (cmp < 0) + break; + if (entrylen == namelen) { + memcpy(result, sha1, 20); + return 0; + } + if (name[entrylen] != '/') + continue; + if (!S_ISDIR(*mode)) + break; + if (++entrylen == namelen) { + memcpy(result, sha1, 20); + return 0; + } + return get_tree_entry(sha1, name + entrylen, result, mode); + } + return -1; +} + +int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode) +{ + int retval; + void *tree; + struct tree_desc t; + + tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL); + if (!tree) + return -1; + t.buf = tree; + retval = find_tree_entry(&t, name, sha1, mode); + free(tree); + return retval; +} + diff --git a/tree-walk.h b/tree-walk.h index 76893e36c3..47438fe1c0 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -22,4 +22,6 @@ typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback); +int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); + #endif diff --git a/update-index.c b/update-index.c index 1efac27c6b..facec8d915 100644 --- a/update-index.c +++ b/update-index.c @@ -6,6 +6,7 @@ #include "cache.h" #include "strbuf.h" #include "quote.h" +#include "tree-walk.h" /* * Default to not allowing changes to the list of files. The @@ -328,7 +329,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, return 0; } -static int chmod_path(int flip, const char *path) +static void chmod_path(int flip, const char *path) { int pos; struct cache_entry *ce; @@ -336,21 +337,24 @@ static int chmod_path(int flip, const char *path) pos = cache_name_pos(path, strlen(path)); if (pos < 0) - return -1; + goto fail; ce = active_cache[pos]; mode = ntohl(ce->ce_mode); if (!S_ISREG(mode)) - return -1; + goto fail; switch (flip) { case '+': ce->ce_mode |= htonl(0111); break; case '-': ce->ce_mode &= htonl(~0111); break; default: - return -1; + goto fail; } active_cache_changed = 1; - return 0; + report("chmod %cx '%s'", flip, path); + return; + fail: + die("git-update-index: cannot chmod %cx '%s'", flip, path); } static struct cache_file cache_file; @@ -471,6 +475,124 @@ static void read_index_info(int line_termination) static const char update_index_usage[] = "git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>..."; +static unsigned char head_sha1[20]; +static unsigned char merge_head_sha1[20]; + +static struct cache_entry *read_one_ent(const char *which, + unsigned char *ent, const char *path, + int namelen, int stage) +{ + unsigned mode; + unsigned char sha1[20]; + int size; + struct cache_entry *ce; + + if (get_tree_entry(ent, path, sha1, &mode)) { + error("%s: not in %s branch.", path, which); + return NULL; + } + if (mode == S_IFDIR) { + error("%s: not a blob in %s branch.", path, which); + return NULL; + } + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + + memcpy(ce->sha1, sha1, 20); + memcpy(ce->name, path, namelen); + ce->ce_flags = create_ce_flags(namelen, stage); + ce->ce_mode = create_ce_mode(mode); + return ce; +} + +static int unresolve_one(const char *path) +{ + int namelen = strlen(path); + int pos; + int ret = 0; + struct cache_entry *ce_2 = NULL, *ce_3 = NULL; + + /* See if there is such entry in the index. */ + pos = cache_name_pos(path, namelen); + if (pos < 0) { + /* If there isn't, either it is unmerged, or + * resolved as "removed" by mistake. We do not + * want to do anything in the former case. + */ + pos = -pos-1; + if (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_namelen(ce) == namelen && + !memcmp(ce->name, path, namelen)) { + fprintf(stderr, + "%s: skipping still unmerged path.\n", + path); + goto free_return; + } + } + } + + /* Grab blobs from given path from HEAD and MERGE_HEAD, + * stuff HEAD version in stage #2, + * stuff MERGE_HEAD version in stage #3. + */ + ce_2 = read_one_ent("our", head_sha1, path, namelen, 2); + ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3); + + if (!ce_2 || !ce_3) { + ret = -1; + goto free_return; + } + if (!memcmp(ce_2->sha1, ce_3->sha1, 20) && + ce_2->ce_mode == ce_3->ce_mode) { + fprintf(stderr, "%s: identical in both, skipping.\n", + path); + goto free_return; + } + + remove_file_from_cache(path); + if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { + error("%s: cannot add our version to the index.", path); + ret = -1; + goto free_return; + } + if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD)) + return 0; + error("%s: cannot add their version to the index.", path); + ret = -1; + free_return: + free(ce_2); + free(ce_3); + return ret; +} + +static void read_head_pointers(void) +{ + if (read_ref(git_path("HEAD"), head_sha1)) + die("No HEAD -- no initial commit yet?\n"); + if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { + fprintf(stderr, "Not in the middle of a merge.\n"); + exit(0); + } +} + +static int do_unresolve(int ac, const char **av) +{ + int i; + int err = 0; + + /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we + * are not doing a merge, so exit with success status. + */ + read_head_pointers(); + + for (i = 1; i < ac; i++) { + const char *arg = av[i]; + err |= unresolve_one(arg); + } + return err; +} + int main(int argc, const char **argv) { int i, newfd, entries, has_errors = 0, line_termination = '\n'; @@ -478,6 +600,7 @@ int main(int argc, const char **argv) int read_from_stdin = 0; const char *prefix = setup_git_directory(); int prefix_length = prefix ? strlen(prefix) : 0; + char set_executable_bit = 0; git_config(git_default_config); @@ -544,8 +667,7 @@ int main(int argc, const char **argv) !strcmp(path, "--chmod=+x")) { if (argc <= i+1) die("git-update-index: %s <path>", path); - if (chmod_path(path[8], argv[++i])) - die("git-update-index: %s cannot chmod %s", path, argv[i]); + set_executable_bit = path[8]; continue; } if (!strcmp(path, "--assume-unchanged")) { @@ -581,6 +703,12 @@ int main(int argc, const char **argv) read_index_info(line_termination); break; } + if (!strcmp(path, "--unresolve")) { + has_errors = do_unresolve(argc - i, argv + i); + if (has_errors) + active_cache_changed = 0; + goto finish; + } if (!strcmp(path, "--ignore-missing")) { not_new = 1; continue; @@ -594,6 +722,8 @@ int main(int argc, const char **argv) die("unknown option %s", path); } update_one(path, prefix, prefix_length); + if (set_executable_bit) + chmod_path(set_executable_bit, path); } if (read_from_stdin) { struct strbuf buf; @@ -608,10 +738,16 @@ int main(int argc, const char **argv) else path_name = buf.buf; update_one(path_name, prefix, prefix_length); + if (set_executable_bit) { + const char *p = prefix_path(prefix, prefix_length, path_name); + chmod_path(set_executable_bit, p); + } if (path_name != buf.buf) free(path_name); } } + + finish: if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || commit_index_file(&cache_file)) |