From 38bd64979a2a3ffa178af801c6a62e6fcd658274 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Mon, 21 Jul 2008 11:23:43 +0200 Subject: Enable threaded delta search on *BSD and Linux. Signed-off-by: Pierre Habouzit Signed-off-by: Junio C Hamano --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 551bde9ff0..82f89b7b64 100644 --- a/Makefile +++ b/Makefile @@ -565,9 +565,11 @@ EXTLIBS = ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -665,6 +667,7 @@ ifeq ($(uname_S),FreeBSD) BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -672,6 +675,7 @@ ifeq ($(uname_S),OpenBSD) NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -680,6 +684,7 @@ ifeq ($(uname_S),NetBSD) BASIC_CFLAGS += -I/usr/pkg/include BASIC_LDFLAGS += -L/usr/pkg/lib ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib + THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease -- cgit v1.2.1 From 041aee31be378b3b38e3a0913b29970a7f78873b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 21 Jul 2008 01:24:17 -0700 Subject: builtin-add.c: restructure the code for maintainability A private function add_files_to_cache() in builtin-add.c was borrowed by checkout and commit re-implementors without getting properly refactored to more library-ish place. This does the refactoring. Signed-off-by: Junio C Hamano --- builtin-add.c | 57 ------------------------------------------------------- cache.h | 1 + read-cache.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 57 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index fc3f96eaef..0de516ad95 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -8,10 +8,6 @@ #include "dir.h" #include "exec_cmd.h" #include "cache-tree.h" -#include "diff.h" -#include "diffcore.h" -#include "commit.h" -#include "revision.h" #include "run-command.h" #include "parse-options.h" @@ -79,59 +75,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec, prune_directory(dir, pathspec, baselen); } -struct update_callback_data -{ - int flags; - int add_errors; -}; - -static void update_callback(struct diff_queue_struct *q, - struct diff_options *opt, void *cbdata) -{ - int i; - struct update_callback_data *data = cbdata; - - for (i = 0; i < q->nr; i++) { - struct diff_filepair *p = q->queue[i]; - const char *path = p->one->path; - switch (p->status) { - default: - die("unexpected diff status %c", p->status); - case DIFF_STATUS_UNMERGED: - case DIFF_STATUS_MODIFIED: - case DIFF_STATUS_TYPE_CHANGED: - if (add_file_to_cache(path, data->flags)) { - if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) - die("updating files failed"); - data->add_errors++; - } - break; - case DIFF_STATUS_DELETED: - if (!(data->flags & ADD_CACHE_PRETEND)) - remove_file_from_cache(path); - if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) - printf("remove '%s'\n", path); - break; - } - } -} - -int add_files_to_cache(const char *prefix, const char **pathspec, int flags) -{ - struct update_callback_data data; - struct rev_info rev; - init_revisions(&rev, prefix); - setup_revisions(0, NULL, &rev, NULL); - rev.prune_data = pathspec; - rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; - rev.diffopt.format_callback_data = &data; - run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - return !!data.add_errors; -} - static void refresh(int verbose, const char **pathspec) { char *seen; diff --git a/cache.h b/cache.h index 38985aa63e..6f374add88 100644 --- a/cache.h +++ b/cache.h @@ -375,6 +375,7 @@ extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_VERBOSE 1 #define ADD_CACHE_PRETEND 2 #define ADD_CACHE_IGNORE_ERRORS 4 +#define ADD_CACHE_IGNORE_REMOVAL 8 extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); diff --git a/read-cache.c b/read-cache.c index a50a851125..6833af6cf1 100644 --- a/read-cache.c +++ b/read-cache.c @@ -8,6 +8,11 @@ #include "cache-tree.h" #include "refs.h" #include "dir.h" +#include "tree.h" +#include "commit.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" /* Index extensions. * @@ -1444,3 +1449,59 @@ int read_index_unmerged(struct index_state *istate) istate->cache_nr = dst - istate->cache; return !!last; } + +struct update_callback_data +{ + int flags; + int add_errors; +}; + +static void update_callback(struct diff_queue_struct *q, + struct diff_options *opt, void *cbdata) +{ + int i; + struct update_callback_data *data = cbdata; + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + const char *path = p->one->path; + switch (p->status) { + default: + die("unexpected diff status %c", p->status); + case DIFF_STATUS_UNMERGED: + case DIFF_STATUS_MODIFIED: + case DIFF_STATUS_TYPE_CHANGED: + if (add_file_to_index(&the_index, path, data->flags)) { + if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) + die("updating files failed"); + data->add_errors++; + } + break; + case DIFF_STATUS_DELETED: + if (data->flags & ADD_CACHE_IGNORE_REMOVAL) + break; + if (!(data->flags & ADD_CACHE_PRETEND)) + remove_file_from_index(&the_index, path); + if (data->flags & (ADD_CACHE_PRETEND|ADD_CACHE_VERBOSE)) + printf("remove '%s'\n", path); + break; + } + } +} + +int add_files_to_cache(const char *prefix, const char **pathspec, int flags) +{ + struct update_callback_data data; + struct rev_info rev; + init_revisions(&rev, prefix); + setup_revisions(0, NULL, &rev, NULL); + rev.prune_data = pathspec; + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = update_callback; + data.flags = flags; + data.add_errors = 0; + rev.diffopt.format_callback_data = &data; + run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); + return !!data.add_errors; +} + -- cgit v1.2.1 From 1e5f764c93edfd8f6575b6ede769b079a1fc5a21 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 22 Jul 2008 22:30:40 -0700 Subject: builtin-add.c: optimize -A option and "git add ." The earlier "git add -A" change was done in a quite inefficient way (i.e. it is as unefficient as "git add -u && git add ." modulo one fork/exec and read/write index). When the user asks "git add .", we do not have to examine all paths we encounter and perform the excluded() and dir_add_name() processing, both of which are slower code and use slower data structure by git standards, especially when the index is already populated. Instead, we implement "git add $pathspec..." as: - read the index; - read_directory() to process untracked, unignored files the current way, that is, recursively doing readdir(), filtering them by pathspec and excluded(), queueing them via dir_add_name() and finally do add_files(); and - iterate over the index, filtering them by pathspec, and update only the modified/type changed paths but not deleted ones. And "git add -A" becomes exactly the same as above, modulo: - missing $pathspec means "." instead of being an error; and - "iterate over the index" part handles deleted ones as well, i.e. exactly what the current update_callback() in builtin-add.c does. In either case, because fill_directory() does not use read_directory() to read everything in, we need to add an extra logic to iterate over the index to catch mistyped pathspec. Signed-off-by: Junio C Hamano --- builtin-add.c | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index 0de516ad95..1834e2d7cd 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -18,6 +18,27 @@ static const char * const builtin_add_usage[] = { static int patch_interactive = 0, add_interactive = 0; static int take_worktree_changes; +static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) +{ + int num_unmatched = 0, i; + + /* + * Since we are walking the index as if we are warlking the directory, + * we have to mark the matched pathspec as seen; otherwise we will + * mistakenly think that the user gave a pathspec that did not match + * anything. + */ + for (i = 0; i < specs; i++) + if (!seen[i]) + num_unmatched++; + if (!num_unmatched) + return; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); + } +} + static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { char *seen; @@ -37,6 +58,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p *dst++ = entry; } dir->nr = dst - dir->entries; + fill_pathspec_matches(pathspec, seen, specs); for (i = 0; i < specs; i++) { if (!seen[i] && !file_exists(pathspec[i])) @@ -201,7 +223,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (addremove && take_worktree_changes) die("-A and -u are mutually incompatible"); - if (addremove && !argc) { + if ((addremove || take_worktree_changes) && !argc) { static const char *here[2] = { ".", NULL }; argc = 1; argv = here; @@ -214,7 +236,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | - (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0)); + (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | + (!(addremove || take_worktree_changes) + ? ADD_CACHE_IGNORE_REMOVAL : 0)); if (require_pathspec && argc == 0) { fprintf(stderr, "Nothing specified, nothing added.\n"); @@ -223,24 +247,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) } pathspec = get_pathspec(prefix, argv); - /* - * If we are adding new files, we need to scan the working - * tree to find the ones that match pathspecs; this needs - * to be done before we read the index. - */ - if (add_new_files) - fill_directory(&dir, pathspec, ignored_too); - if (read_cache() < 0) die("index file corrupt"); + if (add_new_files) + /* This picks up the paths that are not tracked */ + fill_directory(&dir, pathspec, ignored_too); + if (refresh_only) { refresh(verbose, pathspec); goto finish; } - if (take_worktree_changes || addremove) - exit_status |= add_files_to_cache(prefix, pathspec, flags); + exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); -- cgit v1.2.1 From ccf08bc3d06050fbe9b76846f6e2ab6d1cd6bd09 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 22 Jul 2008 03:12:46 -0400 Subject: run-command: add pre-exec callback This is a function provided by the caller which is called _after_ the process is forked, but before the spawned program is executed. On platforms (like mingw) where subprocesses are forked and executed in a single call, the preexec callback is simply ignored. This will be used in the following patch to do some setup for 'less' that must happen in the forked child. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- run-command.c | 2 ++ run-command.h | 1 + 2 files changed, 3 insertions(+) diff --git a/run-command.c b/run-command.c index 6e29fdf9e2..73d0c31276 100644 --- a/run-command.c +++ b/run-command.c @@ -110,6 +110,8 @@ int start_command(struct child_process *cmd) unsetenv(*cmd->env); } } + if (cmd->preexec_cb) + cmd->preexec_cb(); if (cmd->git_cmd) { execv_git_cmd(cmd->argv); } else { diff --git a/run-command.h b/run-command.h index 5203a9ebb1..4f2b7d7d40 100644 --- a/run-command.h +++ b/run-command.h @@ -42,6 +42,7 @@ struct child_process { unsigned no_stderr:1; unsigned git_cmd:1; /* if this is to be git sub-command */ unsigned stdout_to_stderr:1; + void (*preexec_cb)(void); }; int start_command(struct child_process *); -- cgit v1.2.1 From ea27a18ce2bf5860974745c04c24864231029e1d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 22 Jul 2008 03:14:12 -0400 Subject: spawn pager via run_command interface This has two important effects: 1. The pager is now the _child_ process, instead of the parent. This means that whatever spawned git (e.g., the shell) will see the exit code of the git process, and not the pager. 2. The mingw and regular code are now unified, which makes the setup_pager function much simpler. There are two caveats: 1. We used to call execlp directly on the pager, followed by trying to exec it via the shall. We now just use the shell (which is what mingw has always done). This may have different results for pager names which contain shell metacharacters. It is also slightly less efficient because we unnecessarily run the shell; however, pager spawning is by definition an interactive task, so it shouldn't be a huge problem. 2. The git process will remain in memory while the user looks through the pager. This is potentially wasteful. We could get around this by turning the parent into a meta-process which spawns _both_ git and the pager, collects the exit status from git, waits for both to end, and then exits with git's exit code. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- pager.c | 55 +++++++++++-------------------------------------------- 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/pager.c b/pager.c index 6b5c9e44b4..aa0966c9c5 100644 --- a/pager.c +++ b/pager.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "run-command.h" /* * This is split up from the rest of git so that we can do @@ -8,7 +9,7 @@ static int spawned_pager; #ifndef __MINGW32__ -static void run_pager(const char *pager) +static void pager_preexec(void) { /* * Work around bug in "less" by not starting it until we @@ -20,17 +21,13 @@ static void run_pager(const char *pager) FD_SET(0, &in); select(1, &in, NULL, &in, NULL); - execlp(pager, pager, NULL); - execl("/bin/sh", "sh", "-c", pager, NULL); + setenv("LESS", "FRSX", 0); } -#else -#include "run-command.h" +#endif static const char *pager_argv[] = { "sh", "-c", NULL, NULL }; -static struct child_process pager_process = { - .argv = pager_argv, - .in = -1 -}; +static struct child_process pager_process; + static void wait_for_pager(void) { fflush(stdout); @@ -40,14 +37,9 @@ static void wait_for_pager(void) close(2); finish_command(&pager_process); } -#endif void setup_pager(void) { -#ifndef __MINGW32__ - pid_t pid; - int fd[2]; -#endif const char *pager = getenv("GIT_PAGER"); if (!isatty(1)) @@ -66,37 +58,13 @@ void setup_pager(void) spawned_pager = 1; /* means we are emitting to terminal */ -#ifndef __MINGW32__ - if (pipe(fd) < 0) - return; - pid = fork(); - if (pid < 0) { - close(fd[0]); - close(fd[1]); - return; - } - - /* return in the child */ - if (!pid) { - dup2(fd[1], 1); - dup2(fd[1], 2); - close(fd[0]); - close(fd[1]); - return; - } - - /* The original process turns into the PAGER */ - dup2(fd[0], 0); - close(fd[0]); - close(fd[1]); - - setenv("LESS", "FRSX", 0); - run_pager(pager); - die("unable to execute pager '%s'", pager); - exit(255); -#else /* spawn the pager */ pager_argv[2] = pager; + pager_process.argv = pager_argv; + pager_process.in = -1; +#ifndef __MINGW32__ + pager_process.preexec_cb = pager_preexec; +#endif if (start_command(&pager_process)) return; @@ -107,7 +75,6 @@ void setup_pager(void) /* this makes sure that the parent terminates after the pager */ atexit(wait_for_pager); -#endif } int pager_in_use(void) -- cgit v1.2.1 From a0406b94d57e8443a7b31b5412763cac4ddb5d21 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Wed, 9 Jul 2008 22:28:59 +0100 Subject: git-imap-send: Allow the program to be run from subdirectories of a git tree Call setup_git_directory_gently to allow git-imap-send to be used from subdirectories of a git tree. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano --- imap-send.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imap-send.c b/imap-send.c index 1ec1310921..24d76a7031 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1292,10 +1292,12 @@ main(int argc, char **argv) int ofs = 0; int r; int total, n = 0; + int nongit_ok; /* init the random number generator */ arc4_init(); + setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, NULL); if (!imap_folder) { -- cgit v1.2.1 From 684ec6c63cd92a3755500b3b63f3d5ad9f6828b2 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Wed, 9 Jul 2008 22:29:00 +0100 Subject: git-imap-send: Support SSL Allow SSL to be used when a imaps:// URL is used for the host name. Also, automatically use TLS when not using imaps:// by using the IMAP STARTTLS command, if the server supports it. Tested with Courier and Gimap IMAP servers. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano --- Documentation/git-imap-send.txt | 3 +- Makefile | 4 +- git-compat-util.h | 5 ++ imap-send.c | 160 ++++++++++++++++++++++++++++++++++++---- 4 files changed, 156 insertions(+), 16 deletions(-) diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index b3d8da33ee..136c82bfdd 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -37,10 +37,11 @@ configuration file (shown with examples): Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null" [imap] - Host = imap.server.com + Host = imap://imap.example.com User = bob Pass = pwd Port = 143 + sslverify = false .......................... diff --git a/Makefile b/Makefile index 798a2f2f77..bb2a988288 100644 --- a/Makefile +++ b/Makefile @@ -1208,7 +1208,9 @@ endif git-%$X: %.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) -git-imap-send$X: imap-send.o $(LIB_FILE) +git-imap-send$X: imap-send.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) http.o http-walker.o http-push.o transport.o: http.h diff --git a/git-compat-util.h b/git-compat-util.h index cf89cdf459..fbf791a63b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -99,6 +99,11 @@ #include #endif +#ifndef NO_OPENSSL +#include +#include +#endif + /* On most systems would have given us this, but * not on some systems (e.g. GNU/Hurd). */ diff --git a/imap-send.c b/imap-send.c index 24d76a7031..802633494d 100644 --- a/imap-send.c +++ b/imap-send.c @@ -23,6 +23,9 @@ */ #include "cache.h" +#ifdef NO_OPENSSL +typedef void *SSL; +#endif typedef struct store_conf { char *name; @@ -129,6 +132,8 @@ typedef struct imap_server_conf { int port; char *user; char *pass; + int use_ssl; + int ssl_verify; } imap_server_conf_t; typedef struct imap_store_conf { @@ -148,6 +153,7 @@ typedef struct _list { typedef struct { int fd; + SSL *ssl; } Socket_t; typedef struct { @@ -201,6 +207,7 @@ enum CAPABILITY { UIDPLUS, LITERALPLUS, NAMESPACE, + STARTTLS, }; static const char *cap_list[] = { @@ -208,6 +215,7 @@ static const char *cap_list[] = { "UIDPLUS", "LITERAL+", "NAMESPACE", + "STARTTLS", }; #define RESP_OK 0 @@ -225,19 +233,101 @@ static const char *Flags[] = { "Deleted", }; +#ifndef NO_OPENSSL +static void ssl_socket_perror(const char *func) +{ + fprintf(stderr, "%s: %s\n", func, ERR_error_string(ERR_get_error(), 0)); +} +#endif + static void socket_perror( const char *func, Socket_t *sock, int ret ) { - if (ret < 0) - perror( func ); +#ifndef NO_OPENSSL + if (sock->ssl) { + int sslerr = SSL_get_error(sock->ssl, ret); + switch (sslerr) { + case SSL_ERROR_NONE: + break; + case SSL_ERROR_SYSCALL: + perror("SSL_connect"); + break; + default: + ssl_socket_perror("SSL_connect"); + break; + } + } else +#endif + { + if (ret < 0) + perror(func); + else + fprintf(stderr, "%s: unexpected EOF\n", func); + } +} + +static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify) +{ +#ifdef NO_OPENSSL + fprintf(stderr, "SSL requested but SSL support not compiled in\n"); + return -1; +#else + SSL_METHOD *meth; + SSL_CTX *ctx; + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + + if (use_tls_only) + meth = TLSv1_method(); else - fprintf( stderr, "%s: unexpected EOF\n", func ); + meth = SSLv23_method(); + + if (!meth) { + ssl_socket_perror("SSLv23_method"); + return -1; + } + + ctx = SSL_CTX_new(meth); + + if (verify) + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + + if (!SSL_CTX_set_default_verify_paths(ctx)) { + ssl_socket_perror("SSL_CTX_set_default_verify_paths"); + return -1; + } + sock->ssl = SSL_new(ctx); + if (!sock->ssl) { + ssl_socket_perror("SSL_new"); + return -1; + } + if (!SSL_set_fd(sock->ssl, sock->fd)) { + ssl_socket_perror("SSL_set_fd"); + return -1; + } + + ret = SSL_connect(sock->ssl); + if (ret <= 0) { + socket_perror("SSL_connect", sock, ret); + return -1; + } + + return 0; +#endif } static int socket_read( Socket_t *sock, char *buf, int len ) { - ssize_t n = xread( sock->fd, buf, len ); + ssize_t n; +#ifndef NO_OPENSSL + if (sock->ssl) + n = SSL_read(sock->ssl, buf, len); + else +#endif + n = xread( sock->fd, buf, len ); if (n <= 0) { socket_perror( "read", sock, n ); close( sock->fd ); @@ -249,7 +339,13 @@ socket_read( Socket_t *sock, char *buf, int len ) static int socket_write( Socket_t *sock, const char *buf, int len ) { - int n = write_in_full( sock->fd, buf, len ); + int n; +#ifndef NO_OPENSSL + if (sock->ssl) + n = SSL_write(sock->ssl, buf, len); + else +#endif + n = write_in_full( sock->fd, buf, len ); if (n != len) { socket_perror( "write", sock, n ); close( sock->fd ); @@ -258,6 +354,17 @@ socket_write( Socket_t *sock, const char *buf, int len ) return n; } +static void socket_shutdown(Socket_t *sock) +{ +#ifndef NO_OPENSSL + if (sock->ssl) { + SSL_shutdown(sock->ssl); + SSL_free(sock->ssl); + } +#endif + close(sock->fd); +} + /* simple line buffering */ static int buffer_gets( buffer_t * b, char **s ) @@ -875,7 +982,7 @@ imap_close_server( imap_store_t *ictx ) if (imap->buf.sock.fd != -1) { imap_exec( ictx, NULL, "LOGOUT" ); - close( imap->buf.sock.fd ); + socket_shutdown( &imap->buf.sock ); } free_list( imap->ns_personal ); free_list( imap->ns_other ); @@ -958,10 +1065,15 @@ imap_open_store( imap_server_conf_t *srvc ) perror( "connect" ); goto bail; } - imap_info( "ok\n" ); imap->buf.sock.fd = s; + if (srvc->use_ssl && + ssl_socket_connect(&imap->buf.sock, 0, srvc->ssl_verify)) { + close(s); + goto bail; + } + imap_info( "ok\n" ); } /* read the greeting string */ @@ -986,7 +1098,18 @@ imap_open_store( imap_server_conf_t *srvc ) goto bail; if (!preauth) { - +#ifndef NO_OPENSSL + if (!srvc->use_ssl && CAP(STARTTLS)) { + if (imap_exec(ctx, 0, "STARTTLS") != RESP_OK) + goto bail; + if (ssl_socket_connect(&imap->buf.sock, 1, + srvc->ssl_verify)) + goto bail; + /* capabilities may have changed, so get the new capabilities */ + if (imap_exec(ctx, 0, "CAPABILITY") != RESP_OK) + goto bail; + } +#endif imap_info ("Logging in...\n"); if (!srvc->user) { fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); @@ -1014,7 +1137,9 @@ imap_open_store( imap_server_conf_t *srvc ) fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); goto bail; } - imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); + if (!imap->buf.sock.ssl) + imap_warn( "*** IMAP Warning *** Password is being " + "sent in the clear\n" ); if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { fprintf( stderr, "IMAP error: LOGIN failed\n" ); goto bail; @@ -1242,6 +1367,8 @@ static imap_server_conf_t server = 0, /* port */ NULL, /* user */ NULL, /* pass */ + 0, /* use_ssl */ + 1, /* ssl_verify */ }; static char *imap_folder; @@ -1262,11 +1389,11 @@ git_imap_config(const char *key, const char *val, void *cb) if (!strcmp( "folder", key )) { imap_folder = xstrdup( val ); } else if (!strcmp( "host", key )) { - { - if (!prefixcmp(val, "imap:")) - val += 5; - if (!server.port) - server.port = 143; + if (!prefixcmp(val, "imap:")) + val += 5; + else if (!prefixcmp(val, "imaps:")) { + val += 6; + server.use_ssl = 1; } if (!prefixcmp(val, "//")) val += 2; @@ -1280,6 +1407,8 @@ git_imap_config(const char *key, const char *val, void *cb) server.port = git_config_int( key, val ); else if (!strcmp( "tunnel", key )) server.tunnel = xstrdup( val ); + else if (!strcmp( "sslverify", key )) + server.ssl_verify = git_config_bool( key, val ); return 0; } @@ -1300,6 +1429,9 @@ main(int argc, char **argv) setup_git_directory_gently(&nongit_ok); git_config(git_imap_config, NULL); + if (!server.port) + server.port = server.use_ssl ? 993 : 143; + if (!imap_folder) { fprintf( stderr, "no imap store specified\n" ); return 1; -- cgit v1.2.1 From 95c539081e8519e00155c284ac7519727bbacc67 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Wed, 9 Jul 2008 22:29:01 +0100 Subject: imap-send.c: style fixes Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano --- imap-send.c | 629 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 300 insertions(+), 329 deletions(-) diff --git a/imap-send.c b/imap-send.c index 802633494d..a7f7dfd22e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -99,14 +99,14 @@ typedef struct { static int Verbose, Quiet; -static void imap_info( const char *, ... ); -static void imap_warn( const char *, ... ); +static void imap_info(const char *, ...); +static void imap_warn(const char *, ...); -static char *next_arg( char ** ); +static char *next_arg(char **); -static void free_generic_messages( message_t * ); +static void free_generic_messages(message_t *); -static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); +static int nfsnprintf(char *buf, int blen, const char *fmt, ...); static int nfvasprintf(char **strp, const char *fmt, va_list ap) { @@ -122,8 +122,8 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) return len; } -static void arc4_init( void ); -static unsigned char arc4_getbyte( void ); +static void arc4_init(void); +static unsigned char arc4_getbyte(void); typedef struct imap_server_conf { char *name; @@ -184,8 +184,8 @@ typedef struct imap_store { } imap_store_t; struct imap_cmd_cb { - int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt ); - void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response); + int (*cont)(imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt); + void (*done)(imap_store_t *ctx, struct imap_cmd *cmd, int response); void *ctx; char *data; int dlen; @@ -222,7 +222,7 @@ static const char *cap_list[] = { #define RESP_NO 1 #define RESP_BAD 2 -static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); +static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd); static const char *Flags[] = { @@ -240,8 +240,7 @@ static void ssl_socket_perror(const char *func) } #endif -static void -socket_perror( const char *func, Socket_t *sock, int ret ) +static void socket_perror(const char *func, Socket_t *sock, int ret) { #ifndef NO_OPENSSL if (sock->ssl) { @@ -318,8 +317,7 @@ static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify) #endif } -static int -socket_read( Socket_t *sock, char *buf, int len ) +static int socket_read(Socket_t *sock, char *buf, int len) { ssize_t n; #ifndef NO_OPENSSL @@ -327,17 +325,16 @@ socket_read( Socket_t *sock, char *buf, int len ) n = SSL_read(sock->ssl, buf, len); else #endif - n = xread( sock->fd, buf, len ); + n = xread(sock->fd, buf, len); if (n <= 0) { - socket_perror( "read", sock, n ); - close( sock->fd ); + socket_perror("read", sock, n); + close(sock->fd); sock->fd = -1; } return n; } -static int -socket_write( Socket_t *sock, const char *buf, int len ) +static int socket_write(Socket_t *sock, const char *buf, int len) { int n; #ifndef NO_OPENSSL @@ -345,10 +342,10 @@ socket_write( Socket_t *sock, const char *buf, int len ) n = SSL_write(sock->ssl, buf, len); else #endif - n = write_in_full( sock->fd, buf, len ); + n = write_in_full(sock->fd, buf, len); if (n != len) { - socket_perror( "write", sock, n ); - close( sock->fd ); + socket_perror("write", sock, n); + close(sock->fd); sock->fd = -1; } return n; @@ -366,8 +363,7 @@ static void socket_shutdown(Socket_t *sock) } /* simple line buffering */ -static int -buffer_gets( buffer_t * b, char **s ) +static int buffer_gets(buffer_t * b, char **s) { int n; int start = b->offset; @@ -381,7 +377,7 @@ buffer_gets( buffer_t * b, char **s ) /* shift down used bytes */ *s = b->buf; - assert( start <= b->bytes ); + assert(start <= b->bytes); n = b->bytes - start; if (n) @@ -391,8 +387,8 @@ buffer_gets( buffer_t * b, char **s ) start = 0; } - n = socket_read( &b->sock, b->buf + b->bytes, - sizeof(b->buf) - b->bytes ); + n = socket_read(&b->sock, b->buf + b->bytes, + sizeof(b->buf) - b->bytes); if (n <= 0) return -1; @@ -401,12 +397,12 @@ buffer_gets( buffer_t * b, char **s ) } if (b->buf[b->offset] == '\r') { - assert( b->offset + 1 < b->bytes ); + assert(b->offset + 1 < b->bytes); if (b->buf[b->offset + 1] == '\n') { b->buf[b->offset] = 0; /* terminate the string */ b->offset += 2; /* next line */ if (Verbose) - puts( *s ); + puts(*s); return 0; } } @@ -416,39 +412,36 @@ buffer_gets( buffer_t * b, char **s ) /* not reached */ } -static void -imap_info( const char *msg, ... ) +static void imap_info(const char *msg, ...) { va_list va; if (!Quiet) { - va_start( va, msg ); - vprintf( msg, va ); - va_end( va ); - fflush( stdout ); + va_start(va, msg); + vprintf(msg, va); + va_end(va); + fflush(stdout); } } -static void -imap_warn( const char *msg, ... ) +static void imap_warn(const char *msg, ...) { va_list va; if (Quiet < 2) { - va_start( va, msg ); - vfprintf( stderr, msg, va ); - va_end( va ); + va_start(va, msg); + vfprintf(stderr, msg, va); + va_end(va); } } -static char * -next_arg( char **s ) +static char *next_arg(char **s) { char *ret; if (!s || !*s) return NULL; - while (isspace( (unsigned char) **s )) + while (isspace((unsigned char) **s)) (*s)++; if (!**s) { *s = NULL; @@ -457,10 +450,10 @@ next_arg( char **s ) if (**s == '"') { ++*s; ret = *s; - *s = strchr( *s, '"' ); + *s = strchr(*s, '"'); } else { ret = *s; - while (**s && !isspace( (unsigned char) **s )) + while (**s && !isspace((unsigned char) **s)) (*s)++; } if (*s) { @@ -472,27 +465,25 @@ next_arg( char **s ) return ret; } -static void -free_generic_messages( message_t *msgs ) +static void free_generic_messages(message_t *msgs) { message_t *tmsg; for (; msgs; msgs = tmsg) { tmsg = msgs->next; - free( msgs ); + free(msgs); } } -static int -nfsnprintf( char *buf, int blen, const char *fmt, ... ) +static int nfsnprintf(char *buf, int blen, const char *fmt, ...) { int ret; va_list va; - va_start( va, fmt ); - if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) - die( "Fatal: buffer too small. Please report a bug.\n"); - va_end( va ); + va_start(va, fmt); + if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen) + die("Fatal: buffer too small. Please report a bug.\n"); + va_end(va); return ret; } @@ -500,21 +491,20 @@ static struct { unsigned char i, j, s[256]; } rs; -static void -arc4_init( void ) +static void arc4_init(void) { int i, fd; unsigned char j, si, dat[128]; - if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { - fprintf( stderr, "Fatal: no random number source available.\n" ); - exit( 3 ); + if ((fd = open("/dev/urandom", O_RDONLY)) < 0 && (fd = open("/dev/random", O_RDONLY)) < 0) { + fprintf(stderr, "Fatal: no random number source available.\n"); + exit(3); } - if (read_in_full( fd, dat, 128 ) != 128) { - fprintf( stderr, "Fatal: cannot read random number source.\n" ); - exit( 3 ); + if (read_in_full(fd, dat, 128) != 128) { + fprintf(stderr, "Fatal: cannot read random number source.\n"); + exit(3); } - close( fd ); + close(fd); for (i = 0; i < 256; i++) rs.s[i] = i; @@ -530,8 +520,7 @@ arc4_init( void ) arc4_getbyte(); } -static unsigned char -arc4_getbyte( void ) +static unsigned char arc4_getbyte(void) { unsigned char si, sj; @@ -544,54 +533,54 @@ arc4_getbyte( void ) return rs.s[(si + sj) & 0xff]; } -static struct imap_cmd * -v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, - const char *fmt, va_list ap ) +static struct imap_cmd *v_issue_imap_cmd(imap_store_t *ctx, + struct imap_cmd_cb *cb, + const char *fmt, va_list ap) { imap_t *imap = ctx->imap; struct imap_cmd *cmd; int n, bufl; char buf[1024]; - cmd = xmalloc( sizeof(struct imap_cmd) ); - nfvasprintf( &cmd->cmd, fmt, ap ); + cmd = xmalloc(sizeof(struct imap_cmd)); + nfvasprintf(&cmd->cmd, fmt, ap); cmd->tag = ++imap->nexttag; if (cb) cmd->cb = *cb; else - memset( &cmd->cb, 0, sizeof(cmd->cb) ); + memset(&cmd->cb, 0, sizeof(cmd->cb)); while (imap->literal_pending) - get_cmd_result( ctx, NULL ); + get_cmd_result(ctx, NULL); - bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? + bufl = nfsnprintf(buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", - cmd->tag, cmd->cmd, cmd->cb.dlen ); + cmd->tag, cmd->cmd, cmd->cb.dlen); if (Verbose) { if (imap->num_in_progress) - printf( "(%d in progress) ", imap->num_in_progress ); - if (memcmp( cmd->cmd, "LOGIN", 5 )) - printf( ">>> %s", buf ); + printf("(%d in progress) ", imap->num_in_progress); + if (memcmp(cmd->cmd, "LOGIN", 5)) + printf(">>> %s", buf); else - printf( ">>> %d LOGIN \n", cmd->tag ); + printf(">>> %d LOGIN \n", cmd->tag); } - if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) { - free( cmd->cmd ); - free( cmd ); + if (socket_write(&imap->buf.sock, buf, bufl) != bufl) { + free(cmd->cmd); + free(cmd); if (cb) - free( cb->data ); + free(cb->data); return NULL; } if (cmd->cb.data) { if (CAP(LITERALPLUS)) { - n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen ); - free( cmd->cb.data ); + n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen); + free(cmd->cb.data); if (n != cmd->cb.dlen || - (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2) + (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) { - free( cmd->cmd ); - free( cmd ); + free(cmd->cmd); + free(cmd); return NULL; } cmd->cb.data = NULL; @@ -606,109 +595,106 @@ v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, return cmd; } -static struct imap_cmd * -issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +static struct imap_cmd *issue_imap_cmd(imap_store_t *ctx, + struct imap_cmd_cb *cb, + const char *fmt, ...) { struct imap_cmd *ret; va_list ap; - va_start( ap, fmt ); - ret = v_issue_imap_cmd( ctx, cb, fmt, ap ); - va_end( ap ); + va_start(ap, fmt); + ret = v_issue_imap_cmd(ctx, cb, fmt, ap); + va_end(ap); return ret; } -static int -imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +static int imap_exec(imap_store_t *ctx, struct imap_cmd_cb *cb, + const char *fmt, ...) { va_list ap; struct imap_cmd *cmdp; - va_start( ap, fmt ); - cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); - va_end( ap ); + va_start(ap, fmt); + cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap); + va_end(ap); if (!cmdp) return RESP_BAD; - return get_cmd_result( ctx, cmdp ); + return get_cmd_result(ctx, cmdp); } -static int -imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +static int imap_exec_m(imap_store_t *ctx, struct imap_cmd_cb *cb, + const char *fmt, ...) { va_list ap; struct imap_cmd *cmdp; - va_start( ap, fmt ); - cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); - va_end( ap ); + va_start(ap, fmt); + cmdp = v_issue_imap_cmd(ctx, cb, fmt, ap); + va_end(ap); if (!cmdp) return DRV_STORE_BAD; - switch (get_cmd_result( ctx, cmdp )) { + switch (get_cmd_result(ctx, cmdp)) { case RESP_BAD: return DRV_STORE_BAD; case RESP_NO: return DRV_MSG_BAD; default: return DRV_OK; } } -static int -is_atom( list_t *list ) +static int is_atom(list_t *list) { return list && list->val && list->val != NIL && list->val != LIST; } -static int -is_list( list_t *list ) +static int is_list(list_t *list) { return list && list->val == LIST; } -static void -free_list( list_t *list ) +static void free_list(list_t *list) { list_t *tmp; for (; list; list = tmp) { tmp = list->next; - if (is_list( list )) - free_list( list->child ); - else if (is_atom( list )) - free( list->val ); - free( list ); + if (is_list(list)) + free_list(list->child); + else if (is_atom(list)) + free(list->val); + free(list); } } -static int -parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) +static int parse_imap_list_l(imap_t *imap, char **sp, list_t **curp, int level) { list_t *cur; char *s = *sp, *p; int n, bytes; for (;;) { - while (isspace( (unsigned char)*s )) + while (isspace((unsigned char)*s)) s++; if (level && *s == ')') { s++; break; } - *curp = cur = xmalloc( sizeof(*cur) ); + *curp = cur = xmalloc(sizeof(*cur)); curp = &cur->next; cur->val = NULL; /* for clean bail */ if (*s == '(') { /* sublist */ s++; cur->val = LIST; - if (parse_imap_list_l( imap, &s, &cur->child, level + 1 )) + if (parse_imap_list_l(imap, &s, &cur->child, level + 1)) goto bail; } else if (imap && *s == '{') { /* literal */ - bytes = cur->len = strtol( s + 1, &s, 10 ); + bytes = cur->len = strtol(s + 1, &s, 10); if (*s != '}') goto bail; - s = cur->val = xmalloc( cur->len ); + s = cur->val = xmalloc(cur->len); /* dump whats left over in the input buffer */ n = imap->buf.bytes - imap->buf.offset; @@ -717,7 +703,7 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) /* the entire message fit in the buffer */ n = bytes; - memcpy( s, imap->buf.buf + imap->buf.offset, n ); + memcpy(s, imap->buf.buf + imap->buf.offset, n); s += n; bytes -= n; @@ -726,13 +712,13 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) /* now read the rest of the message */ while (bytes > 0) { - if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0) + if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0) goto bail; s += n; bytes -= n; } - if (buffer_gets( &imap->buf, &s )) + if (buffer_gets(&imap->buf, &s)) goto bail; } else if (*s == '"') { /* quoted string */ @@ -747,11 +733,11 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) } else { /* atom */ p = s; - for (; *s && !isspace( (unsigned char)*s ); s++) + for (; *s && !isspace((unsigned char)*s); s++) if (level && *s == ')') break; cur->len = s - p; - if (cur->len == 3 && !memcmp ("NIL", p, 3)) { + if (cur->len == 3 && !memcmp("NIL", p, 3)) { cur->val = NIL; } else { cur->val = xmemdupz(p, cur->len); @@ -772,39 +758,36 @@ parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) return -1; } -static list_t * -parse_imap_list( imap_t *imap, char **sp ) +static list_t *parse_imap_list(imap_t *imap, char **sp) { list_t *head; - if (!parse_imap_list_l( imap, sp, &head, 0 )) + if (!parse_imap_list_l(imap, sp, &head, 0)) return head; - free_list( head ); + free_list(head); return NULL; } -static list_t * -parse_list( char **sp ) +static list_t *parse_list(char **sp) { - return parse_imap_list( NULL, sp ); + return parse_imap_list(NULL, sp); } -static void -parse_capability( imap_t *imap, char *cmd ) +static void parse_capability(imap_t *imap, char *cmd) { char *arg; unsigned i; imap->caps = 0x80000000; - while ((arg = next_arg( &cmd ))) + while ((arg = next_arg(&cmd))) for (i = 0; i < ARRAY_SIZE(cap_list); i++) - if (!strcmp( cap_list[i], arg )) + if (!strcmp(cap_list[i], arg)) imap->caps |= 1 << i; imap->rcaps = imap->caps; } -static int -parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s ) +static int parse_response_code(imap_store_t *ctx, struct imap_cmd_cb *cb, + char *s) { imap_t *imap = ctx->imap; char *arg, *p; @@ -812,43 +795,42 @@ parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s ) if (*s != '[') return RESP_OK; /* no response code */ s++; - if (!(p = strchr( s, ']' ))) { - fprintf( stderr, "IMAP error: malformed response code\n" ); + if (!(p = strchr(s, ']'))) { + fprintf(stderr, "IMAP error: malformed response code\n"); return RESP_BAD; } *p++ = 0; - arg = next_arg( &s ); - if (!strcmp( "UIDVALIDITY", arg )) { - if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) { - fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" ); + arg = next_arg(&s); + if (!strcmp("UIDVALIDITY", arg)) { + if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) { + fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n"); return RESP_BAD; } - } else if (!strcmp( "UIDNEXT", arg )) { - if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) { - fprintf( stderr, "IMAP error: malformed NEXTUID status\n" ); + } else if (!strcmp("UIDNEXT", arg)) { + if (!(arg = next_arg(&s)) || !(imap->uidnext = atoi(arg))) { + fprintf(stderr, "IMAP error: malformed NEXTUID status\n"); return RESP_BAD; } - } else if (!strcmp( "CAPABILITY", arg )) { - parse_capability( imap, s ); - } else if (!strcmp( "ALERT", arg )) { + } else if (!strcmp("CAPABILITY", arg)) { + parse_capability(imap, s); + } else if (!strcmp("ALERT", arg)) { /* RFC2060 says that these messages MUST be displayed * to the user */ - for (; isspace( (unsigned char)*p ); p++); - fprintf( stderr, "*** IMAP ALERT *** %s\n", p ); - } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) { - if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) || - !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg ))) + for (; isspace((unsigned char)*p); p++); + fprintf(stderr, "*** IMAP ALERT *** %s\n", p); + } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) { + if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) || + !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) { - fprintf( stderr, "IMAP error: malformed APPENDUID status\n" ); + fprintf(stderr, "IMAP error: malformed APPENDUID status\n"); return RESP_BAD; } } return RESP_OK; } -static int -get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) +static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd) { imap_t *imap = ctx->imap; struct imap_cmd *cmdp, **pcmdp, *ncmdp; @@ -856,38 +838,38 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) int n, resp, resp2, tag; for (;;) { - if (buffer_gets( &imap->buf, &cmd )) + if (buffer_gets(&imap->buf, &cmd)) return RESP_BAD; - arg = next_arg( &cmd ); + arg = next_arg(&cmd); if (*arg == '*') { - arg = next_arg( &cmd ); + arg = next_arg(&cmd); if (!arg) { - fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + fprintf(stderr, "IMAP error: unable to parse untagged response\n"); return RESP_BAD; } - if (!strcmp( "NAMESPACE", arg )) { - imap->ns_personal = parse_list( &cmd ); - imap->ns_other = parse_list( &cmd ); - imap->ns_shared = parse_list( &cmd ); - } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) || - !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) { - if ((resp = parse_response_code( ctx, NULL, cmd )) != RESP_OK) + if (!strcmp("NAMESPACE", arg)) { + imap->ns_personal = parse_list(&cmd); + imap->ns_other = parse_list(&cmd); + imap->ns_shared = parse_list(&cmd); + } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) || + !strcmp("NO", arg) || !strcmp("BYE", arg)) { + if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK) return resp; - } else if (!strcmp( "CAPABILITY", arg )) - parse_capability( imap, cmd ); - else if ((arg1 = next_arg( &cmd ))) { - if (!strcmp( "EXISTS", arg1 )) - ctx->gen.count = atoi( arg ); - else if (!strcmp( "RECENT", arg1 )) - ctx->gen.recent = atoi( arg ); + } else if (!strcmp("CAPABILITY", arg)) + parse_capability(imap, cmd); + else if ((arg1 = next_arg(&cmd))) { + if (!strcmp("EXISTS", arg1)) + ctx->gen.count = atoi(arg); + else if (!strcmp("RECENT", arg1)) + ctx->gen.recent = atoi(arg); } else { - fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + fprintf(stderr, "IMAP error: unable to parse untagged response\n"); return RESP_BAD; } } else if (!imap->in_progress) { - fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" ); + fprintf(stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : ""); return RESP_BAD; } else if (*arg == '+') { /* This can happen only with the last command underway, as @@ -895,30 +877,30 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) cmdp = (struct imap_cmd *)((char *)imap->in_progress_append - offsetof(struct imap_cmd, next)); if (cmdp->cb.data) { - n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen ); - free( cmdp->cb.data ); + n = socket_write(&imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen); + free(cmdp->cb.data); cmdp->cb.data = NULL; if (n != (int)cmdp->cb.dlen) return RESP_BAD; } else if (cmdp->cb.cont) { - if (cmdp->cb.cont( ctx, cmdp, cmd )) + if (cmdp->cb.cont(ctx, cmdp, cmd)) return RESP_BAD; } else { - fprintf( stderr, "IMAP error: unexpected command continuation request\n" ); + fprintf(stderr, "IMAP error: unexpected command continuation request\n"); return RESP_BAD; } - if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2) + if (socket_write(&imap->buf.sock, "\r\n", 2) != 2) return RESP_BAD; if (!cmdp->cb.cont) imap->literal_pending = 0; if (!tcmd) return DRV_OK; } else { - tag = atoi( arg ); + tag = atoi(arg); for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next) if (cmdp->tag == tag) goto gottag; - fprintf( stderr, "IMAP error: unexpected tag %s\n", arg ); + fprintf(stderr, "IMAP error: unexpected tag %s\n", arg); return RESP_BAD; gottag: if (!(*pcmdp = cmdp->next)) @@ -926,26 +908,26 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) imap->num_in_progress--; if (cmdp->cb.cont || cmdp->cb.data) imap->literal_pending = 0; - arg = next_arg( &cmd ); - if (!strcmp( "OK", arg )) + arg = next_arg(&cmd); + if (!strcmp("OK", arg)) resp = DRV_OK; else { - if (!strcmp( "NO", arg )) { - if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */ - p = strchr( cmdp->cmd, '"' ); - if (!issue_imap_cmd( ctx, NULL, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) { + if (!strcmp("NO", arg)) { + if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp(cmd, "[TRYCREATE]", 11))) { /* SELECT, APPEND or UID COPY */ + p = strchr(cmdp->cmd, '"'); + if (!issue_imap_cmd(ctx, NULL, "CREATE \"%.*s\"", strchr(p + 1, '"') - p + 1, p)) { resp = RESP_BAD; goto normal; } /* not waiting here violates the spec, but a server that does not grok this nonetheless violates it too. */ cmdp->cb.create = 0; - if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) { + if (!(ncmdp = issue_imap_cmd(ctx, &cmdp->cb, "%s", cmdp->cmd))) { resp = RESP_BAD; goto normal; } - free( cmdp->cmd ); - free( cmdp ); + free(cmdp->cmd); + free(cmdp); if (!tcmd) return 0; /* ignored */ if (cmdp == tcmd) @@ -953,21 +935,21 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) continue; } resp = RESP_NO; - } else /*if (!strcmp( "BAD", arg ))*/ + } else /*if (!strcmp("BAD", arg))*/ resp = RESP_BAD; - fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n", - memcmp (cmdp->cmd, "LOGIN", 5) ? + fprintf(stderr, "IMAP command '%s' returned response (%s) - %s\n", + memcmp(cmdp->cmd, "LOGIN", 5) ? cmdp->cmd : "LOGIN ", arg, cmd ? cmd : ""); } - if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp) + if ((resp2 = parse_response_code(ctx, &cmdp->cb, cmd)) > resp) resp = resp2; normal: if (cmdp->cb.done) - cmdp->cb.done( ctx, cmdp, resp ); - free( cmdp->cb.data ); - free( cmdp->cmd ); - free( cmdp ); + cmdp->cb.done(ctx, cmdp, resp); + free(cmdp->cb.data); + free(cmdp->cmd); + free(cmdp); if (!tcmd || tcmd == cmdp) return resp; } @@ -975,31 +957,28 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) /* not reached */ } -static void -imap_close_server( imap_store_t *ictx ) +static void imap_close_server(imap_store_t *ictx) { imap_t *imap = ictx->imap; if (imap->buf.sock.fd != -1) { - imap_exec( ictx, NULL, "LOGOUT" ); - socket_shutdown( &imap->buf.sock ); + imap_exec(ictx, NULL, "LOGOUT"); + socket_shutdown(&imap->buf.sock); } - free_list( imap->ns_personal ); - free_list( imap->ns_other ); - free_list( imap->ns_shared ); - free( imap ); + free_list(imap->ns_personal); + free_list(imap->ns_other); + free_list(imap->ns_shared); + free(imap); } -static void -imap_close_store( store_t *ctx ) +static void imap_close_store(store_t *ctx) { - imap_close_server( (imap_store_t *)ctx ); - free_generic_messages( ctx->msgs ); - free( ctx ); + imap_close_server((imap_store_t *)ctx); + free_generic_messages(ctx->msgs); + free(ctx); } -static store_t * -imap_open_store( imap_server_conf_t *srvc ) +static store_t *imap_open_store(imap_server_conf_t *srvc) { imap_store_t *ctx; imap_t *imap; @@ -1009,60 +988,60 @@ imap_open_store( imap_server_conf_t *srvc ) int s, a[2], preauth; pid_t pid; - ctx = xcalloc( sizeof(*ctx), 1 ); + ctx = xcalloc(sizeof(*ctx), 1); - ctx->imap = imap = xcalloc( sizeof(*imap), 1 ); + ctx->imap = imap = xcalloc(sizeof(*imap), 1); imap->buf.sock.fd = -1; imap->in_progress_append = &imap->in_progress; /* open connection to IMAP server */ if (srvc->tunnel) { - imap_info( "Starting tunnel '%s'... ", srvc->tunnel ); + imap_info("Starting tunnel '%s'... ", srvc->tunnel); - if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { - perror( "socketpair" ); - exit( 1 ); + if (socketpair(PF_UNIX, SOCK_STREAM, 0, a)) { + perror("socketpair"); + exit(1); } pid = fork(); if (pid < 0) - _exit( 127 ); + _exit(127); if (!pid) { - if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1) - _exit( 127 ); - close( a[0] ); - close( a[1] ); - execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL ); - _exit( 127 ); + if (dup2(a[0], 0) == -1 || dup2(a[0], 1) == -1) + _exit(127); + close(a[0]); + close(a[1]); + execl("/bin/sh", "sh", "-c", srvc->tunnel, NULL); + _exit(127); } - close (a[0]); + close(a[0]); imap->buf.sock.fd = a[1]; - imap_info( "ok\n" ); + imap_info("ok\n"); } else { - memset( &addr, 0, sizeof(addr) ); - addr.sin_port = htons( srvc->port ); + memset(&addr, 0, sizeof(addr)); + addr.sin_port = htons(srvc->port); addr.sin_family = AF_INET; - imap_info( "Resolving %s... ", srvc->host ); - he = gethostbyname( srvc->host ); + imap_info("Resolving %s... ", srvc->host); + he = gethostbyname(srvc->host); if (!he) { - perror( "gethostbyname" ); + perror("gethostbyname"); goto bail; } - imap_info( "ok\n" ); + imap_info("ok\n"); addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); - s = socket( PF_INET, SOCK_STREAM, 0 ); + s = socket(PF_INET, SOCK_STREAM, 0); - imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); - if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { - close( s ); - perror( "connect" ); + imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) { + close(s); + perror("connect"); goto bail; } @@ -1073,28 +1052,28 @@ imap_open_store( imap_server_conf_t *srvc ) close(s); goto bail; } - imap_info( "ok\n" ); + imap_info("ok\n"); } /* read the greeting string */ - if (buffer_gets( &imap->buf, &rsp )) { - fprintf( stderr, "IMAP error: no greeting response\n" ); + if (buffer_gets(&imap->buf, &rsp)) { + fprintf(stderr, "IMAP error: no greeting response\n"); goto bail; } - arg = next_arg( &rsp ); - if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) { - fprintf( stderr, "IMAP error: invalid greeting response\n" ); + arg = next_arg(&rsp); + if (!arg || *arg != '*' || (arg = next_arg(&rsp)) == NULL) { + fprintf(stderr, "IMAP error: invalid greeting response\n"); goto bail; } preauth = 0; - if (!strcmp( "PREAUTH", arg )) + if (!strcmp("PREAUTH", arg)) preauth = 1; - else if (strcmp( "OK", arg ) != 0) { - fprintf( stderr, "IMAP error: unknown greeting response\n" ); + else if (strcmp("OK", arg) != 0) { + fprintf(stderr, "IMAP error: unknown greeting response\n"); goto bail; } - parse_response_code( ctx, NULL, rsp ); - if (!imap->caps && imap_exec( ctx, NULL, "CAPABILITY" ) != RESP_OK) + parse_response_code(ctx, NULL, rsp); + if (!imap->caps && imap_exec(ctx, NULL, "CAPABILITY") != RESP_OK) goto bail; if (!preauth) { @@ -1110,38 +1089,38 @@ imap_open_store( imap_server_conf_t *srvc ) goto bail; } #endif - imap_info ("Logging in...\n"); + imap_info("Logging in...\n"); if (!srvc->user) { - fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); + fprintf(stderr, "Skipping server %s, no user\n", srvc->host); goto bail; } if (!srvc->pass) { char prompt[80]; - sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host ); - arg = getpass( prompt ); + sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host); + arg = getpass(prompt); if (!arg) { - perror( "getpass" ); - exit( 1 ); + perror("getpass"); + exit(1); } if (!*arg) { - fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host ); + fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host); goto bail; } /* * getpass() returns a pointer to a static buffer. make a copy * for long term storage. */ - srvc->pass = xstrdup( arg ); + srvc->pass = xstrdup(arg); } if (CAP(NOLOGIN)) { - fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); + fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host); goto bail; } if (!imap->buf.sock.ssl) imap_warn( "*** IMAP Warning *** Password is being " "sent in the clear\n" ); - if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { - fprintf( stderr, "IMAP error: LOGIN failed\n" ); + if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { + fprintf(stderr, "IMAP error: LOGIN failed\n"); goto bail; } } /* !preauth */ @@ -1151,12 +1130,11 @@ imap_open_store( imap_server_conf_t *srvc ) return (store_t *)ctx; bail: - imap_close_store( &ctx->gen ); + imap_close_store(&ctx->gen); return NULL; } -static int -imap_make_flags( int flags, char *buf ) +static int imap_make_flags(int flags, char *buf) { const char *s; unsigned i, d; @@ -1175,8 +1153,7 @@ imap_make_flags( int flags, char *buf ) #define TUIDL 8 -static int -imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +static int imap_store_msg(store_t *gctx, msg_data_t *data, int *uid) { imap_store_t *ctx = (imap_store_t *)gctx; imap_t *imap = ctx->imap; @@ -1187,7 +1164,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) int start, sbreak = 0, ebreak = 0; char flagstr[128], tuid[TUIDL * 2 + 1]; - memset( &cb, 0, sizeof(cb) ); + memset(&cb, 0, sizeof(cb)); fmap = data->data; len = data->len; @@ -1203,18 +1180,18 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) sbreak = ebreak = i - 2 + nocr; goto mktid; } - if (!memcmp( fmap + start, "X-TUID: ", 8 )) { + if (!memcmp(fmap + start, "X-TUID: ", 8)) { extra -= (ebreak = i) - (sbreak = start) + nocr; goto mktid; } goto nloop; } /* invalid message */ - free( fmap ); + free(fmap); return DRV_MSG_BAD; mktid: for (j = 0; j < TUIDL; j++) - sprintf( tuid + j * 2, "%02x", arc4_getbyte() ); + sprintf(tuid + j * 2, "%02x", arc4_getbyte()); extra += 8 + TUIDL * 2 + 2; } if (nocr) @@ -1223,7 +1200,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) extra++; cb.dlen = len + extra; - buf = cb.data = xmalloc( cb.dlen ); + buf = cb.data = xmalloc(cb.dlen); i = 0; if (!CAP(UIDPLUS) && uid) { if (nocr) { @@ -1234,12 +1211,12 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) } else *buf++ = fmap[i]; } else { - memcpy( buf, fmap, sbreak ); + memcpy(buf, fmap, sbreak); buf += sbreak; } - memcpy( buf, "X-TUID: ", 8 ); + memcpy(buf, "X-TUID: ", 8); buf += 8; - memcpy( buf, tuid, TUIDL * 2 ); + memcpy(buf, tuid, TUIDL * 2); buf += TUIDL * 2; *buf++ = '\r'; *buf++ = '\n'; @@ -1253,13 +1230,13 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) } else *buf++ = fmap[i]; } else - memcpy( buf, fmap + i, len - i ); + memcpy(buf, fmap + i, len - i); - free( fmap ); + free(fmap); d = 0; if (data->flags) { - d = imap_make_flags( data->flags, flagstr ); + d = imap_make_flags(data->flags, flagstr); flagstr[d++] = ' '; } flagstr[d] = 0; @@ -1272,11 +1249,11 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) imap->caps = imap->rcaps & ~(1 << LITERALPLUS); } else { box = gctx->name; - prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; + prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix; cb.create = 0; } cb.ctx = uid; - ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr ); + ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr); imap->caps = imap->rcaps; if (ret != DRV_OK) return ret; @@ -1290,8 +1267,7 @@ imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) #define CHUNKSIZE 0x1000 -static int -read_message( FILE *f, msg_data_t *msg ) +static int read_message(FILE *f, msg_data_t *msg) { struct strbuf buf; @@ -1308,8 +1284,7 @@ read_message( FILE *f, msg_data_t *msg ) return msg->len; } -static int -count_messages( msg_data_t *msg ) +static int count_messages(msg_data_t *msg) { int count = 0; char *p = msg->data; @@ -1319,7 +1294,7 @@ count_messages( msg_data_t *msg ) count++; p += 5; } - p = strstr( p+5, "\nFrom "); + p = strstr(p+5, "\nFrom "); if (!p) break; p++; @@ -1327,22 +1302,21 @@ count_messages( msg_data_t *msg ) return count; } -static int -split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) +static int split_msg(msg_data_t *all_msgs, msg_data_t *msg, int *ofs) { char *p, *data; - memset( msg, 0, sizeof *msg ); + memset(msg, 0, sizeof *msg); if (*ofs >= all_msgs->len) return 0; - data = &all_msgs->data[ *ofs ]; + data = &all_msgs->data[*ofs]; msg->len = all_msgs->len - *ofs; if (msg->len < 5 || prefixcmp(data, "From ")) return 0; - p = strchr( data, '\n' ); + p = strchr(data, '\n'); if (p) { p = &p[1]; msg->len -= p-data; @@ -1350,7 +1324,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) data = p; } - p = strstr( data, "\nFrom " ); + p = strstr(data, "\nFrom "); if (p) msg->len = &p[1] - data; @@ -1359,8 +1333,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) return 1; } -static imap_server_conf_t server = -{ +static imap_server_conf_t server = { NULL, /* name */ NULL, /* tunnel */ NULL, /* host */ @@ -1373,12 +1346,11 @@ static imap_server_conf_t server = static char *imap_folder; -static int -git_imap_config(const char *key, const char *val, void *cb) +static int git_imap_config(const char *key, const char *val, void *cb) { char imap_key[] = "imap."; - if (strncmp( key, imap_key, sizeof imap_key - 1 )) + if (strncmp(key, imap_key, sizeof imap_key - 1)) return 0; if (!val) @@ -1386,9 +1358,9 @@ git_imap_config(const char *key, const char *val, void *cb) key += sizeof imap_key - 1; - if (!strcmp( "folder", key )) { - imap_folder = xstrdup( val ); - } else if (!strcmp( "host", key )) { + if (!strcmp("folder", key)) { + imap_folder = xstrdup(val); + } else if (!strcmp("host", key)) { if (!prefixcmp(val, "imap:")) val += 5; else if (!prefixcmp(val, "imaps:")) { @@ -1397,23 +1369,22 @@ git_imap_config(const char *key, const char *val, void *cb) } if (!prefixcmp(val, "//")) val += 2; - server.host = xstrdup( val ); + server.host = xstrdup(val); } - else if (!strcmp( "user", key )) - server.user = xstrdup( val ); - else if (!strcmp( "pass", key )) - server.pass = xstrdup( val ); - else if (!strcmp( "port", key )) - server.port = git_config_int( key, val ); - else if (!strcmp( "tunnel", key )) - server.tunnel = xstrdup( val ); - else if (!strcmp( "sslverify", key )) - server.ssl_verify = git_config_bool( key, val ); + else if (!strcmp("user", key)) + server.user = xstrdup(val); + else if (!strcmp("pass", key)) + server.pass = xstrdup(val); + else if (!strcmp("port", key)) + server.port = git_config_int(key, val); + else if (!strcmp("tunnel", key)) + server.tunnel = xstrdup(val); + else if (!strcmp("sslverify", key)) + server.ssl_verify = git_config_bool(key, val); return 0; } -int -main(int argc, char **argv) +int main(int argc, char **argv) { msg_data_t all_msgs, msg; store_t *ctx = NULL; @@ -1433,50 +1404,50 @@ main(int argc, char **argv) server.port = server.use_ssl ? 993 : 143; if (!imap_folder) { - fprintf( stderr, "no imap store specified\n" ); + fprintf(stderr, "no imap store specified\n"); return 1; } if (!server.host) { if (!server.tunnel) { - fprintf( stderr, "no imap host specified\n" ); + fprintf(stderr, "no imap host specified\n"); return 1; } server.host = "tunnel"; } /* read the messages */ - if (!read_message( stdin, &all_msgs )) { + if (!read_message(stdin, &all_msgs)) { fprintf(stderr,"nothing to send\n"); return 1; } - total = count_messages( &all_msgs ); + total = count_messages(&all_msgs); if (!total) { fprintf(stderr,"no messages to send\n"); return 1; } /* write it to the imap server */ - ctx = imap_open_store( &server ); + ctx = imap_open_store(&server); if (!ctx) { - fprintf( stderr,"failed to open store\n"); + fprintf(stderr,"failed to open store\n"); return 1; } - fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" ); + fprintf(stderr, "sending %d message%s\n", total, (total!=1)?"s":""); ctx->name = imap_folder; while (1) { unsigned percent = n * 100 / total; - fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total ); - if (!split_msg( &all_msgs, &msg, &ofs )) + fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total); + if (!split_msg(&all_msgs, &msg, &ofs)) break; - r = imap_store_msg( ctx, &msg, &uid ); + r = imap_store_msg(ctx, &msg, &uid); if (r != DRV_OK) break; n++; } - fprintf( stderr,"\n" ); + fprintf(stderr, "\n"); - imap_close_store( ctx ); + imap_close_store(ctx); return 0; } -- cgit v1.2.1 From 9f1ad541f95cf6b9024683c8883bfb0ca86f9a3d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 9 Jul 2008 16:37:38 -0700 Subject: imap-send.c: more style fixes The previous one sqeezed unnecessary whitespaces and joined function type and name in the definition split across lines. This patch further fixes many remaining style issues: - We are not particularly fond of typedef to hide the underlying struct definitions. - Asterisk comes next variable, i.e. "type *var", not "type * var" nor "type* var". - Casting to pointer to a type is "(type *)", not "(type*)". - An open brace comes on the same line as closing parenthesis of "if (...)" condition; "else" comes on the same line as closing brace of its corresponding "if (...) {". - Avoid single liner "if (...) ;"; they should be on two separate lines. Signed-off-by: Junio C Hamano --- imap-send.c | 225 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 111 insertions(+), 114 deletions(-) diff --git a/imap-send.c b/imap-send.c index a7f7dfd22e..af7e08c094 100644 --- a/imap-send.c +++ b/imap-send.c @@ -27,70 +27,70 @@ typedef void *SSL; #endif -typedef struct store_conf { +struct store_conf { char *name; const char *path; /* should this be here? its interpretation is driver-specific */ char *map_inbox; char *trash; unsigned max_size; /* off_t is overkill */ unsigned trash_remote_new:1, trash_only_new:1; -} store_conf_t; +}; -typedef struct string_list { +struct string_list { struct string_list *next; char string[1]; -} string_list_t; +}; -typedef struct channel_conf { +struct channel_conf { struct channel_conf *next; char *name; - store_conf_t *master, *slave; + struct store_conf *master, *slave; char *master_name, *slave_name; char *sync_state; - string_list_t *patterns; + struct string_list *patterns; int mops, sops; unsigned max_messages; /* for slave only */ -} channel_conf_t; +}; -typedef struct group_conf { +struct group_conf { struct group_conf *next; char *name; - string_list_t *channels; -} group_conf_t; + struct string_list *channels; +}; /* For message->status */ #define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ #define M_DEAD (1<<1) /* expunged */ #define M_FLAGS (1<<2) /* flags fetched */ -typedef struct message { +struct message { struct message *next; - /* string_list_t *keywords; */ + /* struct string_list *keywords; */ size_t size; /* zero implies "not fetched" */ int uid; unsigned char flags, status; -} message_t; +}; -typedef struct store { - store_conf_t *conf; /* foreign */ +struct store { + struct store_conf *conf; /* foreign */ /* currently open mailbox */ const char *name; /* foreign! maybe preset? */ char *path; /* own */ - message_t *msgs; /* own */ + struct message *msgs; /* own */ int uidvalidity; unsigned char opts; /* maybe preset? */ /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ int count; /* # of messages */ int recent; /* # of recent messages - don't trust this beyond the initial read */ -} store_t; +}; -typedef struct { +struct msg_data { char *data; int len; unsigned char flags; unsigned int crlf:1; -} msg_data_t; +}; #define DRV_OK 0 #define DRV_MSG_BAD -1 @@ -104,7 +104,7 @@ static void imap_warn(const char *, ...); static char *next_arg(char **); -static void free_generic_messages(message_t *); +static void free_generic_messages(struct message *); static int nfsnprintf(char *buf, int blen, const char *fmt, ...); @@ -125,7 +125,7 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) static void arc4_init(void); static unsigned char arc4_getbyte(void); -typedef struct imap_server_conf { +struct imap_server_conf { char *name; char *tunnel; char *host; @@ -134,58 +134,58 @@ typedef struct imap_server_conf { char *pass; int use_ssl; int ssl_verify; -} imap_server_conf_t; +}; -typedef struct imap_store_conf { - store_conf_t gen; - imap_server_conf_t *server; +struct imap_store_conf { + struct store_conf gen; + struct imap_server_conf *server; unsigned use_namespace:1; -} imap_store_conf_t; +}; -#define NIL (void*)0x1 -#define LIST (void*)0x2 +#define NIL (void *)0x1 +#define LIST (void *)0x2 -typedef struct _list { - struct _list *next, *child; +struct imap_list { + struct imap_list *next, *child; char *val; int len; -} list_t; +}; -typedef struct { +struct imap_socket { int fd; SSL *ssl; -} Socket_t; +}; -typedef struct { - Socket_t sock; +struct imap_buffer { + struct imap_socket sock; int bytes; int offset; char buf[1024]; -} buffer_t; +}; struct imap_cmd; -typedef struct imap { +struct imap { int uidnext; /* from SELECT responses */ - list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ + struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ unsigned caps, rcaps; /* CAPABILITY results */ /* command queue */ int nexttag, num_in_progress, literal_pending; struct imap_cmd *in_progress, **in_progress_append; - buffer_t buf; /* this is BIG, so put it last */ -} imap_t; + struct imap_buffer buf; /* this is BIG, so put it last */ +}; -typedef struct imap_store { - store_t gen; +struct imap_store { + struct store gen; int uidvalidity; - imap_t *imap; + struct imap *imap; const char *prefix; unsigned /*currentnc:1,*/ trashnc:1; -} imap_store_t; +}; struct imap_cmd_cb { - int (*cont)(imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt); - void (*done)(imap_store_t *ctx, struct imap_cmd *cmd, int response); + int (*cont)(struct imap_store *ctx, struct imap_cmd *cmd, const char *prompt); + void (*done)(struct imap_store *ctx, struct imap_cmd *cmd, int response); void *ctx; char *data; int dlen; @@ -222,7 +222,7 @@ static const char *cap_list[] = { #define RESP_NO 1 #define RESP_BAD 2 -static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd); +static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd); static const char *Flags[] = { @@ -240,7 +240,7 @@ static void ssl_socket_perror(const char *func) } #endif -static void socket_perror(const char *func, Socket_t *sock, int ret) +static void socket_perror(const char *func, struct imap_socket *sock, int ret) { #ifndef NO_OPENSSL if (sock->ssl) { @@ -265,7 +265,7 @@ static void socket_perror(const char *func, Socket_t *sock, int ret) } } -static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify) +static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify) { #ifdef NO_OPENSSL fprintf(stderr, "SSL requested but SSL support not compiled in\n"); @@ -317,7 +317,7 @@ static int ssl_socket_connect(Socket_t *sock, int use_tls_only, int verify) #endif } -static int socket_read(Socket_t *sock, char *buf, int len) +static int socket_read(struct imap_socket *sock, char *buf, int len) { ssize_t n; #ifndef NO_OPENSSL @@ -334,7 +334,7 @@ static int socket_read(Socket_t *sock, char *buf, int len) return n; } -static int socket_write(Socket_t *sock, const char *buf, int len) +static int socket_write(struct imap_socket *sock, const char *buf, int len) { int n; #ifndef NO_OPENSSL @@ -351,7 +351,7 @@ static int socket_write(Socket_t *sock, const char *buf, int len) return n; } -static void socket_shutdown(Socket_t *sock) +static void socket_shutdown(struct imap_socket *sock) { #ifndef NO_OPENSSL if (sock->ssl) { @@ -363,7 +363,7 @@ static void socket_shutdown(Socket_t *sock) } /* simple line buffering */ -static int buffer_gets(buffer_t * b, char **s) +static int buffer_gets(struct imap_buffer *b, char **s) { int n; int start = b->offset; @@ -465,9 +465,9 @@ static char *next_arg(char **s) return ret; } -static void free_generic_messages(message_t *msgs) +static void free_generic_messages(struct message *msgs) { - message_t *tmsg; + struct message *tmsg; for (; msgs; msgs = tmsg) { tmsg = msgs->next; @@ -533,11 +533,11 @@ static unsigned char arc4_getbyte(void) return rs.s[(si + sj) & 0xff]; } -static struct imap_cmd *v_issue_imap_cmd(imap_store_t *ctx, +static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, va_list ap) { - imap_t *imap = ctx->imap; + struct imap *imap = ctx->imap; struct imap_cmd *cmd; int n, bufl; char buf[1024]; @@ -577,8 +577,7 @@ static struct imap_cmd *v_issue_imap_cmd(imap_store_t *ctx, n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen); free(cmd->cb.data); if (n != cmd->cb.dlen || - (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) - { + (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) { free(cmd->cmd); free(cmd); return NULL; @@ -595,7 +594,7 @@ static struct imap_cmd *v_issue_imap_cmd(imap_store_t *ctx, return cmd; } -static struct imap_cmd *issue_imap_cmd(imap_store_t *ctx, +static struct imap_cmd *issue_imap_cmd(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) { @@ -608,7 +607,7 @@ static struct imap_cmd *issue_imap_cmd(imap_store_t *ctx, return ret; } -static int imap_exec(imap_store_t *ctx, struct imap_cmd_cb *cb, +static int imap_exec(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) { va_list ap; @@ -623,7 +622,7 @@ static int imap_exec(imap_store_t *ctx, struct imap_cmd_cb *cb, return get_cmd_result(ctx, cmdp); } -static int imap_exec_m(imap_store_t *ctx, struct imap_cmd_cb *cb, +static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb, const char *fmt, ...) { va_list ap; @@ -642,19 +641,19 @@ static int imap_exec_m(imap_store_t *ctx, struct imap_cmd_cb *cb, } } -static int is_atom(list_t *list) +static int is_atom(struct imap_list *list) { return list && list->val && list->val != NIL && list->val != LIST; } -static int is_list(list_t *list) +static int is_list(struct imap_list *list) { return list && list->val == LIST; } -static void free_list(list_t *list) +static void free_list(struct imap_list *list) { - list_t *tmp; + struct imap_list *tmp; for (; list; list = tmp) { tmp = list->next; @@ -666,9 +665,9 @@ static void free_list(list_t *list) } } -static int parse_imap_list_l(imap_t *imap, char **sp, list_t **curp, int level) +static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level) { - list_t *cur; + struct imap_list *cur; char *s = *sp, *p; int n, bytes; @@ -737,11 +736,10 @@ static int parse_imap_list_l(imap_t *imap, char **sp, list_t **curp, int level) if (level && *s == ')') break; cur->len = s - p; - if (cur->len == 3 && !memcmp("NIL", p, 3)) { + if (cur->len == 3 && !memcmp("NIL", p, 3)) cur->val = NIL; - } else { + else cur->val = xmemdupz(p, cur->len); - } } if (!level) @@ -753,14 +751,14 @@ static int parse_imap_list_l(imap_t *imap, char **sp, list_t **curp, int level) *curp = NULL; return 0; - bail: +bail: *curp = NULL; return -1; } -static list_t *parse_imap_list(imap_t *imap, char **sp) +static struct imap_list *parse_imap_list(struct imap *imap, char **sp) { - list_t *head; + struct imap_list *head; if (!parse_imap_list_l(imap, sp, &head, 0)) return head; @@ -768,12 +766,12 @@ static list_t *parse_imap_list(imap_t *imap, char **sp) return NULL; } -static list_t *parse_list(char **sp) +static struct imap_list *parse_list(char **sp) { return parse_imap_list(NULL, sp); } -static void parse_capability(imap_t *imap, char *cmd) +static void parse_capability(struct imap *imap, char *cmd) { char *arg; unsigned i; @@ -786,10 +784,10 @@ static void parse_capability(imap_t *imap, char *cmd) imap->rcaps = imap->caps; } -static int parse_response_code(imap_store_t *ctx, struct imap_cmd_cb *cb, +static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb, char *s) { - imap_t *imap = ctx->imap; + struct imap *imap = ctx->imap; char *arg, *p; if (*s != '[') @@ -821,8 +819,7 @@ static int parse_response_code(imap_store_t *ctx, struct imap_cmd_cb *cb, fprintf(stderr, "*** IMAP ALERT *** %s\n", p); } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) { if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) || - !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) - { + !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) { fprintf(stderr, "IMAP error: malformed APPENDUID status\n"); return RESP_BAD; } @@ -830,9 +827,9 @@ static int parse_response_code(imap_store_t *ctx, struct imap_cmd_cb *cb, return RESP_OK; } -static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd) +static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd) { - imap_t *imap = ctx->imap; + struct imap *imap = ctx->imap; struct imap_cmd *cmdp, **pcmdp, *ncmdp; char *cmd, *arg, *arg1, *p; int n, resp, resp2, tag; @@ -902,7 +899,7 @@ static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd) goto gottag; fprintf(stderr, "IMAP error: unexpected tag %s\n", arg); return RESP_BAD; - gottag: + gottag: if (!(*pcmdp = cmdp->next)) imap->in_progress_append = pcmdp; imap->num_in_progress--; @@ -944,7 +941,7 @@ static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd) } if ((resp2 = parse_response_code(ctx, &cmdp->cb, cmd)) > resp) resp = resp2; - normal: + normal: if (cmdp->cb.done) cmdp->cb.done(ctx, cmdp, resp); free(cmdp->cb.data); @@ -957,9 +954,9 @@ static int get_cmd_result(imap_store_t *ctx, struct imap_cmd *tcmd) /* not reached */ } -static void imap_close_server(imap_store_t *ictx) +static void imap_close_server(struct imap_store *ictx) { - imap_t *imap = ictx->imap; + struct imap *imap = ictx->imap; if (imap->buf.sock.fd != -1) { imap_exec(ictx, NULL, "LOGOUT"); @@ -971,17 +968,17 @@ static void imap_close_server(imap_store_t *ictx) free(imap); } -static void imap_close_store(store_t *ctx) +static void imap_close_store(struct store *ctx) { - imap_close_server((imap_store_t *)ctx); + imap_close_server((struct imap_store *)ctx); free_generic_messages(ctx->msgs); free(ctx); } -static store_t *imap_open_store(imap_server_conf_t *srvc) +static struct store *imap_open_store(struct imap_server_conf *srvc) { - imap_store_t *ctx; - imap_t *imap; + struct imap_store *ctx; + struct imap *imap; char *arg, *rsp; struct hostent *he; struct sockaddr_in addr; @@ -1117,8 +1114,8 @@ static store_t *imap_open_store(imap_server_conf_t *srvc) goto bail; } if (!imap->buf.sock.ssl) - imap_warn( "*** IMAP Warning *** Password is being " - "sent in the clear\n" ); + imap_warn("*** IMAP Warning *** Password is being " + "sent in the clear\n"); if (imap_exec(ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass) != RESP_OK) { fprintf(stderr, "IMAP error: LOGIN failed\n"); goto bail; @@ -1127,9 +1124,9 @@ static store_t *imap_open_store(imap_server_conf_t *srvc) ctx->prefix = ""; ctx->trashnc = 1; - return (store_t *)ctx; + return (struct store *)ctx; - bail: +bail: imap_close_store(&ctx->gen); return NULL; } @@ -1153,10 +1150,10 @@ static int imap_make_flags(int flags, char *buf) #define TUIDL 8 -static int imap_store_msg(store_t *gctx, msg_data_t *data, int *uid) +static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) { - imap_store_t *ctx = (imap_store_t *)gctx; - imap_t *imap = ctx->imap; + struct imap_store *ctx = (struct imap_store *)gctx; + struct imap *imap = ctx->imap; struct imap_cmd_cb cb; char *fmap, *buf; const char *prefix, *box; @@ -1171,7 +1168,7 @@ static int imap_store_msg(store_t *gctx, msg_data_t *data, int *uid) nocr = !data->crlf; extra = 0, i = 0; if (!CAP(UIDPLUS) && uid) { - nloop: + nloop: start = i; while (i < len) if (fmap[i++] == '\n') { @@ -1189,7 +1186,7 @@ static int imap_store_msg(store_t *gctx, msg_data_t *data, int *uid) /* invalid message */ free(fmap); return DRV_MSG_BAD; - mktid: + mktid: for (j = 0; j < TUIDL; j++) sprintf(tuid + j * 2, "%02x", arc4_getbyte()); extra += 8 + TUIDL * 2 + 2; @@ -1267,7 +1264,7 @@ static int imap_store_msg(store_t *gctx, msg_data_t *data, int *uid) #define CHUNKSIZE 0x1000 -static int read_message(FILE *f, msg_data_t *msg) +static int read_message(FILE *f, struct msg_data *msg) { struct strbuf buf; @@ -1284,7 +1281,7 @@ static int read_message(FILE *f, msg_data_t *msg) return msg->len; } -static int count_messages(msg_data_t *msg) +static int count_messages(struct msg_data *msg) { int count = 0; char *p = msg->data; @@ -1302,7 +1299,7 @@ static int count_messages(msg_data_t *msg) return count; } -static int split_msg(msg_data_t *all_msgs, msg_data_t *msg, int *ofs) +static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs) { char *p, *data; @@ -1333,7 +1330,7 @@ static int split_msg(msg_data_t *all_msgs, msg_data_t *msg, int *ofs) return 1; } -static imap_server_conf_t server = { +static struct imap_server_conf server = { NULL, /* name */ NULL, /* tunnel */ NULL, /* host */ @@ -1370,8 +1367,7 @@ static int git_imap_config(const char *key, const char *val, void *cb) if (!prefixcmp(val, "//")) val += 2; server.host = xstrdup(val); - } - else if (!strcmp("user", key)) + } else if (!strcmp("user", key)) server.user = xstrdup(val); else if (!strcmp("pass", key)) server.pass = xstrdup(val); @@ -1386,8 +1382,8 @@ static int git_imap_config(const char *key, const char *val, void *cb) int main(int argc, char **argv) { - msg_data_t all_msgs, msg; - store_t *ctx = NULL; + struct msg_data all_msgs, msg; + struct store *ctx = NULL; int uid = 0; int ofs = 0; int r; @@ -1417,24 +1413,24 @@ int main(int argc, char **argv) /* read the messages */ if (!read_message(stdin, &all_msgs)) { - fprintf(stderr,"nothing to send\n"); + fprintf(stderr, "nothing to send\n"); return 1; } total = count_messages(&all_msgs); if (!total) { - fprintf(stderr,"no messages to send\n"); + fprintf(stderr, "no messages to send\n"); return 1; } /* write it to the imap server */ ctx = imap_open_store(&server); if (!ctx) { - fprintf(stderr,"failed to open store\n"); + fprintf(stderr, "failed to open store\n"); return 1; } - fprintf(stderr, "sending %d message%s\n", total, (total!=1)?"s":""); + fprintf(stderr, "sending %d message%s\n", total, (total != 1) ? "s" : ""); ctx->name = imap_folder; while (1) { unsigned percent = n * 100 / total; @@ -1442,7 +1438,8 @@ int main(int argc, char **argv) if (!split_msg(&all_msgs, &msg, &ofs)) break; r = imap_store_msg(ctx, &msg, &uid); - if (r != DRV_OK) break; + if (r != DRV_OK) + break; n++; } fprintf(stderr, "\n"); -- cgit v1.2.1 From c82b0748e53d51cc999c16f97bc76c25f2b46b12 Mon Sep 17 00:00:00 2001 From: Robert Shearman Date: Wed, 9 Jul 2008 22:29:02 +0100 Subject: Documentation: Improve documentation for git-imap-send(1) Change the description to be similar to that used for git-send-email(1) to give a better description of what the tool can be used for and sound more user-friendly. Document the configuration variables used by git-imap-send, split the example into tunnel and direct examples. Rephrase other parts of the git-imap-send documentation to use better grammar and to be clearer. Signed-off-by: Robert Shearman Signed-off-by: Junio C Hamano --- Documentation/git-imap-send.txt | 77 ++++++++++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 136c82bfdd..bd49a0aee8 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -3,7 +3,7 @@ git-imap-send(1) NAME ---- -git-imap-send - Dump a mailbox from stdin into an imap folder +git-imap-send - Send a collection of patches from stdin to an IMAP folder SYNOPSIS @@ -13,9 +13,9 @@ SYNOPSIS DESCRIPTION ----------- -This command uploads a mailbox generated with git-format-patch -into an imap drafts folder. This allows patches to be sent as -other email is sent with mail clients that cannot read mailbox +This command uploads a mailbox generated with 'git-format-patch' +into an IMAP drafts folder. This allows patches to be sent as +other email is when using mail clients that cannot read mailbox files directly. Typical usage is something like: @@ -26,21 +26,74 @@ git format-patch --signoff --stdout --attach origin | git imap-send CONFIGURATION ------------- -'git-imap-send' requires the following values in the repository -configuration file (shown with examples): +To use the tool, imap.folder and either imap.tunnel or imap.host must be set +to appropriate values. + +Variables +~~~~~~~~~ + +imap.folder:: + The folder to drop the mails into, which is typically the Drafts + folder. For example: "INBOX.Drafts", "INBOX/Drafts" or + "[Gmail]/Drafts". Required to use imap-send. + +imap.tunnel:: + Command used to setup a tunnel to the IMAP server through which + commands will be piped instead of using a direct network connection + to the server. Required when imap.host is not set to use imap-send. + +imap.host:: + A URL identifying the server. Use a `imap://` prefix for non-secure + connections and a `imaps://` prefix for secure connections. + Ignored when imap.tunnel is set, but required to use imap-send + otherwise. + +imap.user:: + The username to use when logging in to the server. + +imap.password:: + The password to use when logging in to the server. + +imap.port:: + An integer port number to connect to on the server. + Defaults to 143 for imap:// hosts and 993 for imaps:// hosts. + Ignored when imap.tunnel is set. + +imap.sslverify:: + A boolean to enable/disable verification of the server certificate + used by the SSL/TLS connection. Default is `true`. Ignored when + imap.tunnel is set. + +Examples +~~~~~~~~ + +Using tunnel mode: .......................... [imap] - Folder = "INBOX.Drafts" + folder = "INBOX.Drafts" + tunnel = "ssh -q -C user@example.com /usr/bin/imapd ./Maildir 2> /dev/null" +.......................... +Using direct mode: + +......................... [imap] - Tunnel = "ssh -q user@server.com /usr/bin/imapd ./Maildir 2> /dev/null" + folder = "INBOX.Drafts" + host = imap://imap.example.com + user = bob + pass = p4ssw0rd +.......................... + +Using direct mode with SSL: +......................... [imap] - Host = imap://imap.example.com - User = bob - Pass = pwd - Port = 143 + folder = "INBOX.Drafts" + host = imaps://imap.example.com + user = bob + pass = p4ssw0rd + port = 123 sslverify = false .......................... -- cgit v1.2.1 From 53eda89b2fe13e974ca85367255f9a5e9ab9171f Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Wed, 30 Jul 2008 07:04:14 +0200 Subject: merge-base: teach "git merge-base" to drive underlying merge_bases_many() Even though the underlying function for get_merge_bases() can compute a merge base between one existing commit and another (possibly nonexistent) commit that would be created by merging many commits, the facility was not available to git-merge-base. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- builtin-merge-base.c | 24 ++++++++++++++++-------- commit.h | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/builtin-merge-base.c b/builtin-merge-base.c index 3382b1382a..b08da516e4 100644 --- a/builtin-merge-base.c +++ b/builtin-merge-base.c @@ -2,9 +2,11 @@ #include "cache.h" #include "commit.h" -static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all) +static int show_merge_base(struct commit **rev, int rev_nr, int show_all) { - struct commit_list *result = get_merge_bases(rev1, rev2, 0); + struct commit_list *result; + + result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); if (!result) return 1; @@ -20,7 +22,7 @@ static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_al } static const char merge_base_usage[] = -"git merge-base [--all] "; +"git merge-base [--all] ..."; static struct commit *get_commit_reference(const char *arg) { @@ -38,7 +40,8 @@ static struct commit *get_commit_reference(const char *arg) int cmd_merge_base(int argc, const char **argv, const char *prefix) { - struct commit *rev1, *rev2; + struct commit **rev; + int rev_nr = 0; int show_all = 0; git_config(git_default_config, NULL); @@ -51,10 +54,15 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) usage(merge_base_usage); argc--; argv++; } - if (argc != 3) + if (argc < 3) usage(merge_base_usage); - rev1 = get_commit_reference(argv[1]); - rev2 = get_commit_reference(argv[2]); - return show_merge_base(rev1, rev2, show_all); + rev = xmalloc((argc - 1) * sizeof(*rev)); + + do { + rev[rev_nr++] = get_commit_reference(argv[1]); + argc--; argv++; + } while (argc > 1); + + return show_merge_base(rev, rev_nr, show_all); } diff --git a/commit.h b/commit.h index 77de9621d9..d163c7487b 100644 --- a/commit.h +++ b/commit.h @@ -121,6 +121,7 @@ int read_graft_file(const char *graft_file); struct commit_graft *lookup_commit_graft(const unsigned char *sha1); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); +extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup); extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); extern int register_shallow(const unsigned char *sha1); -- cgit v1.2.1 From 99f1c04be09d73a40ef0a37c6868969d40391196 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 30 Jul 2008 07:04:43 +0200 Subject: documentation: merge-base: explain "git merge-base" with more than 2 args Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- Documentation/git-merge-base.txt | 77 ++++++++++++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index 1a7ecbf8f3..2f0c5259e0 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -8,26 +8,81 @@ git-merge-base - Find as good common ancestors as possible for a merge SYNOPSIS -------- -'git merge-base' [--all] +'git merge-base' [--all] ... DESCRIPTION ----------- -'git-merge-base' finds as good a common ancestor as possible between -the two commits. That is, given two commits A and B, `git merge-base A -B` will output a commit which is reachable from both A and B through -the parent relationship. +'git-merge-base' finds best common ancestor(s) between two commits to use +in a three-way merge. One common ancestor is 'better' than another common +ancestor if the latter is an ancestor of the former. A common ancestor +that does not have any better common ancestor than it is a 'best common +ancestor', i.e. a 'merge base'. Note that there can be more than one +merge bases between two commits. -Given a selection of equally good common ancestors it should not be -relied on to decide in any particular way. - -The 'git-merge-base' algorithm is still in flux - use the source... +Among the two commits to compute their merge bases, one is specified by +the first commit argument on the command line; the other commit is a +(possibly hypothetical) commit that is a merge across all the remaining +commits on the command line. As the most common special case, giving only +two commits from the command line means computing the merge base between +the given two commits. OPTIONS ------- --all:: - Output all common ancestors for the two commits instead of - just one. + Output all merge bases for the commits, instead of just one. + +DISCUSSION +---------- + +Given two commits 'A' and 'B', `git merge-base A B` will output a commit +which is reachable from both 'A' and 'B' through the parent relationship. + +For example, with this topology: + + o---o---o---B + / + ---o---1---o---o---o---A + +the merge base between 'A' and 'B' is '1'. + +Given three commits 'A', 'B' and 'C', `git merge-base A B C` will compute the +merge base between 'A' and an hypothetical commit 'M', which is a merge +between 'B' and 'C'. For example, with this topology: + + o---o---o---o---C + / + / o---o---o---B + / / + ---2---1---o---o---o---A + +the result of `git merge-base A B C` is '1'. This is because the +equivalent topology with a merge commit 'M' between 'B' and 'C' is: + + + o---o---o---o---o + / \ + / o---o---o---o---M + / / + ---2---1---o---o---o---A + +and the result of `git merge-base A M` is '1'. Commit '2' is also a +common ancestor between 'A' and 'M', but '1' is a better common ancestor, +because '2' is an ancestor of '1'. Hence, '2' is not a merge base. + +When the history involves criss-cross merges, there can be more than one +'best' common ancestors between two commits. For example, with this +topology: + + ---1---o---A + \ / + X + / \ + ---2---o---o---B + +both '1' and '2' are merge-base of A and B. Neither one is better than +the other (both are 'best' merge base). When `--all` option is not given, +it is unspecified which best one is output. Author ------ -- cgit v1.2.1 From 940208a771066229bc6a486f6a058e332b71cfe4 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 30 Jul 2008 01:16:58 +0200 Subject: builtin-help: make some internal functions available to other builtins Make load_command_list() capable of filtering for a given prefix and loading into a pair of "struct cmdnames" supplied by the caller. Make the static add_cmdname(), exclude_cmds() and is_in_cmdlist() functions non-static. Make list_commands() accept a custom title, and work from a pair of "struct cmdnames" supplied by the caller. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Makefile | 1 + help.c | 77 ++++++++++++++++++++++++++++++++-------------------------------- help.h | 23 +++++++++++++++++++ 3 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 help.h diff --git a/Makefile b/Makefile index 52c67c1a47..83d79afd9c 100644 --- a/Makefile +++ b/Makefile @@ -355,6 +355,7 @@ LIB_H += git-compat-util.h LIB_H += graph.h LIB_H += grep.h LIB_H += hash.h +LIB_H += help.h LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h diff --git a/help.c b/help.c index 3cb1962896..88c0d5b340 100644 --- a/help.c +++ b/help.c @@ -9,6 +9,7 @@ #include "common-cmds.h" #include "parse-options.h" #include "run-command.h" +#include "help.h" static struct man_viewer_list { struct man_viewer_list *next; @@ -300,18 +301,11 @@ static inline void mput_char(char c, unsigned int num) putchar(c); } -static struct cmdnames { - int alloc; - int cnt; - struct cmdname { - size_t len; - char name[1]; - } **names; -} main_cmds, other_cmds; +struct cmdnames main_cmds, other_cmds; -static void add_cmdname(struct cmdnames *cmds, const char *name, int len) +void add_cmdname(struct cmdnames *cmds, const char *name, int len) { - struct cmdname *ent = xmalloc(sizeof(*ent) + len); + struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1); ent->len = len; memcpy(ent->name, name, len); @@ -342,7 +336,7 @@ static void uniq(struct cmdnames *cmds) cmds->cnt = j; } -static void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) +void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes) { int ci, cj, ei; int cmp; @@ -418,11 +412,11 @@ static int is_executable(const char *name) } static unsigned int list_commands_in_dir(struct cmdnames *cmds, - const char *path) + const char *path, + const char *prefix) { unsigned int longest = 0; - const char *prefix = "git-"; - int prefix_len = strlen(prefix); + int prefix_len; DIR *dir = opendir(path); struct dirent *de; struct strbuf buf = STRBUF_INIT; @@ -430,6 +424,9 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds, if (!dir) return 0; + if (!prefix) + prefix = "git-"; + prefix_len = strlen(prefix); strbuf_addf(&buf, "%s/", path); len = buf.len; @@ -460,7 +457,9 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds, return longest; } -static unsigned int load_command_list(void) +unsigned int load_command_list(const char *prefix, + struct cmdnames *main_cmds, + struct cmdnames *other_cmds) { unsigned int longest = 0; unsigned int len; @@ -469,7 +468,7 @@ static unsigned int load_command_list(void) const char *exec_path = git_exec_path(); if (exec_path) - longest = list_commands_in_dir(&main_cmds, exec_path); + longest = list_commands_in_dir(main_cmds, exec_path, prefix); if (!env_path) { fprintf(stderr, "PATH not set\n"); @@ -481,7 +480,7 @@ static unsigned int load_command_list(void) if ((colon = strchr(path, PATH_SEP))) *colon = 0; - len = list_commands_in_dir(&other_cmds, path); + len = list_commands_in_dir(other_cmds, path, prefix); if (len > longest) longest = len; @@ -491,36 +490,38 @@ static unsigned int load_command_list(void) } free(paths); - qsort(main_cmds.names, main_cmds.cnt, - sizeof(*main_cmds.names), cmdname_compare); - uniq(&main_cmds); + qsort(main_cmds->names, main_cmds->cnt, + sizeof(*main_cmds->names), cmdname_compare); + uniq(main_cmds); - qsort(other_cmds.names, other_cmds.cnt, - sizeof(*other_cmds.names), cmdname_compare); - uniq(&other_cmds); - exclude_cmds(&other_cmds, &main_cmds); + qsort(other_cmds->names, other_cmds->cnt, + sizeof(*other_cmds->names), cmdname_compare); + uniq(other_cmds); + exclude_cmds(other_cmds, main_cmds); return longest; } -static void list_commands(void) +void list_commands(const char *title, unsigned int longest, + struct cmdnames *main_cmds, struct cmdnames *other_cmds) { - unsigned int longest = load_command_list(); const char *exec_path = git_exec_path(); - if (main_cmds.cnt) { - printf("available git commands in '%s'\n", exec_path); - printf("----------------------------"); - mput_char('-', strlen(exec_path)); + if (main_cmds->cnt) { + printf("available %s in '%s'\n", title, exec_path); + printf("----------------"); + mput_char('-', strlen(title) + strlen(exec_path)); putchar('\n'); - pretty_print_string_list(&main_cmds, longest); + pretty_print_string_list(main_cmds, longest); putchar('\n'); } - if (other_cmds.cnt) { - printf("git commands available from elsewhere on your $PATH\n"); - printf("---------------------------------------------------\n"); - pretty_print_string_list(&other_cmds, longest); + if (other_cmds->cnt) { + printf("%s available from elsewhere on your $PATH\n", title); + printf("---------------------------------------"); + mput_char('-', strlen(title)); + putchar('\n'); + pretty_print_string_list(other_cmds, longest); putchar('\n'); } } @@ -542,7 +543,7 @@ void list_common_cmds_help(void) } } -static int is_in_cmdlist(struct cmdnames *c, const char *s) +int is_in_cmdlist(struct cmdnames *c, const char *s) { int i; for (i = 0; i < c->cnt; i++) @@ -553,7 +554,6 @@ static int is_in_cmdlist(struct cmdnames *c, const char *s) static int is_git_command(const char *s) { - load_command_list(); return is_in_cmdlist(&main_cmds, s) || is_in_cmdlist(&other_cmds, s); } @@ -698,8 +698,9 @@ int cmd_help(int argc, const char **argv, const char *prefix) builtin_help_usage, 0); if (show_all) { + unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); printf("usage: %s\n\n", git_usage_string); - list_commands(); + list_commands("git commands", longest, &main_cmds, &other_cmds); printf("%s\n", git_more_info_string); return 0; } diff --git a/help.h b/help.h new file mode 100644 index 0000000000..d614e5491b --- /dev/null +++ b/help.h @@ -0,0 +1,23 @@ +#ifndef HELP_H +#define HELP_H + +struct cmdnames { + int alloc; + int cnt; + struct cmdname { + size_t len; + char name[FLEX_ARRAY]; + } **names; +}; + +unsigned int load_command_list(const char *prefix, + struct cmdnames *main_cmds, + struct cmdnames *other_cmds); +void add_cmdname(struct cmdnames *cmds, const char *name, int len); +/* Here we require that excludes is a sorted list. */ +void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); +int is_in_cmdlist(struct cmdnames *c, const char *s); +void list_commands(const char *title, unsigned int longest, + struct cmdnames *main_cmds, struct cmdnames *other_cmds); + +#endif /* HELP_H */ -- cgit v1.2.1 From 87091b495e8af0daf317c0d0e08ac3ead74a0bb9 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 30 Jul 2008 01:16:59 +0200 Subject: builtin-merge: allow using a custom strategy Allow using a custom strategy, as long as it's named git-merge-foo. The error handling is now done using is_git_command(). The list of available strategies is now shown by list_commands(). If an invalid strategy is supplied, like -s foobar, then git-merge would list all git-merge-* commands. This is not perfect, since for example git-merge-index is not a valid strategy. These are removed from the output by scanning the list of main commands; if the git-merge-foo command is listed in the all_strategy list, then it's shown, otherwise excluded. This does not exclude commands somewhere else in the PATH, where custom strategies are expected. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-merge.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index e78fa18b3a..1f9389bfd7 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -22,6 +22,7 @@ #include "log-tree.h" #include "color.h" #include "rerere.h" +#include "help.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -77,7 +78,9 @@ static int option_parse_message(const struct option *opt, static struct strategy *get_strategy(const char *name) { int i; - struct strbuf err; + struct strategy *ret; + static struct cmdnames main_cmds, other_cmds; + static int longest; if (!name) return NULL; @@ -86,12 +89,37 @@ static struct strategy *get_strategy(const char *name) if (!strcmp(name, all_strategy[i].name)) return &all_strategy[i]; - strbuf_init(&err, 0); - for (i = 0; i < ARRAY_SIZE(all_strategy); i++) - strbuf_addf(&err, " %s", all_strategy[i].name); - fprintf(stderr, "Could not find merge strategy '%s'.\n", name); - fprintf(stderr, "Available strategies are:%s.\n", err.buf); - exit(1); + if (!longest) { + struct cmdnames not_strategies; + + memset(&main_cmds, 0, sizeof(struct cmdnames)); + memset(&other_cmds, 0, sizeof(struct cmdnames)); + memset(¬_strategies, 0, sizeof(struct cmdnames)); + longest = load_command_list("git-merge-", &main_cmds, + &other_cmds); + for (i = 0; i < main_cmds.cnt; i++) { + int j, found = 0; + struct cmdname *ent = main_cmds.names[i]; + for (j = 0; j < ARRAY_SIZE(all_strategy); j++) + if (!strncmp(ent->name, all_strategy[j].name, ent->len) + && !all_strategy[j].name[ent->len]) + found = 1; + if (!found) + add_cmdname(¬_strategies, ent->name, ent->len); + exclude_cmds(&main_cmds, ¬_strategies); + } + } + if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { + + fprintf(stderr, "Could not find merge strategy '%s'.\n\n", name); + list_commands("strategies", longest, &main_cmds, &other_cmds); + exit(1); + } + + ret = xmalloc(sizeof(struct strategy)); + memset(ret, 0, sizeof(struct strategy)); + ret->name = xstrdup(name); + return ret; } static void append_strategy(struct strategy *s) -- cgit v1.2.1 From 1b1d78fe77d676bb79a9272ff6f3517697811990 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 30 Jul 2008 01:17:00 +0200 Subject: Add a new test for using a custom merge strategy Testing is done by creating a simple git-merge-theirs strategy which is the opposite of ours. Using this in real merges is not recommended but it's perfect for our testing needs. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7606-merge-custom.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100755 t/t7606-merge-custom.sh diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh new file mode 100755 index 0000000000..07e0fc7974 --- /dev/null +++ b/t/t7606-merge-custom.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +test_description='git-merge + +Testing a custom strategy.' + +. ./test-lib.sh + +cat >git-merge-theirs <c0.c && + git add c0.c && + git commit -m c0 && + git tag c0 && + echo c1 >c1.c && + git add c1.c && + git commit -m c1 && + git tag c1 && + git reset --hard c0 && + echo c2 >c2.c && + git add c2.c && + git commit -m c2 && + git tag c2 +' + +test_expect_success 'merge c2 with a custom strategy' ' + git reset --hard c1 && + git merge -s theirs c2 && + test "$(git rev-parse c1)" != "$(git rev-parse HEAD)" && + test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && + test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && + test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" && + git diff --exit-code && + test -f c0.c && + test ! -f c1.c && + test -f c2.c +' + +test_done -- cgit v1.2.1 From dce61e728b89611109e76cbc9fa897fe49d7869d Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Wed, 30 Jul 2008 01:17:01 +0200 Subject: Add a second testcase for handling invalid strategies in git-merge This one tests '-s index' which is interesting because git-merge-index is an existing git command but it is not a valid strategy. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- t/t7600-merge.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 5eeb6c2b27..0329aee2cd 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -230,6 +230,10 @@ test_expect_success 'test option parsing' ' test_must_fail git merge ' +test_expect_success 'reject non-strategy with a git-merge-foo name' ' + test_must_fail git merge -s index c1 +' + test_expect_success 'merge c0 with c1' ' git reset --hard c0 && git merge c1 && -- cgit v1.2.1 From a7a66921772ca5e609d283b0b73b8dd446a2a506 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 29 Jul 2008 23:51:41 -0700 Subject: merge-base-many: add trivial tests based on the documentation Signed-off-by: Junio C Hamano --- t/t6010-merge-base.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index b6e57b2426..04e4b7c5c2 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -108,4 +108,52 @@ test_expect_success 'compute merge-base (all)' \ 'MB=$(git merge-base --all PL PR) && expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"' +# Another set to demonstrate base between one commit and a merge +# in the documentation. + +test_expect_success 'merge-base for octopus-step (setup)' ' + test_tick && git commit --allow-empty -m root && git tag MMR && + test_tick && git commit --allow-empty -m 1 && git tag MM1 && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m A && git tag MMA && + git checkout MM1 && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m B && git tag MMB && + git checkout MMR && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m C && git tag MMC +' + +test_expect_success 'merge-base A B C' ' + MB=$(git merge-base --all MMA MMB MMC) && + MM1=$(git rev-parse --verify MM1) && + test "$MM1" = "$MB" +' + +test_expect_success 'criss-cross merge-base for octopus-step (setup)' ' + git reset --hard MMR && + test_tick && git commit --allow-empty -m 1 && git tag CC1 && + git reset --hard E && + test_tick && git commit --allow-empty -m 2 && git tag CC2 && + test_tick && git merge -s ours CC1 && + test_tick && git commit --allow-empty -m o && + test_tick && git commit --allow-empty -m B && git tag CCB && + git reset --hard CC1 && + test_tick && git merge -s ours CC2 && + test_tick && git commit --allow-empty -m A && git tag CCA +' + +test_expect_success 'merge-base B A^^ A^^2' ' + MB0=$(git merge-base --all CCB CCA^^ CCA^^2 | sort) && + MB1=$(git rev-parse CC1 CC2 | sort) && + test "$MB0" = "$MB1" +' + test_done -- cgit v1.2.1 From c5dc9a28298afb75d2be6b212d420fc89c258aa0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 27 Jul 2008 13:47:22 -0700 Subject: git-merge-octopus: use (merge-base A (merge B C D E...)) for stepwise merge Suppose you have this topology, and you are trying to make an octopus across A, B and C (you are at C and merging A and B into your branch). The protoccol between "git merge" and merge strategies is for the former to pass common ancestor(s), '--' and then commits being merged. git-merge-octopus does not produce the final merge in one-go. It iteratively produces pairwise merges. So the first step might be to come up with a merge between B and C: o---o---o---o---C / : / o---o---o---B..(M) / / ---1---2---o---o---o---A and for that, "1" is used as the merge base, not because it is the base across A, B and C but because it is the base between B and C. For this merge, A does not matter. I drew M in parentheses and lines between B and C to it in dotted line because we actually do _not_ create a real commit --- the only thing we need is a tree object, in order to proceed to the next step. Then the final merge result is obtained by merging tree of (M) and A using their common ancestor. For that, we _could_ still use "1" as the merge base. But if you imagine a case where you started from A and M, you would _not_ pick "1" as the merge base; you would rather use "2" which is a better base for this merge. That is why git-merge-octopus ignores the merge base given by "merge" but computes its own. The comment at the end of git-merge-octopus talks about "merge reference commit", that we used to update it to common found in this round, and that that updating was pointless. After the first round of merge to produce the tree for M (but without actually creating the commit object M itself), in order to figure out the merge base used to merge that with A in the second round, we used to use A and "1" (which was merge base between B and C). That was pointless --- "merge-base A 1" is guaranteed to give a base that is no better than either "merge-base A B" or "merge-base A C". So the current code keeps using the original head (iow, MRC=C, because in this case we are starting from C and merging B and then A into it). This trickerly was necessary only because we avoided creating the extra merge commit object M. Side note. An alternative implementation could have been to actually record it as a real merge commit M, and then let the two-commit merge-base compute the base between A and M when merging A to the result of the previous round, but we avoided creating M, at the expense of potentially using suboptimal base in the later rounds. But we do not have to be that pessimistic. We can instead accumulate the commits we have merged so far in MRC, and have merge_bases_many() compute the merge base for the new head being merged and the heads we have processed so far, which can give a better base than what we currently do. Signed-off-by: Junio C Hamano --- git-merge-octopus.sh | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 645e1147dc..1dadbb4966 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -61,7 +61,7 @@ do exit 2 esac - common=$(git merge-base --all $MRC $SHA1) || + common=$(git merge-base --all $SHA1 $MRC) || die "Unable to find common commit with $SHA1" case "$LF$common$LF" in @@ -100,14 +100,7 @@ do next=$(git write-tree 2>/dev/null) fi - # We have merged the other branch successfully. Ideally - # we could implement OR'ed heads in merge-base, and keep - # a list of commits we have merged so far in MRC to feed - # them to merge-base, but we approximate it by keep using - # the current MRC. We used to update it to $common, which - # was incorrectly doing AND'ed merge-base here, which was - # unneeded. - + MRC="$MRC $SHA1" MRT=$next done -- cgit v1.2.1 From 6e4a86d2ed2d798c5462e5e324fefb8614be52a8 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 31 Jul 2008 00:38:07 +0200 Subject: builtin-help: always load_command_list() in cmd_help() When cmd_help() is called, we always need the list of main and other commands, not just when the list of all commands is shown. Before this patch 'git help diff' invoked 'man gitdiff' because cmd_to_page() thought 'diff' is not a git command. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.c b/help.c index 88c0d5b340..968f3683ed 100644 --- a/help.c +++ b/help.c @@ -690,6 +690,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; const char *alias; + unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); setup_git_directory_gently(&nongit); git_config(git_help_config, NULL); @@ -698,7 +699,6 @@ int cmd_help(int argc, const char **argv, const char *prefix) builtin_help_usage, 0); if (show_all) { - unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); printf("usage: %s\n\n", git_usage_string); list_commands("git commands", longest, &main_cmds, &other_cmds); printf("%s\n", git_more_info_string); -- cgit v1.2.1 From 60d30b02fc188da269da094df8bc67139ad6eb04 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 31 Jul 2008 22:17:13 -0700 Subject: revision.c: whitespace fix Signed-off-by: Junio C Hamano --- revision.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revision.c b/revision.c index e75079a6e1..71f0bea899 100644 --- a/revision.c +++ b/revision.c @@ -489,7 +489,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, p->object.flags |= SEEN; insert_by_date_cached(p, list, cached_base, cache_ptr); } - if(revs->first_parent_only) + if (revs->first_parent_only) break; } return 0; -- cgit v1.2.1 From 6546b5931e5608dfdcd3e4fbf9ebc90ee0982b56 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 31 Jul 2008 01:17:41 -0700 Subject: revision traversal: show full history with merge simplification The --full-history traversal keeps all merges in addition to non-merge commits that touch paths in the given pathspec. This is useful to view both sides of a merge in a topology like this: A---M---o / / ---O---B even when A and B makes identical change to the given paths. The revision traversal without --full-history aims to come up with the simplest history to explain the final state of the tree, and one of the side branches can be pruned away. The behaviour to keep all merges however is inconvenient if neither A nor B touches the paths we are interested in. --full-history reduces the topology to: ---O---M---o in such a case, without removing M. This adds a post processing phase on top of --full-history traversal to remove needless merges from the resulting history. The idea is to compute, for each commit in the "full history" result set, the commit that should replace it in the simplified history. The commit to replace it in the final history is determined as follows: * In any case, we first figure out the replacement commits of parents of the commit we are looking at. The commit we are looking at is rewritten as if the replacement commits of its original parents are its parents. While doing so, we reduce the redundant parents from the rewritten parent list by not just removing the identical ones, but also removing a parent that is an ancestor of another parent. * After the above parent simplification, if the commit is a root commit, an UNINTERESTING commit, a merge commit, or modifies the paths we are interested in, then the replacement commit of the commit is itself. In other words, such a commit is not dropped from the final result. The first point above essentially means that the history is rewritten in the bottom up direction. We can rewrite the parent list of a commit only after we know how all of its parents are rewritten. This means that the processing needs to happen on the full history (i.e. after limit_list()). Signed-off-by: Junio C Hamano --- Documentation/rev-list-options.txt | 10 ++- revision.c | 171 ++++++++++++++++++++++++++++++++----- revision.h | 1 + 3 files changed, 160 insertions(+), 22 deletions(-) diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 3aa38097e6..ee6822a85d 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -193,12 +193,18 @@ endif::git-rev-list[] --full-history:: - Show also parts of history irrelevant to current state of a given - path. This turns off history simplification, which removed merges + Show also parts of history irrelevant to current state of given + paths. This turns off history simplification, which removed merges which didn't change anything at all at some child. It will still actually simplify away merges that didn't change anything at all into either child. +--simplify-merges:: + + Simplify away commits that did not change the given paths, similar + to `--full-history`, and further remove merges none of whose + parent history changes the given paths. + --no-merges:: Do not print commits with more than one parent. diff --git a/revision.c b/revision.c index 71f0bea899..662d66be7e 100644 --- a/revision.c +++ b/revision.c @@ -1045,6 +1045,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--topo-order")) { revs->lifo = 1; revs->topo_order = 1; + } else if (!strcmp(arg, "--simplify-merges")) { + revs->simplify_merges = 1; + revs->rewrite_parents = 1; + revs->simplify_history = 0; + revs->limited = 1; } else if (!strcmp(arg, "--date-order")) { revs->lifo = 0; revs->topo_order = 1; @@ -1378,6 +1383,150 @@ static void add_child(struct rev_info *revs, struct commit *parent, struct commi l->next = add_decoration(&revs->children, &parent->object, l); } +static int remove_duplicate_parents(struct commit *commit) +{ + struct commit_list **pp, *p; + int surviving_parents; + + /* Examine existing parents while marking ones we have seen... */ + pp = &commit->parents; + while ((p = *pp) != NULL) { + struct commit *parent = p->item; + if (parent->object.flags & TMP_MARK) { + *pp = p->next; + continue; + } + parent->object.flags |= TMP_MARK; + pp = &p->next; + } + /* count them while clearing the temporary mark */ + surviving_parents = 0; + for (p = commit->parents; p; p = p->next) { + p->item->object.flags &= ~TMP_MARK; + surviving_parents++; + } + return surviving_parents; +} + +static struct commit_list **simplify_one(struct commit *commit, struct commit_list **tail) +{ + struct commit_list *p; + int cnt; + + /* + * We store which commit each one simplifies to in its util field. + * Have we handled this one? + */ + if (commit->util) + return tail; + + /* + * An UNINTERESTING commit simplifies to itself, so does a + * root commit. We do not rewrite parents of such commit + * anyway. + */ + if ((commit->object.flags & UNINTERESTING) || !commit->parents) { + commit->util = commit; + return tail; + } + + /* + * Do we know what commit all of our parents should be rewritten to? + * Otherwise we are not ready to rewrite this one yet. + */ + for (cnt = 0, p = commit->parents; p; p = p->next) { + if (!p->item->util) { + tail = &commit_list_insert(p->item, tail)->next; + cnt++; + } + } + if (cnt) + return tail; + + /* + * Rewrite our list of parents. + */ + for (p = commit->parents; p; p = p->next) + p->item = p->item->util; + cnt = remove_duplicate_parents(commit); + + /* + * It is possible that we are a merge and one side branch + * does not have any commit that touches the given paths; + * in such a case, the immediate parents will be rewritten + * to different commits. + * + * o----X X: the commit we are looking at; + * / / o: a commit that touches the paths; + * ---o----' + * + * Further reduce the parents by removing redundant parents. + */ + if (1 < cnt) { + struct commit_list *h = reduce_heads(commit->parents); + cnt = commit_list_count(h); + free_commit_list(commit->parents); + commit->parents = h; + } + + /* + * A commit simplifies to itself if it is a root, if it is + * UNINTERESTING, if it touches the given paths, or if it is a + * merge and its parents simplifies to more than one commits + * (the first two cases are already handled at the beginning of + * this function). + * + * Otherwise, it simplifies to what its sole parent simplifies to. + */ + if (!cnt || + (commit->object.flags & UNINTERESTING) || + !(commit->object.flags & TREESAME) || + (1 < cnt)) + commit->util = commit; + else + commit->util = commit->parents->item->util; + return tail; +} + +static void simplify_merges(struct rev_info *revs) +{ + struct commit_list *list; + struct commit_list *yet_to_do, **tail; + + /* feed the list reversed */ + yet_to_do = NULL; + for (list = revs->commits; list; list = list->next) + commit_list_insert(list->item, &yet_to_do); + while (yet_to_do) { + list = yet_to_do; + yet_to_do = NULL; + tail = &yet_to_do; + while (list) { + struct commit *commit = list->item; + struct commit_list *next = list->next; + free(list); + list = next; + tail = simplify_one(commit, tail); + } + } + + /* clean up the result, removing the simplified ones */ + list = revs->commits; + revs->commits = NULL; + tail = &revs->commits; + while (list) { + struct commit *commit = list->item; + struct commit_list *next = list->next; + free(list); + list = next; + if (commit->util == commit) + tail = &commit_list_insert(commit, tail)->next; + } + + /* sort topologically at the end */ + sort_in_topological_order(&revs->commits, revs->lifo); +} + static void set_children(struct rev_info *revs) { struct commit_list *l; @@ -1418,6 +1567,8 @@ int prepare_revision_walk(struct rev_info *revs) return -1; if (revs->topo_order) sort_in_topological_order(&revs->commits, revs->lifo); + if (revs->simplify_merges) + simplify_merges(revs); if (revs->children.name) set_children(revs); return 0; @@ -1450,26 +1601,6 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp } } -static void remove_duplicate_parents(struct commit *commit) -{ - struct commit_list **pp, *p; - - /* Examine existing parents while marking ones we have seen... */ - pp = &commit->parents; - while ((p = *pp) != NULL) { - struct commit *parent = p->item; - if (parent->object.flags & TMP_MARK) { - *pp = p->next; - continue; - } - parent->object.flags |= TMP_MARK; - pp = &p->next; - } - /* ... and clear the temporary mark */ - for (p = commit->parents; p; p = p->next) - p->item->object.flags &= ~TMP_MARK; -} - static int rewrite_parents(struct rev_info *revs, struct commit *commit) { struct commit_list **pp = &commit->parents; diff --git a/revision.h b/revision.h index f64e8ce7ff..dfa06b5210 100644 --- a/revision.h +++ b/revision.h @@ -41,6 +41,7 @@ struct rev_info { simplify_history:1, lifo:1, topo_order:1, + simplify_merges:1, tag_objects:1, tree_objects:1, blob_objects:1, -- cgit v1.2.1 From 3d78d1f18f211fd3d3e95977dee85a17a2aeb16c Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sat, 2 Aug 2008 10:08:38 +0200 Subject: Builtin git-help. This patch splits out git-help's functions to builtin-help.c and leaves only functions used by other builtins in help.c. First this removes git-help's functions from libgit which are not interesting for other builtins, second this makes 'git help help' work again. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Makefile | 3 +- builtin-help.c | 462 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ help.c | 464 --------------------------------------------------------- help.h | 6 + 4 files changed, 470 insertions(+), 465 deletions(-) create mode 100644 builtin-help.c diff --git a/Makefile b/Makefile index 83d79afd9c..3bb7c774b7 100644 --- a/Makefile +++ b/Makefile @@ -519,6 +519,7 @@ BUILTIN_OBJS += builtin-for-each-ref.o BUILTIN_OBJS += builtin-fsck.o BUILTIN_OBJS += builtin-gc.o BUILTIN_OBJS += builtin-grep.o +BUILTIN_OBJS += builtin-help.o BUILTIN_OBJS += builtin-init-db.o BUILTIN_OBJS += builtin-log.o BUILTIN_OBJS += builtin-ls-files.o @@ -1085,7 +1086,7 @@ git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -help.o: help.c common-cmds.h GIT-CFLAGS +builtin-help.o: builtin-help.c common-cmds.h GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ diff --git a/builtin-help.c b/builtin-help.c new file mode 100644 index 0000000000..391f749376 --- /dev/null +++ b/builtin-help.c @@ -0,0 +1,462 @@ +/* + * builtin-help.c + * + * Builtin help command + */ +#include "cache.h" +#include "builtin.h" +#include "exec_cmd.h" +#include "common-cmds.h" +#include "parse-options.h" +#include "run-command.h" +#include "help.h" + +static struct man_viewer_list { + struct man_viewer_list *next; + char name[FLEX_ARRAY]; +} *man_viewer_list; + +static struct man_viewer_info_list { + struct man_viewer_info_list *next; + const char *info; + char name[FLEX_ARRAY]; +} *man_viewer_info_list; + +enum help_format { + HELP_FORMAT_MAN, + HELP_FORMAT_INFO, + HELP_FORMAT_WEB, +}; + +static int show_all = 0; +static enum help_format help_format = HELP_FORMAT_MAN; +static struct option builtin_help_options[] = { + OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), + OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), + OPT_SET_INT('w', "web", &help_format, "show manual in web browser", + HELP_FORMAT_WEB), + OPT_SET_INT('i', "info", &help_format, "show info page", + HELP_FORMAT_INFO), + OPT_END(), +}; + +static const char * const builtin_help_usage[] = { + "git help [--all] [--man|--web|--info] [command]", + NULL +}; + +static enum help_format parse_help_format(const char *format) +{ + if (!strcmp(format, "man")) + return HELP_FORMAT_MAN; + if (!strcmp(format, "info")) + return HELP_FORMAT_INFO; + if (!strcmp(format, "web") || !strcmp(format, "html")) + return HELP_FORMAT_WEB; + die("unrecognized help format '%s'", format); +} + +static const char *get_man_viewer_info(const char *name) +{ + struct man_viewer_info_list *viewer; + + for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) + { + if (!strcasecmp(name, viewer->name)) + return viewer->info; + } + return NULL; +} + +static int check_emacsclient_version(void) +{ + struct strbuf buffer = STRBUF_INIT; + struct child_process ec_process; + const char *argv_ec[] = { "emacsclient", "--version", NULL }; + int version; + + /* emacsclient prints its version number on stderr */ + memset(&ec_process, 0, sizeof(ec_process)); + ec_process.argv = argv_ec; + ec_process.err = -1; + ec_process.stdout_to_stderr = 1; + if (start_command(&ec_process)) { + fprintf(stderr, "Failed to start emacsclient.\n"); + return -1; + } + strbuf_read(&buffer, ec_process.err, 20); + close(ec_process.err); + + /* + * Don't bother checking return value, because "emacsclient --version" + * seems to always exits with code 1. + */ + finish_command(&ec_process); + + if (prefixcmp(buffer.buf, "emacsclient")) { + fprintf(stderr, "Failed to parse emacsclient version.\n"); + strbuf_release(&buffer); + return -1; + } + + strbuf_remove(&buffer, 0, strlen("emacsclient")); + version = atoi(buffer.buf); + + if (version < 22) { + fprintf(stderr, + "emacsclient version '%d' too old (< 22).\n", + version); + strbuf_release(&buffer); + return -1; + } + + strbuf_release(&buffer); + return 0; +} + +static void exec_woman_emacs(const char* path, const char *page) +{ + if (!check_emacsclient_version()) { + /* This works only with emacsclient version >= 22. */ + struct strbuf man_page = STRBUF_INIT; + + if (!path) + path = "emacsclient"; + strbuf_addf(&man_page, "(woman \"%s\")", page); + execlp(path, "emacsclient", "-e", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); + } +} + +static void exec_man_konqueror(const char* path, const char *page) +{ + const char *display = getenv("DISPLAY"); + if (display && *display) { + struct strbuf man_page = STRBUF_INIT; + const char *filename = "kfmclient"; + + /* It's simpler to launch konqueror using kfmclient. */ + if (path) { + const char *file = strrchr(path, '/'); + if (file && !strcmp(file + 1, "konqueror")) { + char *new = xstrdup(path); + char *dest = strrchr(new, '/'); + + /* strlen("konqueror") == strlen("kfmclient") */ + strcpy(dest + 1, "kfmclient"); + path = new; + } + if (file) + filename = file; + } else + path = "kfmclient"; + strbuf_addf(&man_page, "man:%s(1)", page); + execlp(path, filename, "newTab", man_page.buf, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); + } +} + +static void exec_man_man(const char* path, const char *page) +{ + if (!path) + path = "man"; + execlp(path, "man", page, NULL); + warning("failed to exec '%s': %s", path, strerror(errno)); +} + +static void exec_man_cmd(const char *cmd, const char *page) +{ + struct strbuf shell_cmd = STRBUF_INIT; + strbuf_addf(&shell_cmd, "%s %s", cmd, page); + execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); + warning("failed to exec '%s': %s", cmd, strerror(errno)); +} + +static void add_man_viewer(const char *name) +{ + struct man_viewer_list **p = &man_viewer_list; + size_t len = strlen(name); + + while (*p) + p = &((*p)->next); + *p = xcalloc(1, (sizeof(**p) + len + 1)); + strncpy((*p)->name, name, len); +} + +static int supported_man_viewer(const char *name, size_t len) +{ + return (!strncasecmp("man", name, len) || + !strncasecmp("woman", name, len) || + !strncasecmp("konqueror", name, len)); +} + +static void do_add_man_viewer_info(const char *name, + size_t len, + const char *value) +{ + struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); + + strncpy(new->name, name, len); + new->info = xstrdup(value); + new->next = man_viewer_info_list; + man_viewer_info_list = new; +} + +static int add_man_viewer_path(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + do_add_man_viewer_info(name, len, value); + else + warning("'%s': path for unsupported man viewer.\n" + "Please consider using 'man..cmd' instead.", + name); + + return 0; +} + +static int add_man_viewer_cmd(const char *name, + size_t len, + const char *value) +{ + if (supported_man_viewer(name, len)) + warning("'%s': cmd for supported man viewer.\n" + "Please consider using 'man..path' instead.", + name); + else + do_add_man_viewer_info(name, len, value); + + return 0; +} + +static int add_man_viewer_info(const char *var, const char *value) +{ + const char *name = var + 4; + const char *subkey = strrchr(name, '.'); + + if (!subkey) + return error("Config with no key for man viewer: %s", name); + + if (!strcmp(subkey, ".path")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_path(name, subkey - name, value); + } + if (!strcmp(subkey, ".cmd")) { + if (!value) + return config_error_nonbool(var); + return add_man_viewer_cmd(name, subkey - name, value); + } + + warning("'%s': unsupported man viewer sub key.", subkey); + return 0; +} + +static int git_help_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "help.format")) { + if (!value) + return config_error_nonbool(var); + help_format = parse_help_format(value); + return 0; + } + if (!strcmp(var, "man.viewer")) { + if (!value) + return config_error_nonbool(var); + add_man_viewer(value); + return 0; + } + if (!prefixcmp(var, "man.")) + return add_man_viewer_info(var, value); + + return git_default_config(var, value, cb); +} + +struct cmdnames main_cmds, other_cmds; + +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)); + puts(common_cmds[i].help); + } +} + +static int is_git_command(const char *s) +{ + return is_in_cmdlist(&main_cmds, s) || + is_in_cmdlist(&other_cmds, s); +} + +static const char *prepend(const char *prefix, const char *cmd) +{ + size_t pre_len = strlen(prefix); + size_t cmd_len = strlen(cmd); + char *p = xmalloc(pre_len + cmd_len + 1); + memcpy(p, prefix, pre_len); + strcpy(p + pre_len, cmd); + return p; +} + +static const char *cmd_to_page(const char *git_cmd) +{ + if (!git_cmd) + return "git"; + else if (!prefixcmp(git_cmd, "git")) + return git_cmd; + else if (is_git_command(git_cmd)) + return prepend("git-", git_cmd); + else + return prepend("git", git_cmd); +} + +static void setup_man_path(void) +{ + struct strbuf new_path; + const char *old_path = getenv("MANPATH"); + + strbuf_init(&new_path, 0); + + /* We should always put ':' after our path. If there is no + * old_path, the ':' at the end will let 'man' to try + * system-wide paths after ours to find the manual page. If + * there is old_path, we need ':' as delimiter. */ + strbuf_addstr(&new_path, GIT_MAN_PATH); + strbuf_addch(&new_path, ':'); + if (old_path) + strbuf_addstr(&new_path, old_path); + + setenv("MANPATH", new_path.buf, 1); + + strbuf_release(&new_path); +} + +static void exec_viewer(const char *name, const char *page) +{ + const char *info = get_man_viewer_info(name); + + if (!strcasecmp(name, "man")) + exec_man_man(info, page); + else if (!strcasecmp(name, "woman")) + exec_woman_emacs(info, page); + else if (!strcasecmp(name, "konqueror")) + exec_man_konqueror(info, page); + else if (info) + exec_man_cmd(info, page); + else + warning("'%s': unknown man viewer.", name); +} + +static void show_man_page(const char *git_cmd) +{ + struct man_viewer_list *viewer; + const char *page = cmd_to_page(git_cmd); + + setup_man_path(); + for (viewer = man_viewer_list; viewer; viewer = viewer->next) + { + exec_viewer(viewer->name, page); /* will return when unable */ + } + exec_viewer("man", page); + die("no man viewer handled the request"); +} + +static void show_info_page(const char *git_cmd) +{ + const char *page = cmd_to_page(git_cmd); + setenv("INFOPATH", GIT_INFO_PATH, 1); + execlp("info", "info", "gitman", page, NULL); +} + +static void get_html_page_path(struct strbuf *page_path, const char *page) +{ + struct stat st; + const char *html_path = system_path(GIT_HTML_PATH); + + /* Check that we have a git documentation directory. */ + if (stat(mkpath("%s/git.html", html_path), &st) + || !S_ISREG(st.st_mode)) + die("'%s': not a documentation directory.", html_path); + + strbuf_init(page_path, 0); + strbuf_addf(page_path, "%s/%s.html", html_path, page); +} + +/* + * If open_html is not defined in a platform-specific way (see for + * example compat/mingw.h), we use the script web--browse to display + * HTML. + */ +#ifndef open_html +void open_html(const char *path) +{ + execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); +} +#endif + +static void show_html_page(const char *git_cmd) +{ + const char *page = cmd_to_page(git_cmd); + struct strbuf page_path; /* it leaks but we exec bellow */ + + get_html_page_path(&page_path, page); + + open_html(page_path.buf); +} + +int cmd_help(int argc, const char **argv, const char *prefix) +{ + int nongit; + const char *alias; + unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); + + setup_git_directory_gently(&nongit); + git_config(git_help_config, NULL); + + argc = parse_options(argc, argv, builtin_help_options, + builtin_help_usage, 0); + + if (show_all) { + printf("usage: %s\n\n", git_usage_string); + list_commands("git commands", longest, &main_cmds, &other_cmds); + printf("%s\n", git_more_info_string); + return 0; + } + + if (!argv[0]) { + printf("usage: %s\n\n", git_usage_string); + list_common_cmds_help(); + printf("\n%s\n", git_more_info_string); + return 0; + } + + alias = alias_lookup(argv[0]); + if (alias && !is_git_command(argv[0])) { + printf("`git %s' is aliased to `%s'\n", argv[0], alias); + return 0; + } + + switch (help_format) { + case HELP_FORMAT_MAN: + show_man_page(argv[0]); + break; + case HELP_FORMAT_INFO: + show_info_page(argv[0]); + break; + case HELP_FORMAT_WEB: + show_html_page(argv[0]); + break; + } + + return 0; +} diff --git a/help.c b/help.c index 968f3683ed..1afbac0927 100644 --- a/help.c +++ b/help.c @@ -1,278 +1,8 @@ -/* - * builtin-help.c - * - * Builtin help-related commands (help, usage, version) - */ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" -#include "common-cmds.h" -#include "parse-options.h" -#include "run-command.h" #include "help.h" -static struct man_viewer_list { - struct man_viewer_list *next; - char name[FLEX_ARRAY]; -} *man_viewer_list; - -static struct man_viewer_info_list { - struct man_viewer_info_list *next; - const char *info; - char name[FLEX_ARRAY]; -} *man_viewer_info_list; - -enum help_format { - HELP_FORMAT_MAN, - HELP_FORMAT_INFO, - HELP_FORMAT_WEB, -}; - -static int show_all = 0; -static enum help_format help_format = HELP_FORMAT_MAN; -static struct option builtin_help_options[] = { - OPT_BOOLEAN('a', "all", &show_all, "print all available commands"), - OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN), - OPT_SET_INT('w', "web", &help_format, "show manual in web browser", - HELP_FORMAT_WEB), - OPT_SET_INT('i', "info", &help_format, "show info page", - HELP_FORMAT_INFO), - OPT_END(), -}; - -static const char * const builtin_help_usage[] = { - "git help [--all] [--man|--web|--info] [command]", - NULL -}; - -static enum help_format parse_help_format(const char *format) -{ - if (!strcmp(format, "man")) - return HELP_FORMAT_MAN; - if (!strcmp(format, "info")) - return HELP_FORMAT_INFO; - if (!strcmp(format, "web") || !strcmp(format, "html")) - return HELP_FORMAT_WEB; - die("unrecognized help format '%s'", format); -} - -static const char *get_man_viewer_info(const char *name) -{ - struct man_viewer_info_list *viewer; - - for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) - { - if (!strcasecmp(name, viewer->name)) - return viewer->info; - } - return NULL; -} - -static int check_emacsclient_version(void) -{ - struct strbuf buffer = STRBUF_INIT; - struct child_process ec_process; - const char *argv_ec[] = { "emacsclient", "--version", NULL }; - int version; - - /* emacsclient prints its version number on stderr */ - memset(&ec_process, 0, sizeof(ec_process)); - ec_process.argv = argv_ec; - ec_process.err = -1; - ec_process.stdout_to_stderr = 1; - if (start_command(&ec_process)) { - fprintf(stderr, "Failed to start emacsclient.\n"); - return -1; - } - strbuf_read(&buffer, ec_process.err, 20); - close(ec_process.err); - - /* - * Don't bother checking return value, because "emacsclient --version" - * seems to always exits with code 1. - */ - finish_command(&ec_process); - - if (prefixcmp(buffer.buf, "emacsclient")) { - fprintf(stderr, "Failed to parse emacsclient version.\n"); - strbuf_release(&buffer); - return -1; - } - - strbuf_remove(&buffer, 0, strlen("emacsclient")); - version = atoi(buffer.buf); - - if (version < 22) { - fprintf(stderr, - "emacsclient version '%d' too old (< 22).\n", - version); - strbuf_release(&buffer); - return -1; - } - - strbuf_release(&buffer); - return 0; -} - -static void exec_woman_emacs(const char* path, const char *page) -{ - if (!check_emacsclient_version()) { - /* This works only with emacsclient version >= 22. */ - struct strbuf man_page = STRBUF_INIT; - - if (!path) - path = "emacsclient"; - strbuf_addf(&man_page, "(woman \"%s\")", page); - execlp(path, "emacsclient", "-e", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_konqueror(const char* path, const char *page) -{ - const char *display = getenv("DISPLAY"); - if (display && *display) { - struct strbuf man_page = STRBUF_INIT; - const char *filename = "kfmclient"; - - /* It's simpler to launch konqueror using kfmclient. */ - if (path) { - const char *file = strrchr(path, '/'); - if (file && !strcmp(file + 1, "konqueror")) { - char *new = xstrdup(path); - char *dest = strrchr(new, '/'); - - /* strlen("konqueror") == strlen("kfmclient") */ - strcpy(dest + 1, "kfmclient"); - path = new; - } - if (file) - filename = file; - } else - path = "kfmclient"; - strbuf_addf(&man_page, "man:%s(1)", page); - execlp(path, filename, "newTab", man_page.buf, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); - } -} - -static void exec_man_man(const char* path, const char *page) -{ - if (!path) - path = "man"; - execlp(path, "man", page, NULL); - warning("failed to exec '%s': %s", path, strerror(errno)); -} - -static void exec_man_cmd(const char *cmd, const char *page) -{ - struct strbuf shell_cmd = STRBUF_INIT; - strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL); - warning("failed to exec '%s': %s", cmd, strerror(errno)); -} - -static void add_man_viewer(const char *name) -{ - struct man_viewer_list **p = &man_viewer_list; - size_t len = strlen(name); - - while (*p) - p = &((*p)->next); - *p = xcalloc(1, (sizeof(**p) + len + 1)); - strncpy((*p)->name, name, len); -} - -static int supported_man_viewer(const char *name, size_t len) -{ - return (!strncasecmp("man", name, len) || - !strncasecmp("woman", name, len) || - !strncasecmp("konqueror", name, len)); -} - -static void do_add_man_viewer_info(const char *name, - size_t len, - const char *value) -{ - struct man_viewer_info_list *new = xcalloc(1, sizeof(*new) + len + 1); - - strncpy(new->name, name, len); - new->info = xstrdup(value); - new->next = man_viewer_info_list; - man_viewer_info_list = new; -} - -static int add_man_viewer_path(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - do_add_man_viewer_info(name, len, value); - else - warning("'%s': path for unsupported man viewer.\n" - "Please consider using 'man..cmd' instead.", - name); - - return 0; -} - -static int add_man_viewer_cmd(const char *name, - size_t len, - const char *value) -{ - if (supported_man_viewer(name, len)) - warning("'%s': cmd for supported man viewer.\n" - "Please consider using 'man..path' instead.", - name); - else - do_add_man_viewer_info(name, len, value); - - return 0; -} - -static int add_man_viewer_info(const char *var, const char *value) -{ - const char *name = var + 4; - const char *subkey = strrchr(name, '.'); - - if (!subkey) - return error("Config with no key for man viewer: %s", name); - - if (!strcmp(subkey, ".path")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_path(name, subkey - name, value); - } - if (!strcmp(subkey, ".cmd")) { - if (!value) - return config_error_nonbool(var); - return add_man_viewer_cmd(name, subkey - name, value); - } - - warning("'%s': unsupported man viewer sub key.", subkey); - return 0; -} - -static int git_help_config(const char *var, const char *value, void *cb) -{ - if (!strcmp(var, "help.format")) { - if (!value) - return config_error_nonbool(var); - help_format = parse_help_format(value); - return 0; - } - if (!strcmp(var, "man.viewer")) { - if (!value) - return config_error_nonbool(var); - add_man_viewer(value); - return 0; - } - if (!prefixcmp(var, "man.")) - return add_man_viewer_info(var, value); - - return git_default_config(var, value, cb); -} - /* most GUI terminals set COLUMNS (although some don't export it) */ static int term_columns(void) { @@ -295,14 +25,6 @@ static int term_columns(void) return 80; } -static inline void mput_char(char c, unsigned int num) -{ - while(num--) - putchar(c); -} - -struct cmdnames main_cmds, other_cmds; - void add_cmdname(struct cmdnames *cmds, const char *name, int len) { struct cmdname *ent = xmalloc(sizeof(*ent) + len + 1); @@ -526,23 +248,6 @@ void list_commands(const char *title, unsigned int longest, } } -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)); - puts(common_cmds[i].help); - } -} - int is_in_cmdlist(struct cmdnames *c, const char *s) { int i; @@ -552,128 +257,6 @@ int is_in_cmdlist(struct cmdnames *c, const char *s) return 0; } -static int is_git_command(const char *s) -{ - return is_in_cmdlist(&main_cmds, s) || - is_in_cmdlist(&other_cmds, s); -} - -static const char *prepend(const char *prefix, const char *cmd) -{ - size_t pre_len = strlen(prefix); - size_t cmd_len = strlen(cmd); - char *p = xmalloc(pre_len + cmd_len + 1); - memcpy(p, prefix, pre_len); - strcpy(p + pre_len, cmd); - return p; -} - -static const char *cmd_to_page(const char *git_cmd) -{ - if (!git_cmd) - return "git"; - else if (!prefixcmp(git_cmd, "git")) - return git_cmd; - else if (is_git_command(git_cmd)) - return prepend("git-", git_cmd); - else - return prepend("git", git_cmd); -} - -static void setup_man_path(void) -{ - struct strbuf new_path; - const char *old_path = getenv("MANPATH"); - - strbuf_init(&new_path, 0); - - /* We should always put ':' after our path. If there is no - * old_path, the ':' at the end will let 'man' to try - * system-wide paths after ours to find the manual page. If - * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, GIT_MAN_PATH); - strbuf_addch(&new_path, ':'); - if (old_path) - strbuf_addstr(&new_path, old_path); - - setenv("MANPATH", new_path.buf, 1); - - strbuf_release(&new_path); -} - -static void exec_viewer(const char *name, const char *page) -{ - const char *info = get_man_viewer_info(name); - - if (!strcasecmp(name, "man")) - exec_man_man(info, page); - else if (!strcasecmp(name, "woman")) - exec_woman_emacs(info, page); - else if (!strcasecmp(name, "konqueror")) - exec_man_konqueror(info, page); - else if (info) - exec_man_cmd(info, page); - else - warning("'%s': unknown man viewer.", name); -} - -static void show_man_page(const char *git_cmd) -{ - struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); - - setup_man_path(); - for (viewer = man_viewer_list; viewer; viewer = viewer->next) - { - exec_viewer(viewer->name, page); /* will return when unable */ - } - exec_viewer("man", page); - die("no man viewer handled the request"); -} - -static void show_info_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - setenv("INFOPATH", GIT_INFO_PATH, 1); - execlp("info", "info", "gitman", page, NULL); -} - -static void get_html_page_path(struct strbuf *page_path, const char *page) -{ - struct stat st; - const char *html_path = system_path(GIT_HTML_PATH); - - /* Check that we have a git documentation directory. */ - if (stat(mkpath("%s/git.html", html_path), &st) - || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); - - strbuf_init(page_path, 0); - strbuf_addf(page_path, "%s/%s.html", html_path, page); -} - -/* - * If open_html is not defined in a platform-specific way (see for - * example compat/mingw.h), we use the script web--browse to display - * HTML. - */ -#ifndef open_html -void open_html(const char *path) -{ - execl_git_cmd("web--browse", "-c", "help.browser", path, NULL); -} -#endif - -static void show_html_page(const char *git_cmd) -{ - const char *page = cmd_to_page(git_cmd); - struct strbuf page_path; /* it leaks but we exec bellow */ - - get_html_page_path(&page_path, page); - - open_html(page_path.buf); -} - void help_unknown_cmd(const char *cmd) { fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); @@ -685,50 +268,3 @@ int cmd_version(int argc, const char **argv, const char *prefix) printf("git version %s\n", git_version_string); return 0; } - -int cmd_help(int argc, const char **argv, const char *prefix) -{ - int nongit; - const char *alias; - unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); - - setup_git_directory_gently(&nongit); - git_config(git_help_config, NULL); - - argc = parse_options(argc, argv, builtin_help_options, - builtin_help_usage, 0); - - if (show_all) { - printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", longest, &main_cmds, &other_cmds); - printf("%s\n", git_more_info_string); - return 0; - } - - if (!argv[0]) { - printf("usage: %s\n\n", git_usage_string); - list_common_cmds_help(); - printf("\n%s\n", git_more_info_string); - return 0; - } - - alias = alias_lookup(argv[0]); - if (alias && !is_git_command(argv[0])) { - printf("`git %s' is aliased to `%s'\n", argv[0], alias); - return 0; - } - - switch (help_format) { - case HELP_FORMAT_MAN: - show_man_page(argv[0]); - break; - case HELP_FORMAT_INFO: - show_info_page(argv[0]); - break; - case HELP_FORMAT_WEB: - show_html_page(argv[0]); - break; - } - - return 0; -} diff --git a/help.h b/help.h index d614e5491b..3f1ae89dd6 100644 --- a/help.h +++ b/help.h @@ -10,6 +10,12 @@ struct cmdnames { } **names; }; +static inline void mput_char(char c, unsigned int num) +{ + while(num--) + putchar(c); +} + unsigned int load_command_list(const char *prefix, struct cmdnames *main_cmds, struct cmdnames *other_cmds); -- cgit v1.2.1 From 43df4f86e035056605ceb757029181d7ddee1e7e Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 08:39:16 +0400 Subject: teach index_fd to work with pipes index_fd can now work with file descriptors that are not normal files but any readable file. If the given file descriptor is a regular file then mmap() is used; for other files, strbuf_read is used. The path parameter, which has been used as hint for filters, can be NULL now to indicate that the file should be hashed literally without any filter. The index_pipe function is removed as redundant. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- cache.h | 1 - hash-object.c | 28 ++++++++++++-------------- sha1_file.c | 64 ++++++++++++++++++++++++++--------------------------------- 3 files changed, 41 insertions(+), 52 deletions(-) diff --git a/cache.h b/cache.h index 2475de9fa8..68ce6e686f 100644 --- a/cache.h +++ b/cache.h @@ -391,7 +391,6 @@ extern int ie_modified(const struct index_state *, struct cache_entry *, struct extern int ce_path_match(const struct cache_entry *ce, const char **pathspec); extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path); -extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object); extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); diff --git a/hash-object.c b/hash-object.c index 46c06a9552..7acfae15d9 100644 --- a/hash-object.c +++ b/hash-object.c @@ -8,15 +8,12 @@ #include "blob.h" #include "quote.h" -static void hash_object(const char *path, enum object_type type, int write_object) +static void hash_fd(int fd, const char *type, int write_object, const char *path) { - int fd; struct stat st; unsigned char sha1[20]; - fd = open(path, O_RDONLY); - if (fd < 0 || - fstat(fd, &st) < 0 || - index_fd(sha1, fd, &st, write_object, type, path)) + if (fstat(fd, &st) < 0 || + index_fd(sha1, fd, &st, write_object, type_from_string(type), path)) die(write_object ? "Unable to add %s to database" : "Unable to hash %s", path); @@ -24,12 +21,13 @@ static void hash_object(const char *path, enum object_type type, int write_objec maybe_flush_or_die(stdout, "hash to stdout"); } -static void hash_stdin(const char *type, int write_object) +static void hash_object(const char *path, const char *type, int write_object) { - unsigned char sha1[20]; - if (index_pipe(sha1, 0, type, write_object)) - die("Unable to add stdin to database"); - printf("%s\n", sha1_to_hex(sha1)); + int fd; + fd = open(path, O_RDONLY); + if (fd < 0) + die("Cannot open %s", path); + hash_fd(fd, type, write_object, path); } static void hash_stdin_paths(const char *type, int write_objects) @@ -45,7 +43,7 @@ static void hash_stdin_paths(const char *type, int write_objects) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - hash_object(buf.buf, type_from_string(type), write_objects); + hash_object(buf.buf, type, write_objects); } strbuf_release(&buf); strbuf_release(&nbuf); @@ -116,13 +114,13 @@ int main(int argc, char **argv) } if (hashstdin) { - hash_stdin(type, write_object); + hash_fd(0, type, write_object, NULL); hashstdin = 0; } if (0 <= prefix_length) arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type_from_string(type), write_object); + hash_object(arg, type, write_object); no_more_flags = 1; } } @@ -131,6 +129,6 @@ int main(int argc, char **argv) hash_stdin_paths(type, write_object); if (hashstdin) - hash_stdin(type, write_object); + hash_fd(0, type, write_object, NULL); return 0; } diff --git a/sha1_file.c b/sha1_file.c index e281c14f01..d453b6664e 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2353,51 +2353,22 @@ int has_sha1_file(const unsigned char *sha1) return has_loose_object(sha1); } -int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) +static int index_mem(unsigned char *sha1, void *buf, size_t size, + int write_object, enum object_type type, const char *path) { - struct strbuf buf; - int ret; - - strbuf_init(&buf, 0); - if (strbuf_read(&buf, fd, 4096) < 0) { - strbuf_release(&buf); - return -1; - } - - if (!type) - type = blob_type; - if (write_object) - ret = write_sha1_file(buf.buf, buf.len, type, sha1); - else - ret = hash_sha1_file(buf.buf, buf.len, type, sha1); - strbuf_release(&buf); - - return ret; -} - -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, - enum object_type type, const char *path) -{ - size_t size = xsize_t(st->st_size); - void *buf = NULL; int ret, re_allocated = 0; - if (size) - buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (!type) type = OBJ_BLOB; /* * Convert blobs to git internal format */ - if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) { + if ((type == OBJ_BLOB) && path) { struct strbuf nbuf; strbuf_init(&nbuf, 0); if (convert_to_git(path, buf, size, &nbuf, write_object ? safe_crlf : 0)) { - munmap(buf, size); buf = strbuf_detach(&nbuf, &size); re_allocated = 1; } @@ -2407,12 +2378,33 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, ret = write_sha1_file(buf, size, typename(type), sha1); else ret = hash_sha1_file(buf, size, typename(type), sha1); - if (re_allocated) { + if (re_allocated) free(buf); - return ret; - } - if (size) + return ret; +} + +int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, + enum object_type type, const char *path) +{ + int ret; + size_t size = xsize_t(st->st_size); + + if (!S_ISREG(st->st_mode)) { + struct strbuf sbuf; + strbuf_init(&sbuf, 0); + if (strbuf_read(&sbuf, fd, 4096) >= 0) + ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object, + type, path); + else + ret = -1; + strbuf_release(&sbuf); + } else if (size) { + void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); + ret = index_mem(sha1, buf, size, write_object, type, path); munmap(buf, size); + } else + ret = index_mem(sha1, NULL, size, write_object, type, path); + close(fd); return ret; } -- cgit v1.2.1 From 81014073f22736e9dcb9370475af44e67234622f Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 18:36:18 +0400 Subject: correct argument checking test for git hash-object Because the file name given to stdin did not exist, git hash-object fails to open it and exits with non-zero error code. Thus the test may pass even if there is an error in argument checking. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- t/t1007-hash-object.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 1ec0535138..6d505fafeb 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -49,16 +49,16 @@ setup_repo # Argument checking test_expect_success "multiple '--stdin's are rejected" ' - test_must_fail git hash-object --stdin --stdin < example + echo example | test_must_fail git hash-object --stdin --stdin ' test_expect_success "Can't use --stdin and --stdin-paths together" ' - test_must_fail git hash-object --stdin --stdin-paths && - test_must_fail git hash-object --stdin-paths --stdin + echo example | test_must_fail git hash-object --stdin --stdin-paths && + echo example | test_must_fail git hash-object --stdin-paths --stdin ' test_expect_success "Can't pass filenames as arguments with --stdin-paths" ' - test_must_fail git hash-object --stdin-paths hello < example + echo example | test_must_fail git hash-object --stdin-paths hello ' # Behavior -- cgit v1.2.1 From 9ae8e008abf2e05dee59142fae068ae1f9004147 Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 18:36:19 +0400 Subject: correct usage help string for git-hash-object The usage string is corrected to make it fit in 80 columns and to make it unequivocal about what options can be used with --stdin-paths. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- Documentation/git-hash-object.txt | 4 +++- hash-object.c | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index ac928e198e..a4703ec0de 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -8,7 +8,9 @@ git-hash-object - Compute object ID and optionally creates a blob from a file SYNOPSIS -------- -'git hash-object' [-t ] [-w] [--stdin | --stdin-paths] [--] ... +[verse] +'git hash-object' [-t ] [-w] [--stdin] [--] ... +'git hash-object' [-t ] [-w] --stdin-paths < DESCRIPTION ----------- diff --git a/hash-object.c b/hash-object.c index 7acfae15d9..bb7c5dc6ee 100644 --- a/hash-object.c +++ b/hash-object.c @@ -50,7 +50,8 @@ static void hash_stdin_paths(const char *type, int write_objects) } static const char hash_object_usage[] = -"git hash-object [ [-t ] [-w] [--stdin] ... | --stdin-paths < ]"; +"git hash-object [-t ] [-w] [--stdin] [--] ...\n" +" or: git hash-object --stdin-paths < "; int main(int argc, char **argv) { -- cgit v1.2.1 From 548601adcc638d96486e0f2a3dd399d4ca215eca Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 18:36:20 +0400 Subject: use parse_options() in git hash-object Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- hash-object.c | 122 +++++++++++++++++++++++++--------------------------------- 1 file changed, 53 insertions(+), 69 deletions(-) diff --git a/hash-object.c b/hash-object.c index bb7c5dc6ee..489e8369d8 100644 --- a/hash-object.c +++ b/hash-object.c @@ -7,6 +7,7 @@ #include "cache.h" #include "blob.h" #include "quote.h" +#include "parse-options.h" static void hash_fd(int fd, const char *type, int write_object, const char *path) { @@ -49,87 +50,70 @@ static void hash_stdin_paths(const char *type, int write_objects) strbuf_release(&nbuf); } -static const char hash_object_usage[] = -"git hash-object [-t ] [-w] [--stdin] [--] ...\n" -" or: git hash-object --stdin-paths < "; +static const char * const hash_object_usage[] = { + "git hash-object [-t ] [-w] [--stdin] [--] ...", + "git hash-object --stdin-paths < ", + NULL +}; -int main(int argc, char **argv) +static const char *type; +static int write_object; +static int hashstdin; +static int stdin_paths; + +static const struct option hash_object_options[] = { + OPT_STRING('t', NULL, &type, "type", "object type"), + OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), + OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), + OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), + OPT_END() +}; + +int main(int argc, const char **argv) { int i; - const char *type = blob_type; - int write_object = 0; const char *prefix = NULL; int prefix_length = -1; - int no_more_flags = 0; - int hashstdin = 0; - int stdin_paths = 0; + const char *errstr = NULL; + + type = blob_type; git_config(git_default_config, NULL); - for (i = 1 ; i < argc; i++) { - if (!no_more_flags && argv[i][0] == '-') { - if (!strcmp(argv[i], "-t")) { - if (argc <= ++i) - usage(hash_object_usage); - type = argv[i]; - } - else if (!strcmp(argv[i], "-w")) { - if (prefix_length < 0) { - prefix = setup_git_directory(); - prefix_length = - prefix ? strlen(prefix) : 0; - } - write_object = 1; - } - else if (!strcmp(argv[i], "--")) { - no_more_flags = 1; - } - else if (!strcmp(argv[i], "--help")) - usage(hash_object_usage); - else if (!strcmp(argv[i], "--stdin-paths")) { - if (hashstdin) { - error("Can't use --stdin-paths with --stdin"); - usage(hash_object_usage); - } - stdin_paths = 1; - - } - else if (!strcmp(argv[i], "--stdin")) { - if (stdin_paths) { - error("Can't use %s with --stdin-paths", argv[i]); - usage(hash_object_usage); - } - if (hashstdin) - die("Multiple --stdin arguments are not supported"); - hashstdin = 1; - } - else - usage(hash_object_usage); - } - else { - const char *arg = argv[i]; - - if (stdin_paths) { - error("Can't specify files (such as \"%s\") with --stdin-paths", arg); - usage(hash_object_usage); - } - - if (hashstdin) { - hash_fd(0, type, write_object, NULL); - hashstdin = 0; - } - if (0 <= prefix_length) - arg = prefix_filename(prefix, prefix_length, - arg); - hash_object(arg, type, write_object); - no_more_flags = 1; - } + argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0); + + if (write_object) { + prefix = setup_git_directory(); + prefix_length = prefix ? strlen(prefix) : 0; } - if (stdin_paths) - hash_stdin_paths(type, write_object); + if (stdin_paths) { + if (hashstdin) + errstr = "Can't use --stdin-paths with --stdin"; + else if (argc) + errstr = "Can't specify files with --stdin-paths"; + } + else if (hashstdin > 1) + errstr = "Multiple --stdin arguments are not supported"; + + if (errstr) { + error (errstr); + usage_with_options(hash_object_usage, hash_object_options); + } if (hashstdin) hash_fd(0, type, write_object, NULL); + + for (i = 0 ; i < argc; i++) { + const char *arg = argv[i]; + + if (0 <= prefix_length) + arg = prefix_filename(prefix, prefix_length, arg); + hash_object(arg, type, write_object); + } + + if (stdin_paths) + hash_stdin_paths(type, write_object); + return 0; } -- cgit v1.2.1 From 39702431500b76425f047209c9e9b2aae7e92b00 Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 18:36:21 +0400 Subject: add --path option to git hash-object The --path option allows us to pretend as if the contents being hashed came from the specified path, and affects which input filter is used via the attributes mechanism. This is useful for hashing a temporary file whose name is different from the path that is meant to have the hashed contents. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- Documentation/git-hash-object.txt | 12 +++++++++++- hash-object.c | 19 +++++++++++++------ t/t1007-hash-object.sh | 24 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index a4703ec0de..fececbf753 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -9,7 +9,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file SYNOPSIS -------- [verse] -'git hash-object' [-t ] [-w] [--stdin] [--] ... +'git hash-object' [-t ] [-w] [--path=] [--stdin] [--] ... 'git hash-object' [-t ] [-w] --stdin-paths < DESCRIPTION @@ -37,6 +37,16 @@ OPTIONS --stdin-paths:: Read file names from stdin instead of from the command-line. +--path:: + Hash object as it were located at the given path. The location of + file does not directly influence on the hash value, but path is + used to determine what git filters should be applied to the object + before it can be placed to the object database, and, as result of + applying filters, the actual blob put into the object database may + differ from the given file. This option is mainly useful for hashing + temporary files located outside of the working directory or files + read from stdin. + Author ------ Written by Junio C Hamano diff --git a/hash-object.c b/hash-object.c index 489e8369d8..ce04d150a4 100644 --- a/hash-object.c +++ b/hash-object.c @@ -22,13 +22,14 @@ static void hash_fd(int fd, const char *type, int write_object, const char *path maybe_flush_or_die(stdout, "hash to stdout"); } -static void hash_object(const char *path, const char *type, int write_object) +static void hash_object(const char *path, const char *type, int write_object, + const char *vpath) { int fd; fd = open(path, O_RDONLY); if (fd < 0) die("Cannot open %s", path); - hash_fd(fd, type, write_object, path); + hash_fd(fd, type, write_object, vpath); } static void hash_stdin_paths(const char *type, int write_objects) @@ -44,14 +45,14 @@ static void hash_stdin_paths(const char *type, int write_objects) die("line is badly quoted"); strbuf_swap(&buf, &nbuf); } - hash_object(buf.buf, type, write_objects); + hash_object(buf.buf, type, write_objects, buf.buf); } strbuf_release(&buf); strbuf_release(&nbuf); } static const char * const hash_object_usage[] = { - "git hash-object [-t ] [-w] [--stdin] [--] ...", + "git hash-object [-t ] [-w] [--path=] [--stdin] [--] ...", "git hash-object --stdin-paths < ", NULL }; @@ -60,12 +61,14 @@ static const char *type; static int write_object; static int hashstdin; static int stdin_paths; +static const char *vpath; static const struct option hash_object_options[] = { OPT_STRING('t', NULL, &type, "type", "object type"), OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), + OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"), OPT_END() }; @@ -85,6 +88,8 @@ int main(int argc, const char **argv) if (write_object) { prefix = setup_git_directory(); prefix_length = prefix ? strlen(prefix) : 0; + if (vpath && prefix) + vpath = prefix_filename(prefix, prefix_length, vpath); } if (stdin_paths) { @@ -92,6 +97,8 @@ int main(int argc, const char **argv) errstr = "Can't use --stdin-paths with --stdin"; else if (argc) errstr = "Can't specify files with --stdin-paths"; + else if (vpath) + errstr = "Can't use --stdin-paths with --path"; } else if (hashstdin > 1) errstr = "Multiple --stdin arguments are not supported"; @@ -102,14 +109,14 @@ int main(int argc, const char **argv) } if (hashstdin) - hash_fd(0, type, write_object, NULL); + hash_fd(0, type, write_object, vpath); for (i = 0 ; i < argc; i++) { const char *arg = argv[i]; if (0 <= prefix_length) arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type, write_object); + hash_object(arg, type, write_object, vpath ? vpath : arg); } if (stdin_paths) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 6d505fafeb..f3972a79af 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -61,6 +61,10 @@ test_expect_success "Can't pass filenames as arguments with --stdin-paths" ' echo example | test_must_fail git hash-object --stdin-paths hello ' +test_expect_success "Can't use --path with --stdin-paths" ' + echo example | test_must_fail git hash-object --stdin-paths --path=foo +' + # Behavior push_repo @@ -93,6 +97,26 @@ test_expect_success 'git hash-object --stdin file1 file0 && + cp file0 file1 && + echo "file0 -crlf" >.gitattributes && + echo "file1 crlf" >>.gitattributes && + git config core.autocrlf true && + file0_sha=$(git hash-object file0) && + file1_sha=$(git hash-object file1) && + test "$file0_sha" != "$file1_sha" && + path1_sha=$(git hash-object --path=file1 file0) && + path0_sha=$(git hash-object --path=file0 file1) && + test "$file0_sha" = "$path0_sha" && + test "$file1_sha" = "$path1_sha" && + path1_sha=$(cat file0 | git hash-object --path=file1 --stdin) && + path0_sha=$(cat file1 | git hash-object --path=file0 --stdin) && + test "$file0_sha" = "$path0_sha" && + test "$file1_sha" = "$path1_sha" && + git config --unset core.autocrlf +' + pop_repo for args in "-w --stdin" "--stdin -w"; do -- cgit v1.2.1 From 4a3d85dcf6722b7e5d16b5b1f69b51e39ad5f1dc Mon Sep 17 00:00:00 2001 From: Dmitry Potapov Date: Sun, 3 Aug 2008 18:36:22 +0400 Subject: add --no-filters option to git hash-object The new option allows the contents to be hashed as is, ignoring any input filter that would have been chosen by the attributes mechanism. This option is incompatible with --path and --stdin-paths options. Signed-off-by: Dmitry Potapov Signed-off-by: Junio C Hamano --- Documentation/git-hash-object.txt | 8 +++++++- hash-object.c | 17 +++++++++++++---- t/t1007-hash-object.sh | 24 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/Documentation/git-hash-object.txt b/Documentation/git-hash-object.txt index fececbf753..0af40cfb85 100644 --- a/Documentation/git-hash-object.txt +++ b/Documentation/git-hash-object.txt @@ -9,7 +9,7 @@ git-hash-object - Compute object ID and optionally creates a blob from a file SYNOPSIS -------- [verse] -'git hash-object' [-t ] [-w] [--path=] [--stdin] [--] ... +'git hash-object' [-t ] [-w] [--path=|--no-filters] [--stdin] [--] ... 'git hash-object' [-t ] [-w] --stdin-paths < DESCRIPTION @@ -47,6 +47,12 @@ OPTIONS temporary files located outside of the working directory or files read from stdin. +--no-filters:: + Hash the contents as is, ignoring any input filter that would + have been chosen by the attributes mechanism, including crlf + conversion. If the file is read from standard input then this + is always implied, unless the --path option is given. + Author ------ Written by Junio C Hamano diff --git a/hash-object.c b/hash-object.c index ce04d150a4..a4d127cf78 100644 --- a/hash-object.c +++ b/hash-object.c @@ -52,7 +52,7 @@ static void hash_stdin_paths(const char *type, int write_objects) } static const char * const hash_object_usage[] = { - "git hash-object [-t ] [-w] [--path=] [--stdin] [--] ...", + "git hash-object [-t ] [-w] [--path=|--no-filters] [--stdin] [--] ...", "git hash-object --stdin-paths < ", NULL }; @@ -61,6 +61,7 @@ static const char *type; static int write_object; static int hashstdin; static int stdin_paths; +static int no_filters; static const char *vpath; static const struct option hash_object_options[] = { @@ -68,6 +69,7 @@ static const struct option hash_object_options[] = { OPT_BOOLEAN('w', NULL, &write_object, "write the object into the object database"), OPT_BOOLEAN( 0 , "stdin", &hashstdin, "read the object from stdin"), OPT_BOOLEAN( 0 , "stdin-paths", &stdin_paths, "read file names from stdin"), + OPT_BOOLEAN( 0 , "no-filters", &no_filters, "store file as is without filters"), OPT_STRING( 0 , "path", &vpath, "file", "process file as it were from this path"), OPT_END() }; @@ -99,9 +101,15 @@ int main(int argc, const char **argv) errstr = "Can't specify files with --stdin-paths"; else if (vpath) errstr = "Can't use --stdin-paths with --path"; + else if (no_filters) + errstr = "Can't use --stdin-paths with --no-filters"; + } + else { + if (hashstdin > 1) + errstr = "Multiple --stdin arguments are not supported"; + if (vpath && no_filters) + errstr = "Can't use --path with --no-filters"; } - else if (hashstdin > 1) - errstr = "Multiple --stdin arguments are not supported"; if (errstr) { error (errstr); @@ -116,7 +124,8 @@ int main(int argc, const char **argv) if (0 <= prefix_length) arg = prefix_filename(prefix, prefix_length, arg); - hash_object(arg, type, write_object, vpath ? vpath : arg); + hash_object(arg, type, write_object, + no_filters ? NULL : vpath ? vpath : arg); } if (stdin_paths) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index f3972a79af..fcdd15a358 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -65,6 +65,14 @@ test_expect_success "Can't use --path with --stdin-paths" ' echo example | test_must_fail git hash-object --stdin-paths --path=foo ' +test_expect_success "Can't use --stdin-paths with --no-filters" ' + echo example | test_must_fail git hash-object --stdin-paths --no-filters +' + +test_expect_success "Can't use --path with --no-filters" ' + test_must_fail git hash-object --no-filters --path=foo +' + # Behavior push_repo @@ -117,6 +125,22 @@ test_expect_success 'check that appropriate filter is invoke when --path is used git config --unset core.autocrlf ' +test_expect_success 'check that --no-filters option works' ' + echo fooQ | tr Q "\\015" >file0 && + cp file0 file1 && + echo "file0 -crlf" >.gitattributes && + echo "file1 crlf" >>.gitattributes && + git config core.autocrlf true && + file0_sha=$(git hash-object file0) && + file1_sha=$(git hash-object file1) && + test "$file0_sha" != "$file1_sha" && + nofilters_file1=$(git hash-object --no-filters file1) && + test "$file0_sha" = "$nofilters_file1" && + nofilters_file1=$(cat file1 | git hash-object --stdin) && + test "$file0_sha" = "$nofilters_file1" && + git config --unset core.autocrlf +' + pop_repo for args in "-w --stdin" "--stdin -w"; do -- cgit v1.2.1 From 65347030590bcc251a9ff2ed96487a0f1b9e9fa8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 3 Aug 2008 17:47:16 -0700 Subject: Topo-sort before --simplify-merges This makes the algorithm more honest about what it is doing. We start from an already limited, topo-sorted list, and postprocess it by simplifying the irrelevant merges away. Signed-off-by: Junio C Hamano --- revision.c | 5 +-- t/t6012-rev-list-simplify.sh | 93 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100755 t/t6012-rev-list-simplify.sh diff --git a/revision.c b/revision.c index 662d66be7e..0aaa4c10b9 100644 --- a/revision.c +++ b/revision.c @@ -1493,6 +1493,8 @@ static void simplify_merges(struct rev_info *revs) struct commit_list *list; struct commit_list *yet_to_do, **tail; + sort_in_topological_order(&revs->commits, revs->lifo); + /* feed the list reversed */ yet_to_do = NULL; for (list = revs->commits; list; list = list->next) @@ -1522,9 +1524,6 @@ static void simplify_merges(struct rev_info *revs) if (commit->util == commit) tail = &commit_list_insert(commit, tail)->next; } - - /* sort topologically at the end */ - sort_in_topological_order(&revs->commits, revs->lifo); } static void set_children(struct rev_info *revs) diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh new file mode 100755 index 0000000000..510bb9679f --- /dev/null +++ b/t/t6012-rev-list-simplify.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='merge simplification' + +. ./test-lib.sh + +note () { + git tag "$1" +} + +_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' +_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" + +unnote () { + git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g" +} + +test_expect_success setup ' + echo "Hi there" >file && + git add file && + test_tick && git commit -m "Initial file" && + note A && + + git branch other-branch && + + echo "Hello" >file && + git add file && + test_tick && git commit -m "Modified file" && + note B && + + git checkout other-branch && + + echo "Hello" >file && + git add file && + test_tick && git commit -m "Modified the file identically" && + note C && + + echo "This is a stupid example" >another-file && + git add another-file && + test_tick && git commit -m "Add another file" && + note D && + + test_tick && git merge -m "merge" master && + note E && + + echo "Yet another" >elif && + git add elif && + test_tick && git commit -m "Irrelevant change" && + note F && + + git checkout master && + echo "Yet another" >elif && + git add elif && + test_tick && git commit -m "Another irrelevant change" && + note G && + + test_tick && git merge -m "merge" other-branch && + note H && + + echo "Final change" >file && + test_tick && git commit -a -m "Final change" && + note I +' + +FMT='tformat:%P %H | %s' + +check_result () { + for c in $1 + do + echo "$c" + done >expect && + shift && + param="$*" && + test_expect_success "log $param" ' + git log --pretty="$FMT" --parents $param | + unnote >actual && + sed -e "s/^.* \([^ ]*\) .*/\1/" >check Date: Mon, 4 Aug 2008 00:51:42 -0700 Subject: update-index: refuse to add working tree items beyond symlinks When "sym" is a symbolic link that is inside the working tree, and it points at a directory "dir" that has "path" in it, "update-index --add sym/path" used to mistakenly add "sym/path" as if "sym" were a normal directory. "git apply", "git diff" and "git merge" have been taught about this issue some time ago, but "update-index" and "add" have been left ignorant for too long. Signed-off-by: Junio C Hamano --- builtin-update-index.c | 5 ++++- t/t0055-beyond-symlinks.sh | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100755 t/t0055-beyond-symlinks.sh diff --git a/builtin-update-index.c b/builtin-update-index.c index 38eb53ccba..434cb8e4a0 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -194,6 +194,10 @@ static int process_path(const char *path) int len; struct stat st; + len = strlen(path); + if (has_symlink_leading_path(len, path)) + return error("'%s' is beyond a symbolic link", path); + /* * First things first: get the stat information, to decide * what to do about the pathname! @@ -201,7 +205,6 @@ static int process_path(const char *path) if (lstat(path, &st) < 0) return process_lstat_error(path, errno); - len = strlen(path); if (S_ISDIR(st.st_mode)) return process_directory(path, len, &st); diff --git a/t/t0055-beyond-symlinks.sh b/t/t0055-beyond-symlinks.sh new file mode 100755 index 0000000000..eb11dd7821 --- /dev/null +++ b/t/t0055-beyond-symlinks.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +test_description='update-index refuses to add beyond symlinks' + +. ./test-lib.sh + +test_expect_success setup ' + >a && + mkdir b && + ln -s b c && + >c/d && + git update-index --add a b/d +' + +test_expect_success 'update-index --add beyond symlinks' ' + test_must_fail git update-index --add c/d && + ! ( git ls-files | grep c/d ) +' + +test_done -- cgit v1.2.1 From 725b06050a083474e240a2436121e0a80bb9f175 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 4 Aug 2008 00:52:37 -0700 Subject: add: refuse to add working tree items beyond symlinks This is the same fix for the issue of adding "sym/path" when "sym" is a symblic link that points at a directory "dir" with "path" in it. Signed-off-by: Junio C Hamano --- builtin-add.c | 12 +++++++++++- dir.c | 6 +++++- t/t0055-beyond-symlinks.sh | 7 ++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/builtin-add.c b/builtin-add.c index fc3f96eaef..81b64d7b9d 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -153,6 +153,16 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p { const char **pathspec = get_pathspec(prefix, argv); + if (pathspec) { + const char **p; + for (p = pathspec; *p; p++) { + if (has_symlink_leading_path(strlen(*p), *p)) { + int len = prefix ? strlen(prefix) : 0; + die("'%s' is beyond a symbolic link", *p + len); + } + } + } + return pathspec; } @@ -278,7 +288,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) fprintf(stderr, "Maybe you wanted to say 'git add .'?\n"); return 0; } - pathspec = get_pathspec(prefix, argv); + pathspec = validate_pathspec(argc, argv, prefix); /* * If we are adding new files, we need to scan the working diff --git a/dir.c b/dir.c index 29d1d5ba31..ae7046fd03 100644 --- a/dir.c +++ b/dir.c @@ -727,8 +727,12 @@ static void free_simplify(struct path_simplify *simplify) int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec) { - struct path_simplify *simplify = create_simplify(pathspec); + struct path_simplify *simplify; + if (has_symlink_leading_path(strlen(path), path)) + return dir->nr; + + simplify = create_simplify(pathspec); read_directory_recursive(dir, path, base, baselen, 0, simplify); free_simplify(simplify); qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name); diff --git a/t/t0055-beyond-symlinks.sh b/t/t0055-beyond-symlinks.sh index eb11dd7821..b29c37a5a4 100755 --- a/t/t0055-beyond-symlinks.sh +++ b/t/t0055-beyond-symlinks.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='update-index refuses to add beyond symlinks' +test_description='update-index and add refuse to add beyond symlinks' . ./test-lib.sh @@ -17,4 +17,9 @@ test_expect_success 'update-index --add beyond symlinks' ' ! ( git ls-files | grep c/d ) ' +test_expect_success 'add beyond symlinks' ' + test_must_fail git add c/d && + ! ( git ls-files | grep c/d ) +' + test_done -- cgit v1.2.1 From ff30fff38c09dac7c1349fb774c55daa8fd92972 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Aug 2008 07:59:13 +0200 Subject: t9700: remove useless check t9700 used to check if the basename of the current directory is 'trash directory', the expensive way. However, there is absolutely no good reason why this test should not run in, say 'life is good' or 'i love tests'. So remove the check altogether. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/t9700/test.pl | 3 --- 1 file changed, 3 deletions(-) diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 4d2312548a..851cea4a4b 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -14,10 +14,7 @@ use File::Temp; BEGIN { use_ok('Git') } # set up -our $repo_dir = "trash directory"; our $abs_repo_dir = Cwd->cwd; -die "this must be run by calling the t/t97* shell script(s)\n" - if basename(Cwd->cwd) ne $repo_dir; ok(our $r = Git->repository(Directory => "."), "open repository"); # config -- cgit v1.2.1 From e3df89a4b167c2ca0bd2b0a0025fd16c4d3dd651 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Aug 2008 07:59:18 +0200 Subject: tests: Clarify dependencies between tests, 'aggregate-results' and 'clean' The Makefile targets 'aggregate-results' and 'clean' pretended to be independent. This is not true, of course, since aggregate-results needs the results _before_ they are removed. Likewise, the tests should have been run already when the results are to be aggregated. However, as it is legitimate to run only a few tests, and then aggregate just those results, so another target is introduced, that depends on all tests, then aggregates the results, and only then removes the results. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/t/Makefile b/t/Makefile index 0d65cedaa6..aa952e1c5c 100644 --- a/t/Makefile +++ b/t/Makefile @@ -14,7 +14,8 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) TSVN = $(wildcard t91[0-9][0-9]-*.sh) -all: pre-clean $(T) aggregate-results clean +all: pre-clean + $(MAKE) aggregate-results-and-cleanup $(T): @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) @@ -25,6 +26,10 @@ pre-clean: clean: $(RM) -r 'trash directory' test-results +aggregate-results-and-cleanup: $(T) + $(MAKE) aggregate-results + $(MAKE) clean + aggregate-results: '$(SHELL_PATH_SQ)' ./aggregate-results.sh test-results/t*-* -- cgit v1.2.1 From abc5d372ec242fc654dc6780df6ea3d63dc72f2f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 8 Aug 2008 13:08:37 +0200 Subject: Enable parallel tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On multiprocessor machines, or with I/O heavy tests (that leave the CPU waiting a lot), it makes sense to parallelize the tests. However, care has to be taken that the different jobs use different trash directories. This commit does so, by creating the trash directories with a suffix that is unique with regard to the test, as it is the test's base name. Further, the trash directory is removed in the test itself if everything went fine, so that the trash directories do not pile up only to be removed at the very end. If a test failed, the trash directory is not removed. Chances are that the exact error message is lost in the clutter, but you can still see what test failed from the name of the trash directory, and repeat the test (without -j). If all was good, you will see the aggregated results. Suggestions to simplify this commit came from Junio and René. There still is an issue with tests that want to run a server process and listen to a fixed port (http and svn) --- they cannot run in parallel but this patch does not address this issue. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/Makefile | 1 - t/test-lib.sh | 8 +++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/t/Makefile b/t/Makefile index aa952e1c5c..ed49c20b16 100644 --- a/t/Makefile +++ b/t/Makefile @@ -39,4 +39,3 @@ full-svn-test: $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 .PHONY: pre-clean $(T) aggregate-results clean -.NOTPARALLEL: diff --git a/t/test-lib.sh b/t/test-lib.sh index 11c027571b..7f60b614ea 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -449,6 +449,11 @@ test_done () { # we will leave things as they are. say_color pass "passed all $msg" + + test -d "$remove_trash" && + cd "$(dirname "$remove_trash")" && + rm -rf "$(basename "$remove_trash")" + exit 0 ;; *) @@ -485,7 +490,8 @@ fi . ../GIT-BUILD-OPTIONS # Test repository -test="trash directory" +test="trash directory.$(basename "$0" .sh)" +remove_trash="$TEST_DIRECTORY/$test" rm -fr "$test" || { trap - exit echo >&5 "FATAL: Cannot prepare test area" -- cgit v1.2.1 From a57114c81832a70efda4991131b9b99d1b112ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Fri, 8 Aug 2008 22:48:23 +0200 Subject: Refactoring: Split up diff_tree_stdin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Into a first half that determines what operation to do, and a second half that does it. Currently the only operation is diffing one or more commits, but a later patch will add diffing of trees, at which point this refactoring will pay off. Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano --- builtin-diff-tree.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index 415cb1612f..ebbd6317c3 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -14,20 +14,10 @@ static int diff_tree_commit_sha1(const unsigned char *sha1) return log_tree_commit(&log_tree_opt, commit); } -static int diff_tree_stdin(char *line) +/* Diff one or more commits. */ +static int stdin_diff_commit(struct commit *commit, char *line, int len) { - int len = strlen(line); unsigned char sha1[20]; - struct commit *commit; - - if (!len || line[len-1] != '\n') - return -1; - line[len-1] = 0; - if (get_sha1_hex(line, sha1)) - return -1; - commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) - return -1; if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) { /* Graft the fake parents locally to the commit */ int pos = 41; @@ -52,6 +42,23 @@ static int diff_tree_stdin(char *line) return log_tree_commit(&log_tree_opt, commit); } +static int diff_tree_stdin(char *line) +{ + int len = strlen(line); + unsigned char sha1[20]; + struct commit *commit; + + if (!len || line[len-1] != '\n') + return -1; + line[len-1] = 0; + if (get_sha1_hex(line, sha1)) + return -1; + commit = lookup_commit(sha1); + if (!commit || parse_commit(commit)) + return -1; + return stdin_diff_commit(commit, line, len); +} + static const char diff_tree_usage[] = "git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " "[] [] [...]\n" -- cgit v1.2.1 From 7cccfaa2809a09cb321a5f1276c5b91a71594527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Sun, 10 Aug 2008 18:12:53 +0200 Subject: diff-tree: Note that the commit ID is printed with --stdin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's sort of already documented with the --no-commit-id command-line flag, but let's not hide important information from the user. Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano --- Documentation/git-diff-tree.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index 1fdf20dcc9..1f4b91ed45 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -52,10 +52,14 @@ include::diff-options.txt[] reads either one or a list of separated with a single space from its standard input. + -When a single commit is given on one line of such input, it compares -the commit with its parents. The following flags further affects its -behavior. The remaining commits, when given, are used as if they are +When a single commit is given, it compares the commit with its +parents. The remaining commits, when given, are used as if they are parents of the first commit. ++ +The ID of the first (or only) commit, followed by a newline, is +printed before the differences. ++ +The following flags further affects its behavior. -m:: By default, 'git-diff-tree --stdin' does not show -- cgit v1.2.1 From 140b378d07229e3bcef0613e11aa0a04e4db3ecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Sun, 10 Aug 2008 18:12:58 +0200 Subject: Teach git diff-tree --stdin to diff trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When feeding trees on the command line, you can give exactly two trees, not three nor one; --stdin now supports this "two tree" form on its input, in addition to accepting lines with one or more commits. When diffing trees (either specified on the command line or from the standard input), the -s, -v, --pretty, --abbrev-commit, --encoding, --no-commit-id, and --always options are ignored, since they do not apply to trees; and the -m, -c, and --cc options are ignored since they would be meaningful only with three or more trees, which is not supported (yet). Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano --- Documentation/git-diff-tree.txt | 15 ++++++++++----- builtin-diff-tree.c | 33 +++++++++++++++++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Documentation/git-diff-tree.txt b/Documentation/git-diff-tree.txt index 1f4b91ed45..5d48664e62 100644 --- a/Documentation/git-diff-tree.txt +++ b/Documentation/git-diff-tree.txt @@ -49,17 +49,22 @@ include::diff-options.txt[] --stdin:: When '--stdin' is specified, the command does not take arguments from the command line. Instead, it - reads either one or a list of - separated with a single space from its standard input. + reads lines containing either two , one , or a + list of from its standard input. (Use a single space + as separator.) + +When two trees are given, it compares the first tree with the second. When a single commit is given, it compares the commit with its parents. The remaining commits, when given, are used as if they are parents of the first commit. + -The ID of the first (or only) commit, followed by a newline, is -printed before the differences. +When comparing two trees, the ID of both trees (separated by a space +and terminated by a newline) is printed before the difference. When +comparing commits, the ID of the first (or only) commit, followed by a +newline, is printed. + -The following flags further affects its behavior. +The following flags further affects the behavior when comparing +commits (but not trees). -m:: By default, 'git-diff-tree --stdin' does not show diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index ebbd6317c3..1138c2da73 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -42,21 +42,46 @@ static int stdin_diff_commit(struct commit *commit, char *line, int len) return log_tree_commit(&log_tree_opt, commit); } +/* Diff two trees. */ +static int stdin_diff_trees(struct tree *tree1, char *line, int len) +{ + unsigned char sha1[20]; + struct tree *tree2; + if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1)) + return error("Need exactly two trees, separated by a space"); + tree2 = lookup_tree(sha1); + if (!tree2 || parse_tree(tree2)) + return -1; + printf("%s %s\n", sha1_to_hex(tree1->object.sha1), + sha1_to_hex(tree2->object.sha1)); + diff_tree_sha1(tree1->object.sha1, tree2->object.sha1, + "", &log_tree_opt.diffopt); + log_tree_diff_flush(&log_tree_opt); + return 0; +} + static int diff_tree_stdin(char *line) { int len = strlen(line); unsigned char sha1[20]; - struct commit *commit; + struct object *obj; if (!len || line[len-1] != '\n') return -1; line[len-1] = 0; if (get_sha1_hex(line, sha1)) return -1; - commit = lookup_commit(sha1); - if (!commit || parse_commit(commit)) + obj = lookup_object(sha1); + obj = obj ? obj : parse_object(sha1); + if (!obj) return -1; - return stdin_diff_commit(commit, line, len); + if (obj->type == OBJ_COMMIT) + return stdin_diff_commit((struct commit *)obj, line, len); + if (obj->type == OBJ_TREE) + return stdin_diff_trees((struct tree *)obj, line, len); + error("Object %s is a %s, not a commit or tree", + sha1_to_hex(sha1), typename(obj->type)); + return -1; } static const char diff_tree_usage[] = -- cgit v1.2.1 From 5bf707cde14f60b7a066bdab5dbdefaec1a1d0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karl=20Hasselstr=C3=B6m?= Date: Sun, 10 Aug 2008 18:13:04 +0200 Subject: Add test for diff-tree --stdin with two trees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Karl Hasselström Signed-off-by: Junio C Hamano --- t/t4002-diff-basic.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index a4cfde6b29..27743c4c59 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -168,6 +168,20 @@ test_expect_success \ 'git diff-tree -r $tree_A $tree_B >.test-a && cmp -s .test-a .test-recursive-AB' +test_expect_success \ + 'diff-tree --stdin of known trees.' \ + 'echo $tree_A $tree_B | git diff-tree --stdin > .test-a && + echo $tree_A $tree_B > .test-plain-ABx && + cat .test-plain-AB >> .test-plain-ABx && + cmp -s .test-a .test-plain-ABx' + +test_expect_success \ + 'diff-tree --stdin of known trees.' \ + 'echo $tree_A $tree_B | git diff-tree -r --stdin > .test-a && + echo $tree_A $tree_B > .test-recursive-ABx && + cat .test-recursive-AB >> .test-recursive-ABx && + cmp -s .test-a .test-recursive-ABx' + test_expect_success \ 'diff-cache O with A in cache' \ 'git read-tree $tree_A && -- cgit v1.2.1 From bb0ceb6264fa1aea6e68e07cb13cd9a88473febb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 Aug 2008 16:00:12 +0200 Subject: checkout --track: make up a sensible branch name if '-b' was omitted What does the user most likely want with this command? $ git checkout --track origin/next Exactly. A branch called 'next', that tracks origin's branch 'next'. Make it so. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 10 +++++++++- builtin-checkout.c | 21 ++++++++++++++++++--- t/t7201-co.sh | 11 +++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 5aa69c0e12..43d4502547 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,7 +8,7 @@ git-checkout - Checkout a branch or paths to the working tree SYNOPSIS -------- [verse] -'git checkout' [-q] [-f] [[--track | --no-track] -b [-l]] [-m] [] +'git checkout' [-q] [-f] [--track | --no-track] [-b [-l]] [-m] [] 'git checkout' [] [--] ... DESCRIPTION @@ -21,6 +21,10 @@ specified, . Using -b will cause to be created; in this case you can use the --track or --no-track options, which will be passed to `git branch`. +As a convenience, --track will default to create a branch whose +name is constructed from the specified branch name by stripping +the first namespace level. + When are given, this command does *not* switch branches. It updates the named paths in the working tree from the index file (i.e. it runs `git checkout-index -f -u`), or @@ -59,6 +63,10 @@ OPTIONS 'git-checkout' and 'git-branch' to always behave as if '--no-track' were given. Set it to `always` if you want this behavior when the start-point is either a local or remote branch. ++ +If no '-b' option was given, a name will be made up for you, by stripping +the part up to the first slash of the tracked branch. For example, if you +called 'git checkout --track origin/next', the branch name will be 'next'. --no-track:: Ignore the branch.autosetupmerge configuration variable. diff --git a/builtin-checkout.c b/builtin-checkout.c index 411cc513c6..e95eab9b1b 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -437,13 +437,28 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - opts.track = git_branch_track; + opts.track = -1; argc = parse_options(argc, argv, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); - if (!opts.new_branch && (opts.track != git_branch_track)) - die("git checkout: --track and --no-track require -b"); + /* --track without -b should DWIM */ + if (opts.track && opts.track != -1 && !opts.new_branch) { + char *slash; + if (!argc || !strcmp(argv[0], "--")) + die ("--track needs a branch name"); + slash = strchr(argv[0], '/'); + if (slash && !prefixcmp(argv[0], "refs/")) + slash = strchr(slash + 1, '/'); + if (slash && !prefixcmp(argv[0], "remotes/")) + slash = strchr(slash + 1, '/'); + if (!slash || !slash[1]) + die ("Missing branch name; try -b"); + opts.new_branch = slash + 1; + } + + if (opts.track == -1) + opts.track = git_branch_track; if (opts.force && opts.merge) die("git checkout: -f and -m are incompatible"); diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 9ad5d635a2..943dd57aac 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -337,4 +337,15 @@ test_expect_success \ test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && test_must_fail git checkout --track -b track' +test_expect_success \ + 'checkout with --track fakes a sensible -b ' ' + git update-ref refs/remotes/origin/koala/bear renamer && + git checkout --track origin/koala/bear && + test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && + test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"' + +test_expect_success \ + 'checkout with --track, but without -b, fails with too short tracked name' ' + test_must_fail git checkout --track renamer' + test_done -- cgit v1.2.1 From aa1a0111cc0e2a12c21ed05c88d8e9872fc166b2 Mon Sep 17 00:00:00 2001 From: Abhijit Menon-Sen Date: Sun, 10 Aug 2008 17:18:55 +0530 Subject: Make cherry-pick use rerere for conflict resolution. Cherry-picking can be helped by reusing previous confliction resolution by invoking rerere automatically. Signed-off-by: Abhijit Menon-Sen Signed-off-by: Junio C Hamano --- builtin-revert.c | 2 ++ t/t3504-cherry-pick-rerere.sh | 45 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100755 t/t3504-cherry-pick-rerere.sh diff --git a/builtin-revert.c b/builtin-revert.c index 27881e9493..36677053f8 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -11,6 +11,7 @@ #include "cache-tree.h" #include "diff.h" #include "revision.h" +#include "rerere.h" /* * This implements the builtins revert and cherry-pick. @@ -395,6 +396,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) die ("Error wrapping up %s", defmsg); fprintf(stderr, "Automatic %s failed.%s\n", me, help_msg(commit->object.sha1)); + rerere(); exit(1); } if (commit_lock_file(&msg_file) < 0) diff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh new file mode 100755 index 0000000000..f7b3518a32 --- /dev/null +++ b/t/t3504-cherry-pick-rerere.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='cherry-pick should rerere for conflicts' + +. ./test-lib.sh + +test_expect_success setup ' + echo foo >foo && + git add foo && test_tick && git commit -q -m 1 && + echo foo-master >foo && + git add foo && test_tick && git commit -q -m 2 && + + git checkout -b dev HEAD^ && + echo foo-dev >foo && + git add foo && test_tick && git commit -q -m 3 && + git config rerere.enabled true +' + +test_expect_success 'conflicting merge' ' + test_must_fail git merge master +' + +test_expect_success 'fixup' ' + echo foo-dev >foo && + git add foo && test_tick && git commit -q -m 4 && + git reset --hard HEAD^ + echo foo-dev >expect +' + +test_expect_success 'cherry-pick conflict' ' + test_must_fail git cherry-pick master && + test_cmp expect foo +' + +test_expect_success 'reconfigure' ' + git config rerere.enabled false + git reset --hard +' + +test_expect_success 'cherry-pick conflict without rerere' ' + test_must_fail git cherry-pick master && + test_must_fail test_cmp expect foo +' + +test_done -- cgit v1.2.1 From 6d6f9cddbe419710a36e778a50a7712ac4ba016f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 12 Aug 2008 11:31:06 -0700 Subject: pack-objects: Allow missing base objects when creating thin packs If we are building a thin pack and one of the base objects we would consider for deltification is missing its OK, the other side already has that base object. We may be able to get a delta from another object, or we can simply send the new object whole (no delta). This change allows a shallow clone to store only the objects which are unique to it, as well as the boundary commit and its trees, but avoids storing the boundary blobs. This special form of a shallow clone is able to represent just the difference between two trees. Pack objects change suggested by Nicolas Pitre. Signed-off-by: Shawn O. Pearce Acked-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 15 +++++++--- t/t5306-pack-nobase.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) create mode 100755 t/t5306-pack-nobase.sh diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 2dadec1630..b43e0b8f27 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1096,9 +1096,12 @@ static void check_object(struct object_entry *entry) } entry->type = sha1_object_info(entry->idx.sha1, &entry->size); - if (entry->type < 0) - die("unable to get type of object %s", - sha1_to_hex(entry->idx.sha1)); + /* + * The error condition is checked in prepare_pack(). This is + * to permit a missing preferred base object to be ignored + * as a preferred base. Doing so can result in a larger + * pack file, but the transfer will still take place. + */ } static int pack_offset_sort(const void *_a, const void *_b) @@ -1722,8 +1725,12 @@ static void prepare_pack(int window, int depth) if (entry->no_try_delta) continue; - if (!entry->preferred_base) + if (!entry->preferred_base) { nr_deltas++; + if (entry->type < 0) + die("unable to get type of object %s", + sha1_to_hex(entry->idx.sha1)); + } delta_list[n++] = entry; } diff --git a/t/t5306-pack-nobase.sh b/t/t5306-pack-nobase.sh new file mode 100755 index 0000000000..f4931c0c2a --- /dev/null +++ b/t/t5306-pack-nobase.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# +# Copyright (c) 2008 Google Inc. +# + +test_description='git-pack-object with missing base + +' +. ./test-lib.sh + +# Create A-B chain +# +test_expect_success \ + 'setup base' \ + 'for a in a b c d e f g h i; do echo $a >>text; done && + echo side >side && + git update-index --add text side && + A=$(echo A | git commit-tree $(git write-tree)) && + + echo m >>text && + git update-index text && + B=$(echo B | git commit-tree $(git write-tree) -p $A) && + git update-ref HEAD $B + ' + +# Create repository with C whose parent is B. +# Repository contains C, C^{tree}, C:text, B, B^{tree}. +# Repository is missing B:text (best delta base for C:text). +# Repository is missing A (parent of B). +# Repository is missing A:side. +# +test_expect_success \ + 'setup patch_clone' \ + 'base_objects=$(pwd)/.git/objects && + (mkdir patch_clone && + cd patch_clone && + git init && + echo "$base_objects" >.git/objects/info/alternates && + echo q >>text && + git read-tree $B && + git update-index text && + git update-ref HEAD $(echo C | git commit-tree $(git write-tree) -p $B) && + rm .git/objects/info/alternates && + + git --git-dir=../.git cat-file commit $B | + git hash-object -t commit -w --stdin && + + git --git-dir=../.git cat-file tree "$B^{tree}" | + git hash-object -t tree -w --stdin + ) && + C=$(git --git-dir=patch_clone/.git rev-parse HEAD) + ' + +# Clone patch_clone indirectly by cloning base and fetching. +# +test_expect_success \ + 'indirectly clone patch_clone' \ + '(mkdir user_clone && + cd user_clone && + git init && + git pull ../.git && + test $(git rev-parse HEAD) = $B && + + git pull ../patch_clone/.git && + test $(git rev-parse HEAD) = $C + ) + ' + +# Cloning the patch_clone directly should fail. +# +test_expect_success \ + 'clone of patch_clone is incomplete' \ + '(mkdir user_direct && + cd user_direct && + git init && + test_must_fail git fetch ../patch_clone/.git + ) + ' + +test_done -- cgit v1.2.1 From 6e84b712373d343ea3531740d8eb048e84240a39 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Tue, 12 Aug 2008 10:45:57 +0200 Subject: filter-branch: Extend test to show rewriting bug This extends the --subdirectory-filter test in t7003 to demonstrate a rewriting bug: when rewriting two refs A and B such that B is an ancestor of A, it fails to rewrite B. The underlying issue is that the rev-list invocation at git-filter-branch.sh:332 more or less boils down to git rev-list B --boundary ^A which outputs nothing because B is an ancestor of A. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- t/t7003-filter-branch.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index a0ab096c8f..4382baa27d 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -96,13 +96,17 @@ test_expect_success 'filter subdirectory only' ' test_tick && git commit -m "again not subdir" && git branch sub && - git-filter-branch -f --subdirectory-filter subdir refs/heads/sub + git branch sub-earlier HEAD~2 && + git-filter-branch -f --subdirectory-filter subdir \ + refs/heads/sub refs/heads/sub-earlier ' -test_expect_success 'subdirectory filter result looks okay' ' +test_expect_failure 'subdirectory filter result looks okay' ' test 2 = $(git rev-list sub | wc -l) && git show sub:new && - test_must_fail git show sub:subdir + test_must_fail git show sub:subdir && + git show sub-earlier:new && + test_must_fail git show sub-earlier:subdir ' test_expect_success 'more setup' ' -- cgit v1.2.1 From a0e46390d397e71182e42930b98b6b59a1a84898 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Tue, 12 Aug 2008 10:45:58 +0200 Subject: filter-branch: fix ref rewriting with --subdirectory-filter The previous ancestor discovery code failed on any refs that are (pre-rewrite) ancestors of commits marked for rewriting. This means that in a situation A -- B(topic) -- C(master) where B is dropped by --subdirectory-filter pruning, the 'topic' was not moved up to A as intended, but left unrewritten because we asked about 'git rev-list ^master topic', which does not return anything. Instead, we use the straightforward git rev-list -1 $ref -- $filter_subdir to find the right ancestor. To justify this, note that the nearest ancestor is unique: We use the output of git rev-list --parents -- $filter_subdir to rewrite commits in the first pass, before any ref rewriting. If B is a non-merge commit, the only candidate is its parent. If it is a merge, there are two cases: - All sides of the merge bring the same subdirectory contents. Then rev-list already pruned away the merge in favour for just one of its parents, so there is only one candidate. - Some merge sides, or the merge outcome, differ. Then the merge is not pruned and can be rewritten directly. So it is always safe to use rev-list -1. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 27 +++++++++++---------------- t/t7003-filter-branch.sh | 2 +- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a324cf0596..a140337e35 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -317,24 +317,19 @@ done <../revs # In case of a subdirectory filter, it is possible that a specified head # is not in the set of rewritten commits, because it was pruned by the -# revision walker. Fix it by mapping these heads to the next rewritten -# ancestor(s), i.e. the boundaries in the set of rewritten commits. +# revision walker. Fix it by mapping these heads to the unique nearest +# ancestor that survived the pruning. -# NEEDSWORK: we should sort the unmapped refs topologically first -while read ref -do - sha1=$(git rev-parse "$ref"^0) - test -f "$workdir"/../map/$sha1 && continue - # Assign the boundarie(s) in the set of rewritten commits - # as the replacement commit(s). - # (This would look a bit nicer if --not --stdin worked.) - for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") | - git rev-list $ref --boundary --stdin | - sed -n "s/^-//p") +if test "$filter_subdir" +then + while read ref do - map $p >> "$workdir"/../map/$sha1 - done -done < "$tempdir"/heads + sha1=$(git rev-parse "$ref"^0) + test -f "$workdir"/../map/$sha1 && continue + ancestor=$(git rev-list -1 $ref -- "$filter_subdir") + test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1 + done < "$tempdir"/heads +fi # Finally update the refs diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 4382baa27d..233254f2b5 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -101,7 +101,7 @@ test_expect_success 'filter subdirectory only' ' refs/heads/sub refs/heads/sub-earlier ' -test_expect_failure 'subdirectory filter result looks okay' ' +test_expect_success 'subdirectory filter result looks okay' ' test 2 = $(git rev-list sub | wc -l) && git show sub:new && test_must_fail git show sub:subdir && -- cgit v1.2.1 From f34a9416ab9efa13a53a31cabd9061d583aaba7e Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Tue, 12 Aug 2008 10:45:59 +0200 Subject: filter-branch: use --simplify-merges Use rev-list --simplify-merges everywhere. This changes the behaviour of --subdirectory-filter in cases such as O -- A -\ \ \ \- B -- M where A and B bring the same changes to the subdirectory: It now keeps both sides of the merge. Previously, the history would have been simplified to 'O -- A'. Merges of unrelated side histories that never touch the subdirectory are still removed. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a140337e35..2688254af3 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -232,11 +232,11 @@ mkdir ../map || die "Could not create map/ directory" case "$filter_subdir" in "") git rev-list --reverse --topo-order --default HEAD \ - --parents "$@" + --parents --simplify-merges "$@" ;; *) git rev-list --reverse --topo-order --default HEAD \ - --parents "$@" -- "$filter_subdir" + --parents --simplify-merges "$@" -- "$filter_subdir" esac > ../revs || die "Could not get the commits" commits=$(wc -l <../revs | tr -d " ") @@ -326,7 +326,8 @@ then do sha1=$(git rev-parse "$ref"^0) test -f "$workdir"/../map/$sha1 && continue - ancestor=$(git rev-list -1 $ref -- "$filter_subdir") + ancestor=$(git rev-list --simplify-merges -1 \ + $ref -- "$filter_subdir") test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1 done < "$tempdir"/heads fi -- cgit v1.2.1 From 186f8aa9082dda98bcd7b56f78d7f859966cd605 Mon Sep 17 00:00:00 2001 From: Alexandre Bourget Date: Mon, 11 Aug 2008 17:19:16 -0400 Subject: git-gui: Update french translation Signed-off-by: Alexandre Bourget Signed-off-by: Shawn O. Pearce --- po/fr.po | 253 ++++++++++++++++++++++++++++++++------------------------------- 1 file changed, 127 insertions(+), 126 deletions(-) diff --git a/po/fr.po b/po/fr.po index 89b6d51ea0..128cd69ef5 100644 --- a/po/fr.po +++ b/po/fr.po @@ -1,17 +1,18 @@ -# translation of fr.po to French +# translation of fr.po to Français # Translation of git-gui to French. # Copyright (C) 2008 Shawn Pearce, et al. # This file is distributed under the same license as the git package. # # Christian Couder , 2008. +# Alexandre Bourget , 2008. msgid "" msgstr "" "Project-Id-Version: fr\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2008-03-14 07:18+0100\n" -"PO-Revision-Date: 2008-04-04 22:05+0200\n" -"Last-Translator: Christian Couder \n" -"Language-Team: French\n" +"PO-Revision-Date: 2008-08-11 16:28-0400\n" +"Last-Translator: Alexandre Bourget \n" +"Language-Team: Français \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -26,15 +27,15 @@ msgstr "git-gui: erreur fatale" #: git-gui.sh:593 #, tcl-format msgid "Invalid font specified in %s:" -msgstr "Invalide fonte spécifiée dans %s :" +msgstr "Police invalide spécifiée dans %s :" #: git-gui.sh:620 msgid "Main Font" -msgstr "Fonte principale" +msgstr "Police principale" #: git-gui.sh:621 msgid "Diff/Console Font" -msgstr "Fonte diff/console" +msgstr "Police diff/console" #: git-gui.sh:635 msgid "Cannot find git in PATH." @@ -65,7 +66,7 @@ msgstr "" #: git-gui.sh:918 msgid "Git directory not found:" -msgstr "Impossible de trouver le répertoire de Git :" +msgstr "Impossible de trouver le répertoire git :" #: git-gui.sh:925 msgid "Cannot move to top of working directory:" @@ -73,11 +74,11 @@ msgstr "Impossible d'aller à la racine du répertoire de travail :" #: git-gui.sh:932 msgid "Cannot use funny .git directory:" -msgstr "Impossible d'utiliser un drôle de répertoire git :" +msgstr "Impossible d'utiliser le répertoire .git:" #: git-gui.sh:937 msgid "No working directory" -msgstr "Pas de répertoire de travail" +msgstr "Aucun répertoire de travail" #: git-gui.sh:1084 lib/checkout_op.tcl:283 msgid "Refreshing file status..." @@ -97,23 +98,23 @@ msgstr "Non modifié" #: git-gui.sh:1592 msgid "Modified, not staged" -msgstr "Modifié, non pré-commité" +msgstr "Modifié, pas indexé" #: git-gui.sh:1593 git-gui.sh:1598 msgid "Staged for commit" -msgstr "Pré-commité" +msgstr "Indexé" #: git-gui.sh:1594 git-gui.sh:1599 msgid "Portions staged for commit" -msgstr "En partie pré-commité" +msgstr "Portions indexées" #: git-gui.sh:1595 git-gui.sh:1600 msgid "Staged for commit, missing" -msgstr "Pré-commité, manquant" +msgstr "Indexés, manquant" #: git-gui.sh:1597 msgid "Untracked, not staged" -msgstr "Non suivi, non pré-commité" +msgstr "Non versionné, non indexé" #: git-gui.sh:1602 msgid "Missing" @@ -121,11 +122,11 @@ msgstr "Manquant" #: git-gui.sh:1603 msgid "Staged for removal" -msgstr "Pré-commité pour suppression" +msgstr "Indexé pour suppression" #: git-gui.sh:1604 msgid "Staged for removal, still present" -msgstr "Pré-commité pour suppression, toujours présent" +msgstr "Indexé pour suppression, toujours présent" #: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 msgid "Requires merge resolution" @@ -133,7 +134,7 @@ msgstr "Nécessite la résolution d'une fusion" #: git-gui.sh:1644 msgid "Starting gitk... please wait..." -msgstr "Lancement de gitk... merci de patienter..." +msgstr "Lancement de gitk... un instant..." #: git-gui.sh:1653 #, tcl-format @@ -148,11 +149,11 @@ msgstr "" #: git-gui.sh:1860 lib/choose_repository.tcl:36 msgid "Repository" -msgstr "Référentiel" +msgstr "Dépôt" #: git-gui.sh:1861 msgid "Edit" -msgstr "Editer" +msgstr "Edition" #: git-gui.sh:1863 lib/choose_rev.tcl:561 msgid "Branch" @@ -168,15 +169,15 @@ msgstr "Fusionner" #: git-gui.sh:1870 lib/choose_rev.tcl:557 msgid "Remote" -msgstr "Référentiel distant" +msgstr "Dépôt distant" #: git-gui.sh:1879 msgid "Browse Current Branch's Files" -msgstr "Visionner fichiers dans branche courante" +msgstr "Naviguer dans la branche courante" #: git-gui.sh:1883 msgid "Browse Branch Files..." -msgstr "Visionner fichiers de branche" +msgstr "Naviguer dans la branche..." #: git-gui.sh:1888 msgid "Visualize Current Branch's History" @@ -184,29 +185,29 @@ msgstr "Visualiser historique branche courante" #: git-gui.sh:1892 msgid "Visualize All Branch History" -msgstr "Visualiser historique toutes branches" +msgstr "Voir l'historique de toutes les branches" #: git-gui.sh:1899 #, tcl-format msgid "Browse %s's Files" -msgstr "Visionner fichiers de %s" +msgstr "Naviguer l'arborescence de %s" #: git-gui.sh:1901 #, tcl-format msgid "Visualize %s's History" -msgstr "Visualiser historique de %s" +msgstr "Voir l'historique de la branche: %s" #: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" -msgstr "Statistiques base de donnée" +msgstr "Statistiques du dépôt" #: git-gui.sh:1909 lib/database.tcl:34 msgid "Compress Database" -msgstr "Comprimer base de donnée" +msgstr "Comprimer le dépôt" #: git-gui.sh:1912 msgid "Verify Database" -msgstr "Vérifier base de donnée" +msgstr "Vérifier le dépôt" #: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71 @@ -253,7 +254,7 @@ msgstr "Créer..." #: git-gui.sh:1974 msgid "Checkout..." -msgstr "Emprunter... " +msgstr "Charger (checkout)..." #: git-gui.sh:1980 msgid "Rename..." @@ -277,27 +278,27 @@ msgstr "Corriger dernier commit" #: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 msgid "Rescan" -msgstr "Resynchroniser" +msgstr "Recharger modifs." #: git-gui.sh:2025 msgid "Stage To Commit" -msgstr "Commiter un pré-commit" +msgstr "Indexer" #: git-gui.sh:2031 msgid "Stage Changed Files To Commit" -msgstr "Commiter fichiers modifiés dans pré-commit" +msgstr "Indexer toutes modifications" #: git-gui.sh:2037 msgid "Unstage From Commit" -msgstr "Commit vers pré-commit" +msgstr "Désindexer" #: git-gui.sh:2042 lib/index.tcl:395 msgid "Revert Changes" -msgstr "Inverser modification" +msgstr "Annuler les modifications (revert)" #: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467 msgid "Sign Off" -msgstr "Se désinscrire" +msgstr "Signer" #: git-gui.sh:2053 git-gui.sh:2372 msgid "Commit@@verb" @@ -323,7 +324,7 @@ msgstr "Pomme" #: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 #, tcl-format msgid "About %s" -msgstr "A propos de %s" +msgstr "À propos de %s" #: git-gui.sh:2099 msgid "Preferences..." @@ -352,15 +353,15 @@ msgstr "Branche courante :" #: git-gui.sh:2292 msgid "Staged Changes (Will Commit)" -msgstr "Modifications pré-commitées" +msgstr "Modifs. indexées (pour commit)" #: git-gui.sh:2312 msgid "Unstaged Changes" -msgstr "Modifications non pré-commitées" +msgstr "Modifs. non indexées" #: git-gui.sh:2362 msgid "Stage Changed" -msgstr "Pré-commit modifié" +msgstr "Indexer modifs." #: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 msgid "Push" @@ -416,19 +417,19 @@ msgstr "Rafraichir" #: git-gui.sh:2631 msgid "Decrease Font Size" -msgstr "Réduire fonte" +msgstr "Diminuer la police" #: git-gui.sh:2635 msgid "Increase Font Size" -msgstr "Agrandir fonte" +msgstr "Agrandir la police" #: git-gui.sh:2646 msgid "Unstage Hunk From Commit" -msgstr "Enlever section pré-commitée" +msgstr "Désindexer la section" #: git-gui.sh:2648 msgid "Stage Hunk For Commit" -msgstr "Pré-commiter section" +msgstr "Indexer la section" #: git-gui.sh:2667 msgid "Initializing..." @@ -545,11 +546,11 @@ msgstr "Copié ou déplacé ici par :" #: lib/branch_checkout.tcl:14 lib/branch_checkout.tcl:19 msgid "Checkout Branch" -msgstr "Emprunter branche" +msgstr "Charger la branche (checkout)" #: lib/branch_checkout.tcl:23 msgid "Checkout" -msgstr "Emprunter" +msgstr "Charger (checkout)" #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 @@ -568,11 +569,11 @@ msgstr "Options" #: lib/branch_checkout.tcl:39 lib/branch_create.tcl:92 msgid "Fetch Tracking Branch" -msgstr "Branche suivant récupération" +msgstr "Récupérer la branche de suivi" #: lib/branch_checkout.tcl:44 msgid "Detach From Local Branch" -msgstr "Détacher de branche locale" +msgstr "Détacher de la branche locale" #: lib/branch_create.tcl:22 msgid "Create Branch" @@ -600,7 +601,7 @@ msgstr "Trouver nom de branche de suivi" #: lib/branch_create.tcl:66 msgid "Starting Revision" -msgstr "Début de révision" +msgstr "Révision initiale" #: lib/branch_create.tcl:72 msgid "Update Existing Branch:" @@ -612,7 +613,7 @@ msgstr "Non" #: lib/branch_create.tcl:80 msgid "Fast Forward Only" -msgstr "Avance rapide seulement" +msgstr "Mise-à-jour rectiligne seulement (fast-forward)" #: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 msgid "Reset" @@ -620,20 +621,20 @@ msgstr "Réinitialiser" #: lib/branch_create.tcl:97 msgid "Checkout After Creation" -msgstr "Emprunt après création" +msgstr "Charger (checkout) après création" #: lib/branch_create.tcl:131 msgid "Please select a tracking branch." -msgstr "Merci de choisir une branche de suivi" +msgstr "Choisissez une branche de suivi" #: lib/branch_create.tcl:140 #, tcl-format msgid "Tracking branch %s is not a branch in the remote repository." -msgstr "La branche de suivi %s n'est pas une branche dans le référentiel distant." +msgstr "La branche de suivi %s n'est pas une branche dans le dépôt distant." #: lib/branch_create.tcl:153 lib/branch_rename.tcl:86 msgid "Please supply a branch name." -msgstr "Merci de fournir un nom de branche." +msgstr "Fournissez un nom de branche." #: lib/branch_create.tcl:164 lib/branch_rename.tcl:106 #, tcl-format @@ -712,7 +713,7 @@ msgstr "La branche '%s' existe déjà." #: lib/branch_rename.tcl:117 #, tcl-format msgid "Failed to rename '%s'." -msgstr "Le renommage de '%s' a échoué." +msgstr "Échec pour renommer '%s'." #: lib/browser.tcl:17 msgid "Starting..." @@ -733,13 +734,13 @@ msgstr "[Jusqu'au parent]" #: lib/browser.tcl:267 lib/browser.tcl:273 msgid "Browse Branch Files" -msgstr "Visionner fichiers de branches" +msgstr "Naviguer dans les fichiers de le branche" #: lib/browser.tcl:278 lib/choose_repository.tcl:387 #: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 #: lib/choose_repository.tcl:987 msgid "Browse" -msgstr "Visionner" +msgstr "Naviguer" #: lib/checkout_op.tcl:79 #, tcl-format @@ -770,7 +771,7 @@ msgid "" msgstr "" "La branche '%s' existe déjà.\n" "\n" -"Impossible d'avancer rapidement à %s.\n" +"Impossible de faire une avance rapide (fast forward) vers %s.\n" "Une fusion est nécessaire." #: lib/checkout_op.tcl:220 @@ -785,7 +786,7 @@ msgstr "La mise à jour de '%s' a échouée." #: lib/checkout_op.tcl:251 msgid "Staging area (index) is already locked." -msgstr "L'espace de pré-commit ('index' ou 'staging') est déjà vérouillé." +msgstr "L'index (staging area) est déjà vérouillé" #: lib/checkout_op.tcl:266 msgid "" @@ -796,9 +797,9 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" -"L'état lors de la dernière synchronisation ne correspond plus à l'état du référentiel.\n" +"L'état lors de la dernière synchronisation ne correspond plus à l'état du dépôt\n" "\n" -"Un autre programme Git a modifié ce référentiel depuis la dernière synchronisation. Une resynchronisation doit être effectuée avant de pouvoir modifier la branche courante.\n" +"Un autre programme Git a modifié ce dépôt depuis la dernière synchronisation. Une resynchronisation doit être effectuée avant de pouvoir modifier la branche courante.\n" "\n" "Cela va être fait tout de suite automatiquement.\n" @@ -809,12 +810,12 @@ msgstr "Mise à jour du répertoire courant avec '%s'..." #: lib/checkout_op.tcl:323 msgid "files checked out" -msgstr "fichiers empruntés" +msgstr "fichiers chargés" #: lib/checkout_op.tcl:353 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "Emprunt de '%s' abandonné. (Il est nécessaire de fusionner des fichiers.)" +msgstr "Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)." #: lib/checkout_op.tcl:354 msgid "File level merge required." @@ -840,7 +841,7 @@ msgstr "" #: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 #, tcl-format msgid "Checked out '%s'." -msgstr "'%s' emprunté." +msgstr "'%s' chargé." #: lib/checkout_op.tcl:478 #, tcl-format @@ -884,15 +885,15 @@ msgstr "Sélectionner" #: lib/choose_font.tcl:53 msgid "Font Family" -msgstr "Famille de fonte" +msgstr "Familles de polices" #: lib/choose_font.tcl:74 msgid "Font Size" -msgstr "Taille de fonte" +msgstr "Taille de police" #: lib/choose_font.tcl:91 msgid "Font Example" -msgstr "Exemple de fonte" +msgstr "Exemple de police" #: lib/choose_font.tcl:103 msgid "" @@ -900,7 +901,7 @@ msgid "" "If you like this text, it can be your font." msgstr "" "C'est un texte d'exemple.\n" -"Si vous aimez ce texte, vous pouvez choisir cette fonte." +"Si vous aimez ce texte, vous pouvez choisir cette police" #: lib/choose_repository.tcl:28 msgid "Git Gui" @@ -908,7 +909,7 @@ msgstr "Git Gui" #: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376 msgid "Create New Repository" -msgstr "Créer nouveau référentiel" +msgstr "Créer nouveau dépôt" #: lib/choose_repository.tcl:87 msgid "New..." @@ -916,7 +917,7 @@ msgstr "Nouveau..." #: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 msgid "Clone Existing Repository" -msgstr "Cloner référentiel existant" +msgstr "Cloner dépôt existant" #: lib/choose_repository.tcl:100 msgid "Clone..." @@ -924,7 +925,7 @@ msgstr "Cloner..." #: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 msgid "Open Existing Repository" -msgstr "Ouvrir référentiel existant" +msgstr "Ouvrir dépôt existant" #: lib/choose_repository.tcl:113 msgid "Open..." @@ -932,17 +933,17 @@ msgstr "Ouvrir..." #: lib/choose_repository.tcl:126 msgid "Recent Repositories" -msgstr "Référentiels récents" +msgstr "Dépôt récemment utilisés" #: lib/choose_repository.tcl:132 msgid "Open Recent Repository:" -msgstr "Ouvrir référentiel récent :" +msgstr "Ouvrir dépôt récent :" #: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303 #: lib/choose_repository.tcl:310 #, tcl-format msgid "Failed to create repository %s:" -msgstr "La création du référentiel %s a échouée :" +msgstr "La création du dépôt %s a échouée :" #: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478 msgid "Directory:" @@ -951,7 +952,7 @@ msgstr "Répertoire :" #: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 #: lib/choose_repository.tcl:1011 msgid "Git Repository" -msgstr "Référentiel Git" +msgstr "Dépôt Git" #: lib/choose_repository.tcl:437 #, tcl-format @@ -992,15 +993,15 @@ msgstr "Partagé (le plus rapide, non recommandé, pas de sauvegarde)" #: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025 #, tcl-format msgid "Not a Git repository: %s" -msgstr "'%s' n'est pas un référentiel Git." +msgstr "'%s' n'est pas un dépôt Git." #: lib/choose_repository.tcl:579 msgid "Standard only available for local repository." -msgstr "Standard n'est disponible que pour un référentiel local." +msgstr "Standard n'est disponible que pour un dépôt local." #: lib/choose_repository.tcl:583 msgid "Shared only available for local repository." -msgstr "Partagé n'est disponible que pour un référentiel local." +msgstr "Partagé n'est disponible que pour un dépôt local." #: lib/choose_repository.tcl:604 #, tcl-format @@ -1013,7 +1014,7 @@ msgstr "La configuration de l'origine a échouée." #: lib/choose_repository.tcl:627 msgid "Counting objects" -msgstr "Comptage des objets" +msgstr "Décompte des objets" #: lib/choose_repository.tcl:628 msgid "buckets" @@ -1032,11 +1033,11 @@ msgstr "Il n'y a rien à cloner depuis %s." #: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 #: lib/choose_repository.tcl:916 msgid "The 'master' branch has not been initialized." -msgstr "Cette branche 'master' n'a pas été initialisée." +msgstr "La branche 'master' n'a pas été initialisée." #: lib/choose_repository.tcl:703 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "Les liens durs ne sont pas disponibles. On se résoud à copier." +msgstr "Les liens durs ne sont pas supportés. Une copie sera effectuée à la place." #: lib/choose_repository.tcl:715 #, tcl-format @@ -1078,7 +1079,7 @@ msgstr "" #: lib/choose_repository.tcl:856 msgid "Cannot fetch tags. See console output for details." msgstr "" -"Impossible de récupérer les marques. Voir la sortie console pour plus de " +"Impossible de récupérer les marques (tags). Voir la sortie console pour plus de " "détails." #: lib/choose_repository.tcl:880 @@ -1114,7 +1115,7 @@ msgstr "fichiers" #: lib/choose_repository.tcl:955 msgid "Initial file checkout failed." -msgstr "L'emprunt initial de fichier a échoué." +msgstr "Chargement initial du fichier échoué." #: lib/choose_repository.tcl:971 msgid "Open" @@ -1122,12 +1123,12 @@ msgstr "Ouvrir" #: lib/choose_repository.tcl:981 msgid "Repository:" -msgstr "Référentiel :" +msgstr "Dépôt :" #: lib/choose_repository.tcl:1031 #, tcl-format msgid "Failed to open repository %s:" -msgstr "Impossible d'ouvrir le référentiel %s :" +msgstr "Impossible d'ouvrir le dépôt %s :" #: lib/choose_rev.tcl:53 msgid "This Detached Checkout" @@ -1143,11 +1144,11 @@ msgstr "Branche locale" #: lib/choose_rev.tcl:79 msgid "Tracking Branch" -msgstr "Suivi de branche" +msgstr "Branche de suivi" #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 msgid "Tag" -msgstr "Marque" +msgstr "Marque (tag)" #: lib/choose_rev.tcl:317 #, tcl-format @@ -1164,7 +1165,7 @@ msgstr "L'expression de révision est vide." #: lib/choose_rev.tcl:531 msgid "Updated" -msgstr "Misa à jour" +msgstr "À jour (updated)" #: lib/choose_rev.tcl:559 msgid "URL" @@ -1218,9 +1219,9 @@ msgid "" "The rescan will be automatically started now.\n" msgstr "" "L'état lors de la dernière synchronisation ne correspond plus à l'état du " -"référentiel.\n" +"dépôt.\n" "\n" -"Un autre programme Git a modifié ce référentiel depuis la dernière " +"Un autre programme Git a modifié ce dépôt depuis la dernière " "synchronisation. Une resynshronisation doit être effectuée avant de pouvoir " "créer un nouveau commit.\n" "\n" @@ -1258,7 +1259,7 @@ msgid "" msgstr "" "Pas de modification à commiter.\n" "\n" -"Vous devez pré-commiter au moins 1 fichier avant de pouvoir commiter.\n" +"Vous devez indexer au moins 1 fichier avant de pouvoir commiter.\n" #: lib/commit.tcl:183 msgid "" @@ -1285,19 +1286,19 @@ msgstr "attention : Tcl ne supporte pas l'encodage '%s'." #: lib/commit.tcl:221 msgid "Calling pre-commit hook..." -msgstr "Appel du programme externe d'avant commit..." +msgstr "Lancement de l'action d'avant-commit..." #: lib/commit.tcl:236 msgid "Commit declined by pre-commit hook." -msgstr "Commit refusé par le programme externe d'avant commit." +msgstr "Commit refusé par l'action d'avant-commit." #: lib/commit.tcl:259 msgid "Calling commit-msg hook..." -msgstr "Appel du programme externe de message de commit..." +msgstr "Lancement de l'action \"message de commit\"..." #: lib/commit.tcl:274 msgid "Commit declined by commit-msg hook." -msgstr "Commit refusé par le programme externe de message de commit." +msgstr "Commit refusé par l'action \"message de commit\"." #: lib/commit.tcl:287 msgid "Committing changes..." @@ -1406,7 +1407,7 @@ msgid "" "\n" "Compress the database now?" msgstr "" -"Ce référentiel comprend actuellement environ %i objets ayant leur fichier " +"Ce dépôt comprend actuellement environ %i objets ayant leur fichier " "particulier.\n" "\n" "Pour conserver une performance optimale, il est fortement recommandé de " @@ -1459,7 +1460,7 @@ msgstr "Erreur lors du chargement du fichier :" #: lib/diff.tcl:122 msgid "Git Repository (subproject)" -msgstr "Référentiel Git (sous projet)" +msgstr "Dépôt Git (sous projet)" #: lib/diff.tcl:134 msgid "* Binary file (not showing content)." @@ -1471,11 +1472,11 @@ msgstr "Erreur lors du chargement des différences :" #: lib/diff.tcl:303 msgid "Failed to unstage selected hunk." -msgstr "La suppression dans le pré-commit de la section sélectionnée a échouée." +msgstr "Échec lors de la désindexation de la section sélectionnée." #: lib/diff.tcl:310 msgid "Failed to stage selected hunk." -msgstr "Le pré-commit de la section sélectionnée a échoué." +msgstr "Échec lors de l'indexation de la section." #: lib/error.tcl:20 lib/error.tcl:114 msgid "error" @@ -1491,17 +1492,17 @@ msgstr "Vous devez corriger les erreurs suivantes avant de pouvoir commiter." #: lib/index.tcl:6 msgid "Unable to unlock the index." -msgstr "Impossible de dévérouiller le pré-commit." +msgstr "Impossible de dévérouiller l'index." #: lib/index.tcl:15 msgid "Index Error" -msgstr "Erreur de pré-commit" +msgstr "Erreur de l'index" #: lib/index.tcl:21 msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." -msgstr "Le pré-commit a échoué. Une resynchronisation va être lancée automatiquement." +msgstr "Échec de la mise à jour de l'index. Une resynchronisation va être lancée automatiquement." #: lib/index.tcl:27 msgid "Continue" @@ -1509,12 +1510,12 @@ msgstr "Continuer" #: lib/index.tcl:31 msgid "Unlock Index" -msgstr "Dévérouiller le pré-commit" +msgstr "Déverouiller l'index" #: lib/index.tcl:282 #, tcl-format msgid "Unstaging %s from commit" -msgstr "Supprimer %s du commit" +msgstr "Désindexation de: %s" #: lib/index.tcl:313 msgid "Ready to commit." @@ -1523,23 +1524,23 @@ msgstr "Prêt à être commité." #: lib/index.tcl:326 #, tcl-format msgid "Adding %s" -msgstr "Ajouter %s" +msgstr "Ajout de %s" #: lib/index.tcl:381 #, tcl-format msgid "Revert changes in file %s?" -msgstr "Inverser les modifications dans le fichier %s ? " +msgstr "Annuler les modifications dans le fichier %s ? " #: lib/index.tcl:383 #, tcl-format msgid "Revert changes in these %i files?" -msgstr "Inverser les modifications dans ces %i fichiers ?" +msgstr "Annuler les modifications dans ces %i fichiers ?" #: lib/index.tcl:391 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" -"Toutes les modifications non pré-commitées seront définitivement perdues " -"lors de l'inversion." +"Toutes les modifications non-indexées seront définitivement perdues " +"par l'annulation." #: lib/index.tcl:394 msgid "Do Nothing" @@ -1551,7 +1552,7 @@ msgid "" "\n" "You must finish amending this commit before starting any type of merge.\n" msgstr "" -"Impossible de fucionner pendant une correction.\n" +"Impossible de fusionner pendant une correction.\n" "\n" "Vous devez finir de corriger ce commit avant de lancer une quelconque " "fusion.\n" @@ -1566,9 +1567,9 @@ msgid "" "The rescan will be automatically started now.\n" msgstr "" "L'état lors de la dernière synchronisation ne correspond plus à l'état du " -"référentiel.\n" +"dépôt.\n" "\n" -"Un autre programme Git a modifié ce référentiel depuis la dernière " +"Un autre programme Git a modifié ce dépôt depuis la dernière " "synchronisation. Une resynchronisation doit être effectuée avant de pouvoir " "fusionner de nouveau.\n" "\n" @@ -1588,8 +1589,8 @@ msgstr "" "\n" "Le fichier %s a des conflicts de fusion.\n" "\n" -"Vous devez les résoudre, puis pré-commiter le fichier, et enfin commiter " -"pour terminer la fusion courante. Seulementà ce moment là, il sera possible " +"Vous devez les résoudre, puis indexer le fichier, et enfin commiter " +"pour terminer la fusion courante. Seulement à ce moment là sera-t-il possible " "d'effectuer une nouvelle fusion.\n" #: lib/merge.tcl:54 @@ -1704,11 +1705,11 @@ msgstr "Sauvegarder" #: lib/option.tcl:109 #, tcl-format msgid "%s Repository" -msgstr "Référentiel de %s" +msgstr "Dépôt: %s" #: lib/option.tcl:110 msgid "Global (All Repositories)" -msgstr "Globales (tous les référentiels)" +msgstr "Globales (tous les dépôts)" #: lib/option.tcl:116 msgid "User Name" @@ -1736,7 +1737,7 @@ msgstr "Faire confiance aux dates de modification de fichiers " #: lib/option.tcl:124 msgid "Prune Tracking Branches During Fetch" -msgstr "Nettoyer les branches de suivi pendant la récupération" +msgstr "Purger les branches de suivi pendant la récupération" #: lib/option.tcl:125 msgid "Match Tracking Branches" @@ -1760,7 +1761,7 @@ msgstr "Dictionnaire d'orthographe :" #: lib/option.tcl:216 msgid "Change Font" -msgstr "Modifier les fontes" +msgstr "Modifier les polices" #: lib/option.tcl:220 #, tcl-format @@ -1785,7 +1786,7 @@ msgstr "Supprimer branche distante" #: lib/remote_branch_delete.tcl:47 msgid "From Repository" -msgstr "Référentiel" +msgstr "Dépôt source" #: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 msgid "Remote:" @@ -1856,7 +1857,7 @@ msgstr "Supprimer les branches de %s" #: lib/remote_branch_delete.tcl:286 msgid "No repository selected." -msgstr "Aucun référentiel n'est sélectionné." +msgstr "Aucun dépôt n'est sélectionné." #: lib/remote_branch_delete.tcl:291 #, tcl-format @@ -1865,7 +1866,7 @@ msgstr "Synchronisation de %s..." #: lib/remote.tcl:165 msgid "Prune from" -msgstr "Nettoyer de" +msgstr "Purger de" #: lib/remote.tcl:170 msgid "Fetch from" @@ -1914,7 +1915,7 @@ msgstr "Aucune suggestion" #: lib/spellcheck.tcl:381 msgid "Unexpected EOF from spell checker" -msgstr "Fin de fichier innatendue envoyée par le vérificateur d'orthographe" +msgstr "EOF inattendue envoyée par le vérificateur d'orthographe" #: lib/spellcheck.tcl:385 msgid "Spell Checker Failed" @@ -1938,7 +1939,7 @@ msgstr "Récupération des dernières modifications de %s" #: lib/transport.tcl:18 #, tcl-format msgid "remote prune %s" -msgstr "nettoyer à distance %s" +msgstr "purger à distance %s" #: lib/transport.tcl:19 #, tcl-format @@ -1970,11 +1971,11 @@ msgstr "Branches source" #: lib/transport.tcl:120 msgid "Destination Repository" -msgstr "Référentiel de destination" +msgstr "Dépôt de destination" #: lib/transport.tcl:158 msgid "Transfer Options" -msgstr "Transférer options" +msgstr "Options de transfert" #: lib/transport.tcl:160 msgid "Force overwrite existing branch (may discard changes)" @@ -1988,5 +1989,5 @@ msgstr "Utiliser des petits paquets (pour les connexions lentes)" #: lib/transport.tcl:168 msgid "Include tags" -msgstr "Inclure les marques" +msgstr "Inclure les marques (tags)" -- cgit v1.2.1 From f2816b3d34ef93553b14a5fe23c48d092a74901f Mon Sep 17 00:00:00 2001 From: Alexandre Bourget Date: Mon, 11 Aug 2008 17:19:17 -0400 Subject: git-gui: update all remaining translations to French. Simply.. Signed-off-by: Alexandre Bourget Signed-off-by: Shawn O. Pearce --- po/fr.po | 553 ++++++++++++++++++++++++++++++++++----------------------------- 1 file changed, 299 insertions(+), 254 deletions(-) diff --git a/po/fr.po b/po/fr.po index 128cd69ef5..26b866f551 100644 --- a/po/fr.po +++ b/po/fr.po @@ -9,8 +9,8 @@ msgid "" msgstr "" "Project-Id-Version: fr\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-03-14 07:18+0100\n" -"PO-Revision-Date: 2008-08-11 16:28-0400\n" +"POT-Creation-Date: 2008-08-02 14:45-0700\n" +"PO-Revision-Date: 2008-08-11 17:12-0400\n" "Last-Translator: Alexandre Bourget \n" "Language-Team: Français \n" "MIME-Version: 1.0\n" @@ -19,33 +19,33 @@ msgstr "" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 -#: git-gui.sh:763 +#: git-gui.sh:41 git-gui.sh:688 git-gui.sh:702 git-gui.sh:715 git-gui.sh:798 +#: git-gui.sh:817 msgid "git-gui: fatal error" msgstr "git-gui: erreur fatale" -#: git-gui.sh:593 +#: git-gui.sh:644 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Police invalide spécifiée dans %s :" -#: git-gui.sh:620 +#: git-gui.sh:674 msgid "Main Font" msgstr "Police principale" -#: git-gui.sh:621 +#: git-gui.sh:675 msgid "Diff/Console Font" msgstr "Police diff/console" -#: git-gui.sh:635 +#: git-gui.sh:689 msgid "Cannot find git in PATH." msgstr "Impossible de trouver git dans PATH." -#: git-gui.sh:662 +#: git-gui.sh:716 msgid "Cannot parse Git version string:" msgstr "Impossible de parser la version de Git :" -#: git-gui.sh:680 +#: git-gui.sh:734 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -64,378 +64,381 @@ msgstr "" "\n" "Peut'on considérer que '%s' est en version 1.5.0 ?\n" -#: git-gui.sh:918 +#: git-gui.sh:972 msgid "Git directory not found:" msgstr "Impossible de trouver le répertoire git :" -#: git-gui.sh:925 +#: git-gui.sh:979 msgid "Cannot move to top of working directory:" msgstr "Impossible d'aller à la racine du répertoire de travail :" -#: git-gui.sh:932 +#: git-gui.sh:986 msgid "Cannot use funny .git directory:" msgstr "Impossible d'utiliser le répertoire .git:" -#: git-gui.sh:937 +#: git-gui.sh:991 msgid "No working directory" msgstr "Aucun répertoire de travail" -#: git-gui.sh:1084 lib/checkout_op.tcl:283 +#: git-gui.sh:1138 lib/checkout_op.tcl:305 msgid "Refreshing file status..." msgstr "Rafraichissement du status des fichiers..." -#: git-gui.sh:1149 +#: git-gui.sh:1194 msgid "Scanning for modified files ..." msgstr "Recherche de fichiers modifiés..." -#: git-gui.sh:1324 lib/browser.tcl:246 +#: git-gui.sh:1369 lib/browser.tcl:246 msgid "Ready." msgstr "Prêt." -#: git-gui.sh:1590 +#: git-gui.sh:1635 msgid "Unmodified" msgstr "Non modifié" -#: git-gui.sh:1592 +#: git-gui.sh:1637 msgid "Modified, not staged" msgstr "Modifié, pas indexé" -#: git-gui.sh:1593 git-gui.sh:1598 +#: git-gui.sh:1638 git-gui.sh:1643 msgid "Staged for commit" msgstr "Indexé" -#: git-gui.sh:1594 git-gui.sh:1599 +#: git-gui.sh:1639 git-gui.sh:1644 msgid "Portions staged for commit" msgstr "Portions indexées" -#: git-gui.sh:1595 git-gui.sh:1600 +#: git-gui.sh:1640 git-gui.sh:1645 msgid "Staged for commit, missing" msgstr "Indexés, manquant" -#: git-gui.sh:1597 +#: git-gui.sh:1642 msgid "Untracked, not staged" msgstr "Non versionné, non indexé" -#: git-gui.sh:1602 +#: git-gui.sh:1647 msgid "Missing" msgstr "Manquant" -#: git-gui.sh:1603 +#: git-gui.sh:1648 msgid "Staged for removal" msgstr "Indexé pour suppression" -#: git-gui.sh:1604 +#: git-gui.sh:1649 msgid "Staged for removal, still present" msgstr "Indexé pour suppression, toujours présent" -#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 +#: git-gui.sh:1651 git-gui.sh:1652 git-gui.sh:1653 git-gui.sh:1654 msgid "Requires merge resolution" msgstr "Nécessite la résolution d'une fusion" -#: git-gui.sh:1644 +#: git-gui.sh:1689 msgid "Starting gitk... please wait..." msgstr "Lancement de gitk... un instant..." -#: git-gui.sh:1653 -#, tcl-format -msgid "" -"Unable to start gitk:\n" -"\n" -"%s does not exist" -msgstr "" -"Impossible de lancer gitk :\n" -"\n" -"%s inexistant" +#: git-gui.sh:1698 +msgid "Couldn't find gitk in PATH" +msgstr "Impossible de trouver gitk dans PATH." -#: git-gui.sh:1860 lib/choose_repository.tcl:36 +#: git-gui.sh:1948 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Dépôt" -#: git-gui.sh:1861 +#: git-gui.sh:1949 msgid "Edit" msgstr "Edition" -#: git-gui.sh:1863 lib/choose_rev.tcl:561 +#: git-gui.sh:1951 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Branche" -#: git-gui.sh:1866 lib/choose_rev.tcl:548 +#: git-gui.sh:1954 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "Commit" -#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 +#: git-gui.sh:1957 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 msgid "Merge" msgstr "Fusionner" -#: git-gui.sh:1870 lib/choose_rev.tcl:557 +#: git-gui.sh:1958 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Dépôt distant" -#: git-gui.sh:1879 +#: git-gui.sh:1967 msgid "Browse Current Branch's Files" msgstr "Naviguer dans la branche courante" -#: git-gui.sh:1883 +#: git-gui.sh:1971 msgid "Browse Branch Files..." msgstr "Naviguer dans la branche..." -#: git-gui.sh:1888 +#: git-gui.sh:1976 msgid "Visualize Current Branch's History" msgstr "Visualiser historique branche courante" -#: git-gui.sh:1892 +#: git-gui.sh:1980 msgid "Visualize All Branch History" msgstr "Voir l'historique de toutes les branches" -#: git-gui.sh:1899 +#: git-gui.sh:1987 #, tcl-format msgid "Browse %s's Files" msgstr "Naviguer l'arborescence de %s" -#: git-gui.sh:1901 +#: git-gui.sh:1989 #, tcl-format msgid "Visualize %s's History" msgstr "Voir l'historique de la branche: %s" -#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:1994 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "Statistiques du dépôt" -#: git-gui.sh:1909 lib/database.tcl:34 +#: git-gui.sh:1997 lib/database.tcl:34 msgid "Compress Database" msgstr "Comprimer le dépôt" -#: git-gui.sh:1912 +#: git-gui.sh:2000 msgid "Verify Database" msgstr "Vérifier le dépôt" -#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 +#: git-gui.sh:2007 git-gui.sh:2011 git-gui.sh:2015 lib/shortcut.tcl:7 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71 msgid "Create Desktop Icon" msgstr "Créer icône sur bureau" -#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 +#: git-gui.sh:2023 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 msgid "Quit" msgstr "Quitter" -#: git-gui.sh:1939 +#: git-gui.sh:2031 msgid "Undo" msgstr "Défaire" -#: git-gui.sh:1942 +#: git-gui.sh:2034 msgid "Redo" msgstr "Refaire" -#: git-gui.sh:1946 git-gui.sh:2443 +#: git-gui.sh:2038 git-gui.sh:2545 msgid "Cut" msgstr "Couper" -#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614 +#: git-gui.sh:2041 git-gui.sh:2548 git-gui.sh:2622 git-gui.sh:2715 #: lib/console.tcl:69 msgid "Copy" msgstr "Copier" -#: git-gui.sh:1952 git-gui.sh:2449 +#: git-gui.sh:2044 git-gui.sh:2551 msgid "Paste" msgstr "Coller" -#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26 +#: git-gui.sh:2047 git-gui.sh:2554 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Supprimer" -#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71 +#: git-gui.sh:2051 git-gui.sh:2558 git-gui.sh:2719 lib/console.tcl:71 msgid "Select All" msgstr "Tout sélectionner" -#: git-gui.sh:1968 +#: git-gui.sh:2060 msgid "Create..." msgstr "Créer..." -#: git-gui.sh:1974 +#: git-gui.sh:2066 msgid "Checkout..." msgstr "Charger (checkout)..." -#: git-gui.sh:1980 +#: git-gui.sh:2072 msgid "Rename..." msgstr "Renommer..." -#: git-gui.sh:1985 git-gui.sh:2085 +#: git-gui.sh:2077 git-gui.sh:2187 msgid "Delete..." msgstr "Supprimer..." -#: git-gui.sh:1990 +#: git-gui.sh:2082 msgid "Reset..." msgstr "Réinitialiser..." -#: git-gui.sh:2002 git-gui.sh:2389 +#: git-gui.sh:2094 git-gui.sh:2491 msgid "New Commit" msgstr "Nouveau commit" -#: git-gui.sh:2010 git-gui.sh:2396 +#: git-gui.sh:2102 git-gui.sh:2498 msgid "Amend Last Commit" msgstr "Corriger dernier commit" -#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2111 git-gui.sh:2458 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Recharger modifs." -#: git-gui.sh:2025 +#: git-gui.sh:2117 msgid "Stage To Commit" msgstr "Indexer" -#: git-gui.sh:2031 +#: git-gui.sh:2123 msgid "Stage Changed Files To Commit" msgstr "Indexer toutes modifications" -#: git-gui.sh:2037 +#: git-gui.sh:2129 msgid "Unstage From Commit" msgstr "Désindexer" -#: git-gui.sh:2042 lib/index.tcl:395 +#: git-gui.sh:2134 lib/index.tcl:395 msgid "Revert Changes" msgstr "Annuler les modifications (revert)" -#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467 +#: git-gui.sh:2141 git-gui.sh:2702 +msgid "Show Less Context" +msgstr "Montrer moins de contexte" + +#: git-gui.sh:2145 git-gui.sh:2706 +msgid "Show More Context" +msgstr "Montrer plus de contexte" + +#: git-gui.sh:2151 git-gui.sh:2470 git-gui.sh:2569 msgid "Sign Off" msgstr "Signer" -#: git-gui.sh:2053 git-gui.sh:2372 +#: git-gui.sh:2155 git-gui.sh:2474 msgid "Commit@@verb" msgstr "Commiter" -#: git-gui.sh:2064 +#: git-gui.sh:2166 msgid "Local Merge..." msgstr "Fusion locale..." -#: git-gui.sh:2069 +#: git-gui.sh:2171 msgid "Abort Merge..." msgstr "Abandonner fusion..." -#: git-gui.sh:2081 +#: git-gui.sh:2183 msgid "Push..." msgstr "Pousser..." -#: git-gui.sh:2092 lib/choose_repository.tcl:41 -msgid "Apple" -msgstr "Pomme" - -#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14 +#: git-gui.sh:2197 git-gui.sh:2219 lib/about.tcl:14 #: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 #, tcl-format msgid "About %s" msgstr "À propos de %s" -#: git-gui.sh:2099 +#: git-gui.sh:2201 msgid "Preferences..." msgstr "Préférences..." -#: git-gui.sh:2107 git-gui.sh:2639 +#: git-gui.sh:2209 git-gui.sh:2740 msgid "Options..." msgstr "Options..." -#: git-gui.sh:2113 lib/choose_repository.tcl:47 +#: git-gui.sh:2215 lib/choose_repository.tcl:47 msgid "Help" msgstr "Aide" -#: git-gui.sh:2154 +#: git-gui.sh:2256 msgid "Online Documentation" msgstr "Documentation en ligne" -#: git-gui.sh:2238 +#: git-gui.sh:2340 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" -msgstr "erreur fatale : pas d'infos sur le chemin %s : Fichier ou répertoire inexistant" +msgstr "" +"erreur fatale : pas d'infos sur le chemin %s : Fichier ou répertoire " +"inexistant" -#: git-gui.sh:2271 +#: git-gui.sh:2373 msgid "Current Branch:" msgstr "Branche courante :" -#: git-gui.sh:2292 +#: git-gui.sh:2394 msgid "Staged Changes (Will Commit)" msgstr "Modifs. indexées (pour commit)" -#: git-gui.sh:2312 +#: git-gui.sh:2414 msgid "Unstaged Changes" msgstr "Modifs. non indexées" -#: git-gui.sh:2362 +#: git-gui.sh:2464 msgid "Stage Changed" msgstr "Indexer modifs." -#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:2480 lib/transport.tcl:93 lib/transport.tcl:182 msgid "Push" msgstr "Pousser" -#: git-gui.sh:2408 +#: git-gui.sh:2510 msgid "Initial Commit Message:" msgstr "Message de commit initial :" -#: git-gui.sh:2409 +#: git-gui.sh:2511 msgid "Amended Commit Message:" msgstr "Message de commit corrigé :" -#: git-gui.sh:2410 +#: git-gui.sh:2512 msgid "Amended Initial Commit Message:" msgstr "Message de commit initial corrigé :" -#: git-gui.sh:2411 +#: git-gui.sh:2513 msgid "Amended Merge Commit Message:" msgstr "Message de commit de fusion corrigé :" -#: git-gui.sh:2412 +#: git-gui.sh:2514 msgid "Merge Commit Message:" msgstr "Message de commit de fusion :" -#: git-gui.sh:2413 +#: git-gui.sh:2515 msgid "Commit Message:" msgstr "Message de commit :" -#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73 +#: git-gui.sh:2561 git-gui.sh:2723 lib/console.tcl:73 msgid "Copy All" msgstr "Copier tout" -#: git-gui.sh:2483 lib/blame.tcl:107 +#: git-gui.sh:2585 lib/blame.tcl:100 msgid "File:" msgstr "Fichier :" -#: git-gui.sh:2589 +#: git-gui.sh:2691 msgid "Apply/Reverse Hunk" msgstr "Appliquer/Inverser section" -#: git-gui.sh:2595 -msgid "Show Less Context" -msgstr "Montrer moins de contexte" - -#: git-gui.sh:2602 -msgid "Show More Context" -msgstr "Montrer plus de contexte" +#: git-gui.sh:2696 +msgid "Apply/Reverse Line" +msgstr "Appliquer/Inverser la ligne" -#: git-gui.sh:2610 +#: git-gui.sh:2711 msgid "Refresh" msgstr "Rafraichir" -#: git-gui.sh:2631 +#: git-gui.sh:2732 msgid "Decrease Font Size" msgstr "Diminuer la police" -#: git-gui.sh:2635 +#: git-gui.sh:2736 msgid "Increase Font Size" msgstr "Agrandir la police" -#: git-gui.sh:2646 +#: git-gui.sh:2747 msgid "Unstage Hunk From Commit" msgstr "Désindexer la section" -#: git-gui.sh:2648 +#: git-gui.sh:2748 +msgid "Unstage Line From Commit" +msgstr "Désindexer la ligne" + +#: git-gui.sh:2750 msgid "Stage Hunk For Commit" msgstr "Indexer la section" -#: git-gui.sh:2667 +#: git-gui.sh:2751 +msgid "Stage Line For Commit" +msgstr "Indexer la ligne" + +#: git-gui.sh:2771 msgid "Initializing..." msgstr "Initialisation..." -#: git-gui.sh:2762 +#: git-gui.sh:2876 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -452,7 +455,7 @@ msgstr "" "sous-processus de Git lancés par %s\n" "\n" -#: git-gui.sh:2792 +#: git-gui.sh:2906 msgid "" "\n" "This is due to a known issue with the\n" @@ -462,7 +465,7 @@ msgstr "" "Ceci est du à un problème connu avec\n" "le binaire Tcl distribué par Cygwin." -#: git-gui.sh:2797 +#: git-gui.sh:2911 #, tcl-format msgid "" "\n" @@ -483,64 +486,80 @@ msgstr "" msgid "git-gui - a graphical user interface for Git." msgstr "git-gui - une interface graphique utilisateur pour Git" -#: lib/blame.tcl:77 +#: lib/blame.tcl:70 msgid "File Viewer" msgstr "Visionneur de fichier" -#: lib/blame.tcl:81 +#: lib/blame.tcl:74 msgid "Commit:" msgstr "Commit :" -#: lib/blame.tcl:264 +#: lib/blame.tcl:257 msgid "Copy Commit" msgstr "Copier commit" -#: lib/blame.tcl:384 +#: lib/blame.tcl:260 +msgid "Do Full Copy Detection" +msgstr "Lancer la détection approfondie des copies" + +#: lib/blame.tcl:388 #, tcl-format msgid "Reading %s..." msgstr "Lecture de %s..." -#: lib/blame.tcl:488 +#: lib/blame.tcl:492 msgid "Loading copy/move tracking annotations..." msgstr "Chargement des annotations de suivi des copies/déplacements..." -#: lib/blame.tcl:508 +#: lib/blame.tcl:512 msgid "lines annotated" msgstr "lignes annotées" -#: lib/blame.tcl:689 +#: lib/blame.tcl:704 msgid "Loading original location annotations..." msgstr "Chargement des annotations d'emplacement original" -#: lib/blame.tcl:692 +#: lib/blame.tcl:707 msgid "Annotation complete." msgstr "Annotation terminée." -#: lib/blame.tcl:746 +#: lib/blame.tcl:737 +msgid "Busy" +msgstr "Occupé" + +#: lib/blame.tcl:738 +msgid "Annotation process is already running." +msgstr "Annotation en cours d'exécution." + +#: lib/blame.tcl:777 +msgid "Running thorough copy detection..." +msgstr "Recherche de copie approfondie en cours..." + +#: lib/blame.tcl:827 msgid "Loading annotation..." msgstr "Chargement des annotations..." -#: lib/blame.tcl:802 +#: lib/blame.tcl:883 msgid "Author:" msgstr "Auteur :" -#: lib/blame.tcl:806 +#: lib/blame.tcl:887 msgid "Committer:" msgstr "Commiteur :" -#: lib/blame.tcl:811 +#: lib/blame.tcl:892 msgid "Original File:" msgstr "Fichier original :" -#: lib/blame.tcl:925 +#: lib/blame.tcl:1006 msgid "Originally By:" msgstr "A l'origine par :" -#: lib/blame.tcl:931 +#: lib/blame.tcl:1012 msgid "In File:" msgstr "Dans le fichier :" -#: lib/blame.tcl:936 +#: lib/blame.tcl:1017 msgid "Copied Or Moved Here By:" msgstr "Copié ou déplacé ici par :" @@ -554,7 +573,7 @@ msgstr "Charger (checkout)" #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 -#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171 +#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:171 #: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 msgid "Cancel" msgstr "Annuler" @@ -563,7 +582,7 @@ msgstr "Annuler" msgid "Revision" msgstr "Révision" -#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242 +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:244 msgid "Options" msgstr "Options" @@ -615,7 +634,7 @@ msgstr "Non" msgid "Fast Forward Only" msgstr "Mise-à-jour rectiligne seulement (fast-forward)" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 msgid "Reset" msgstr "Réinitialiser" @@ -655,7 +674,7 @@ msgstr "Branches locales" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" -msgstr "Supprimer ssi fusion dedans" +msgstr "Supprimer seulement si fusionnée dans:" #: lib/branch_delete.tcl:54 msgid "Always (Do not perform merge test.)" @@ -705,7 +724,7 @@ msgstr "Nouveau nom :" msgid "Please select a branch to rename." msgstr "Merci de sélectionner une branche à renommer." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 #, tcl-format msgid "Branch '%s' already exists." msgstr "La branche '%s' existe déjà." @@ -737,31 +756,36 @@ msgid "Browse Branch Files" msgstr "Naviguer dans les fichiers de le branche" #: lib/browser.tcl:278 lib/choose_repository.tcl:387 -#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 -#: lib/choose_repository.tcl:987 +#: lib/choose_repository.tcl:472 lib/choose_repository.tcl:482 +#: lib/choose_repository.tcl:985 msgid "Browse" msgstr "Naviguer" -#: lib/checkout_op.tcl:79 +#: lib/checkout_op.tcl:84 #, tcl-format msgid "Fetching %s from %s" msgstr "Récupération de %s à partir de %s" -#: lib/checkout_op.tcl:127 +#: lib/checkout_op.tcl:132 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "erreur fatale : Impossible de résoudre %s" -#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 msgid "Close" msgstr "Fermer" -#: lib/checkout_op.tcl:169 +#: lib/checkout_op.tcl:174 #, tcl-format msgid "Branch '%s' does not exist." msgstr "La branche '%s' n'existe pas." -#: lib/checkout_op.tcl:206 +#: lib/checkout_op.tcl:193 +#, tcl-format +msgid "Failed to configure simplified git-pull for '%s'." +msgstr "Échec de la configuration simplifiée de git-pull pour '%s'." + +#: lib/checkout_op.tcl:228 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -774,21 +798,21 @@ msgstr "" "Impossible de faire une avance rapide (fast forward) vers %s.\n" "Une fusion est nécessaire." -#: lib/checkout_op.tcl:220 +#: lib/checkout_op.tcl:242 #, tcl-format msgid "Merge strategy '%s' not supported." msgstr "La stratégie de fusion '%s' n'est pas supportée." -#: lib/checkout_op.tcl:239 +#: lib/checkout_op.tcl:261 #, tcl-format msgid "Failed to update '%s'." msgstr "La mise à jour de '%s' a échouée." -#: lib/checkout_op.tcl:251 +#: lib/checkout_op.tcl:273 msgid "Staging area (index) is already locked." msgstr "L'index (staging area) est déjà vérouillé" -#: lib/checkout_op.tcl:266 +#: lib/checkout_op.tcl:288 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -797,36 +821,39 @@ msgid "" "\n" "The rescan will be automatically started now.\n" msgstr "" -"L'état lors de la dernière synchronisation ne correspond plus à l'état du dépôt\n" +"L'état lors de la dernière synchronisation ne correspond plus à l'état du " +"dépôt\n" "\n" -"Un autre programme Git a modifié ce dépôt depuis la dernière synchronisation. Une resynchronisation doit être effectuée avant de pouvoir modifier la branche courante.\n" +"Un autre programme Git a modifié ce dépôt depuis la dernière " +"synchronisation. Une resynchronisation doit être effectuée avant de pouvoir " +"modifier la branche courante.\n" "\n" "Cela va être fait tout de suite automatiquement.\n" -#: lib/checkout_op.tcl:322 +#: lib/checkout_op.tcl:344 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Mise à jour du répertoire courant avec '%s'..." -#: lib/checkout_op.tcl:323 +#: lib/checkout_op.tcl:345 msgid "files checked out" msgstr "fichiers chargés" -#: lib/checkout_op.tcl:353 +#: lib/checkout_op.tcl:375 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." msgstr "Chargement de '%s' abandonné (il est nécessaire de fusionner des fichiers)." -#: lib/checkout_op.tcl:354 +#: lib/checkout_op.tcl:376 msgid "File level merge required." msgstr "Il est nécessaire de fusionner des fichiers." -#: lib/checkout_op.tcl:358 +#: lib/checkout_op.tcl:380 #, tcl-format msgid "Staying on branch '%s'." msgstr "Le répertoire de travail reste sur la branche '%s'." -#: lib/checkout_op.tcl:429 +#: lib/checkout_op.tcl:451 msgid "" "You are no longer on a local branch.\n" "\n" @@ -838,30 +865,30 @@ msgstr "" "Si vous vouliez être sur une branche, créez en une maintenant en partant de " "'Cet emprunt détaché'." -#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 +#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 #, tcl-format msgid "Checked out '%s'." msgstr "'%s' chargé." -#: lib/checkout_op.tcl:478 +#: lib/checkout_op.tcl:500 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "Réinitialiser '%s' à '%s' va faire perdre les commits suivants :" -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:522 msgid "Recovering lost commits may not be easy." msgstr "Récupérer les commits perdus ne sera peut être pas facile." -#: lib/checkout_op.tcl:505 +#: lib/checkout_op.tcl:527 #, tcl-format msgid "Reset '%s'?" msgstr "Réinitialiser '%s' ?" -#: lib/checkout_op.tcl:510 lib/merge.tcl:163 +#: lib/checkout_op.tcl:532 lib/merge.tcl:163 msgid "Visualize" msgstr "Visualiser" -#: lib/checkout_op.tcl:578 +#: lib/checkout_op.tcl:600 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -915,7 +942,7 @@ msgstr "Créer nouveau dépôt" msgid "New..." msgstr "Nouveau..." -#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:458 msgid "Clone Existing Repository" msgstr "Cloner dépôt existant" @@ -923,7 +950,7 @@ msgstr "Cloner dépôt existant" msgid "Clone..." msgstr "Cloner..." -#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 +#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:974 msgid "Open Existing Repository" msgstr "Ouvrir dépôt existant" @@ -945,187 +972,187 @@ msgstr "Ouvrir dépôt récent :" msgid "Failed to create repository %s:" msgstr "La création du dépôt %s a échouée :" -#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478 +#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:476 msgid "Directory:" msgstr "Répertoire :" -#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 -#: lib/choose_repository.tcl:1011 +#: lib/choose_repository.tcl:410 lib/choose_repository.tcl:535 +#: lib/choose_repository.tcl:1007 msgid "Git Repository" msgstr "Dépôt Git" -#: lib/choose_repository.tcl:437 +#: lib/choose_repository.tcl:435 #, tcl-format msgid "Directory %s already exists." msgstr "Le répertoire %s existe déjà." -#: lib/choose_repository.tcl:441 +#: lib/choose_repository.tcl:439 #, tcl-format msgid "File %s already exists." msgstr "Le fichier %s existe déjà." -#: lib/choose_repository.tcl:455 +#: lib/choose_repository.tcl:453 msgid "Clone" msgstr "Cloner" -#: lib/choose_repository.tcl:468 +#: lib/choose_repository.tcl:466 msgid "URL:" msgstr "URL :" -#: lib/choose_repository.tcl:489 +#: lib/choose_repository.tcl:487 msgid "Clone Type:" msgstr "Type de clonage :" -#: lib/choose_repository.tcl:495 +#: lib/choose_repository.tcl:493 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Standard (rapide, semi-redondant, liens durs)" -#: lib/choose_repository.tcl:501 +#: lib/choose_repository.tcl:499 msgid "Full Copy (Slower, Redundant Backup)" msgstr "Copy complète (plus lent, sauvegarde redondante)" -#: lib/choose_repository.tcl:507 +#: lib/choose_repository.tcl:505 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Partagé (le plus rapide, non recommandé, pas de sauvegarde)" -#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590 -#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806 -#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025 +#: lib/choose_repository.tcl:541 lib/choose_repository.tcl:588 +#: lib/choose_repository.tcl:734 lib/choose_repository.tcl:804 +#: lib/choose_repository.tcl:1013 lib/choose_repository.tcl:1021 #, tcl-format msgid "Not a Git repository: %s" msgstr "'%s' n'est pas un dépôt Git." -#: lib/choose_repository.tcl:579 +#: lib/choose_repository.tcl:577 msgid "Standard only available for local repository." msgstr "Standard n'est disponible que pour un dépôt local." -#: lib/choose_repository.tcl:583 +#: lib/choose_repository.tcl:581 msgid "Shared only available for local repository." msgstr "Partagé n'est disponible que pour un dépôt local." -#: lib/choose_repository.tcl:604 +#: lib/choose_repository.tcl:602 #, tcl-format msgid "Location %s already exists." msgstr "L'emplacement %s existe déjà." -#: lib/choose_repository.tcl:615 +#: lib/choose_repository.tcl:613 msgid "Failed to configure origin" msgstr "La configuration de l'origine a échouée." -#: lib/choose_repository.tcl:627 +#: lib/choose_repository.tcl:625 msgid "Counting objects" msgstr "Décompte des objets" -#: lib/choose_repository.tcl:628 +#: lib/choose_repository.tcl:626 msgid "buckets" msgstr "paniers" -#: lib/choose_repository.tcl:652 +#: lib/choose_repository.tcl:650 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Impossible de copier 'objects/info/alternates' : %s" -#: lib/choose_repository.tcl:688 +#: lib/choose_repository.tcl:686 #, tcl-format msgid "Nothing to clone from %s." msgstr "Il n'y a rien à cloner depuis %s." -#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 -#: lib/choose_repository.tcl:916 +#: lib/choose_repository.tcl:688 lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:914 msgid "The 'master' branch has not been initialized." msgstr "La branche 'master' n'a pas été initialisée." -#: lib/choose_repository.tcl:703 +#: lib/choose_repository.tcl:701 msgid "Hardlinks are unavailable. Falling back to copying." msgstr "Les liens durs ne sont pas supportés. Une copie sera effectuée à la place." -#: lib/choose_repository.tcl:715 +#: lib/choose_repository.tcl:713 #, tcl-format msgid "Cloning from %s" msgstr "Clonage depuis %s" -#: lib/choose_repository.tcl:746 +#: lib/choose_repository.tcl:744 msgid "Copying objects" msgstr "Copie des objets" -#: lib/choose_repository.tcl:747 +#: lib/choose_repository.tcl:745 msgid "KiB" msgstr "KiB" -#: lib/choose_repository.tcl:771 +#: lib/choose_repository.tcl:769 #, tcl-format msgid "Unable to copy object: %s" msgstr "Impossible de copier l'objet : %s" -#: lib/choose_repository.tcl:781 +#: lib/choose_repository.tcl:779 msgid "Linking objects" msgstr "Liaison des objets" -#: lib/choose_repository.tcl:782 +#: lib/choose_repository.tcl:780 msgid "objects" msgstr "objets" -#: lib/choose_repository.tcl:790 +#: lib/choose_repository.tcl:788 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Impossible créer un lien dur pour l'objet : %s" -#: lib/choose_repository.tcl:845 +#: lib/choose_repository.tcl:843 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Impossible de récupérer les branches et objets. Voir la sortie console pour " "plus de détails." -#: lib/choose_repository.tcl:856 +#: lib/choose_repository.tcl:854 msgid "Cannot fetch tags. See console output for details." msgstr "" -"Impossible de récupérer les marques (tags). Voir la sortie console pour plus de " -"détails." +"Impossible de récupérer les marques (tags). Voir la sortie console pour plus " +"de détails." -#: lib/choose_repository.tcl:880 +#: lib/choose_repository.tcl:878 msgid "Cannot determine HEAD. See console output for details." msgstr "Impossible de déterminer HEAD. Voir la sortie console pour plus de détails." -#: lib/choose_repository.tcl:889 +#: lib/choose_repository.tcl:887 #, tcl-format msgid "Unable to cleanup %s" msgstr "Impossible de nettoyer %s" -#: lib/choose_repository.tcl:895 +#: lib/choose_repository.tcl:893 msgid "Clone failed." msgstr "Le clonage a échoué." -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:900 msgid "No default branch obtained." msgstr "Aucune branche par défaut n'a été obtenue." -#: lib/choose_repository.tcl:913 +#: lib/choose_repository.tcl:911 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Impossible de résoudre %s comme commit." -#: lib/choose_repository.tcl:925 +#: lib/choose_repository.tcl:923 msgid "Creating working directory" msgstr "Création du répertoire de travail" -#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127 +#: lib/choose_repository.tcl:924 lib/index.tcl:65 lib/index.tcl:127 #: lib/index.tcl:193 msgid "files" msgstr "fichiers" -#: lib/choose_repository.tcl:955 +#: lib/choose_repository.tcl:953 msgid "Initial file checkout failed." msgstr "Chargement initial du fichier échoué." -#: lib/choose_repository.tcl:971 +#: lib/choose_repository.tcl:969 msgid "Open" msgstr "Ouvrir" -#: lib/choose_repository.tcl:981 +#: lib/choose_repository.tcl:979 msgid "Repository:" msgstr "Dépôt :" -#: lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:1027 #, tcl-format msgid "Failed to open repository %s:" msgstr "Impossible d'ouvrir le dépôt %s :" @@ -1165,7 +1192,7 @@ msgstr "L'expression de révision est vide." #: lib/choose_rev.tcl:531 msgid "Updated" -msgstr "À jour (updated)" +msgstr "Mise-à-jour:" #: lib/choose_rev.tcl:559 msgid "URL" @@ -1421,7 +1448,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "Date invalide de Git : %s" -#: lib/diff.tcl:42 +#: lib/diff.tcl:44 #, tcl-format msgid "" "No differences detected.\n" @@ -1444,40 +1471,48 @@ msgstr "" "Une resynchronisation va être lancée automatiquement pour trouver d'autres " "fichiers qui pourraient se trouver dans le même état." -#: lib/diff.tcl:81 +#: lib/diff.tcl:83 #, tcl-format msgid "Loading diff of %s..." msgstr "Chargement des différences de %s..." -#: lib/diff.tcl:114 lib/diff.tcl:184 +#: lib/diff.tcl:116 lib/diff.tcl:190 #, tcl-format msgid "Unable to display %s" msgstr "Impossible d'afficher %s" -#: lib/diff.tcl:115 +#: lib/diff.tcl:117 msgid "Error loading file:" msgstr "Erreur lors du chargement du fichier :" -#: lib/diff.tcl:122 +#: lib/diff.tcl:124 msgid "Git Repository (subproject)" msgstr "Dépôt Git (sous projet)" -#: lib/diff.tcl:134 +#: lib/diff.tcl:136 msgid "* Binary file (not showing content)." msgstr "* Fichier binaire (pas d'apperçu du contenu)." -#: lib/diff.tcl:185 +#: lib/diff.tcl:191 msgid "Error loading diff:" msgstr "Erreur lors du chargement des différences :" -#: lib/diff.tcl:303 +#: lib/diff.tcl:313 msgid "Failed to unstage selected hunk." msgstr "Échec lors de la désindexation de la section sélectionnée." -#: lib/diff.tcl:310 +#: lib/diff.tcl:320 msgid "Failed to stage selected hunk." msgstr "Échec lors de l'indexation de la section." +#: lib/diff.tcl:386 +msgid "Failed to unstage selected line." +msgstr "Échec lors de la désindexation de la ligne sélectionnée." + +#: lib/diff.tcl:394 +msgid "Failed to stage selected line." +msgstr "Échec lors de l'indexation de la ligne." + #: lib/error.tcl:20 lib/error.tcl:114 msgid "error" msgstr "erreur" @@ -1502,7 +1537,9 @@ msgstr "Erreur de l'index" msgid "" "Updating the Git index failed. A rescan will be automatically started to " "resynchronize git-gui." -msgstr "Échec de la mise à jour de l'index. Une resynchronisation va être lancée automatiquement." +msgstr "" +"Échec de la mise à jour de l'index. Une resynchronisation va être lancée " +"automatiquement." #: lib/index.tcl:27 msgid "Continue" @@ -1539,8 +1576,8 @@ msgstr "Annuler les modifications dans ces %i fichiers ?" #: lib/index.tcl:391 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" -"Toutes les modifications non-indexées seront définitivement perdues " -"par l'annulation." +"Toutes les modifications non-indexées seront définitivement perdues par " +"l'annulation." #: lib/index.tcl:394 msgid "Do Nothing" @@ -1589,8 +1626,8 @@ msgstr "" "\n" "Le fichier %s a des conflicts de fusion.\n" "\n" -"Vous devez les résoudre, puis indexer le fichier, et enfin commiter " -"pour terminer la fusion courante. Seulement à ce moment là sera-t-il possible " +"Vous devez les résoudre, puis indexer le fichier, et enfin commiter pour " +"terminer la fusion courante. Seulement à ce moment là sera-t-il possible " "d'effectuer une nouvelle fusion.\n" #: lib/merge.tcl:54 @@ -1686,11 +1723,11 @@ msgstr "Abandon" msgid "files reset" msgstr "fichiers réinitialisés" -#: lib/merge.tcl:265 +#: lib/merge.tcl:266 msgid "Abort failed." msgstr "L'abandon a échoué." -#: lib/merge.tcl:267 +#: lib/merge.tcl:268 msgid "Abort completed. Ready." msgstr "Abandon teminé. Prêt." @@ -1744,42 +1781,62 @@ msgid "Match Tracking Branches" msgstr "Faire correspondre les branches de suivi" #: lib/option.tcl:126 +msgid "Blame Copy Only On Changed Files" +msgstr "Annoter les copies seulement sur fichiers modifiés" + +#: lib/option.tcl:127 +msgid "Minimum Letters To Blame Copy On" +msgstr "Minimum de caratères pour annoter une copie" + +#: lib/option.tcl:128 msgid "Number of Diff Context Lines" msgstr "Nombre de lignes de contexte dans les diffs" -#: lib/option.tcl:127 +#: lib/option.tcl:129 msgid "Commit Message Text Width" msgstr "Largeur du texte de message de commit" -#: lib/option.tcl:128 +#: lib/option.tcl:130 msgid "New Branch Name Template" msgstr "Nouveau modèle de nom de branche" -#: lib/option.tcl:192 +#: lib/option.tcl:194 msgid "Spelling Dictionary:" msgstr "Dictionnaire d'orthographe :" -#: lib/option.tcl:216 +#: lib/option.tcl:218 msgid "Change Font" msgstr "Modifier les polices" -#: lib/option.tcl:220 +#: lib/option.tcl:222 #, tcl-format msgid "Choose %s" msgstr "Choisir %s" -#: lib/option.tcl:226 +#: lib/option.tcl:228 msgid "pt." msgstr "pt." -#: lib/option.tcl:240 +#: lib/option.tcl:242 msgid "Preferences" msgstr "Préférences" -#: lib/option.tcl:275 +#: lib/option.tcl:277 msgid "Failed to completely save options:" msgstr "La sauvegarde complète des options a échouée :" +#: lib/remote.tcl:165 +msgid "Prune from" +msgstr "Purger de" + +#: lib/remote.tcl:170 +msgid "Fetch from" +msgstr "Récupérer de" + +#: lib/remote.tcl:213 +msgid "Push to" +msgstr "Pousser vers" + #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 msgid "Delete Remote Branch" msgstr "Supprimer branche distante" @@ -1864,18 +1921,6 @@ msgstr "Aucun dépôt n'est sélectionné." msgid "Scanning %s..." msgstr "Synchronisation de %s..." -#: lib/remote.tcl:165 -msgid "Prune from" -msgstr "Purger de" - -#: lib/remote.tcl:170 -msgid "Fetch from" -msgstr "Récupérer de" - -#: lib/remote.tcl:213 -msgid "Push to" -msgstr "Pousser vers" - #: lib/shortcut.tcl:20 lib/shortcut.tcl:61 msgid "Cannot write shortcut:" msgstr "Impossible d'écrire le raccourcis :" @@ -1909,15 +1954,15 @@ msgstr "La vérification d'orthographe a échouée silentieusement au démarrage msgid "Unrecognized spell checker" msgstr "Vérificateur d'orthographe non reconnu" -#: lib/spellcheck.tcl:180 +#: lib/spellcheck.tcl:186 msgid "No Suggestions" msgstr "Aucune suggestion" -#: lib/spellcheck.tcl:381 +#: lib/spellcheck.tcl:387 msgid "Unexpected EOF from spell checker" msgstr "EOF inattendue envoyée par le vérificateur d'orthographe" -#: lib/spellcheck.tcl:385 +#: lib/spellcheck.tcl:391 msgid "Spell Checker Failed" msgstr "Le vérificateur d'orthographe a échoué" -- cgit v1.2.1 From d266a988456fcaab9918eae39f5faf8dcb20cb26 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Tue, 12 Aug 2008 01:55:37 +0200 Subject: Documentation: rev-list-options: move --simplify-merges documentation Fits --simplify-merges documentation into the 'History Simplification' section, including example. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- Documentation/rev-list-options.txt | 48 +++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 059ae69d84..15752b92a5 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -193,12 +193,6 @@ endif::git-rev-list[] Stop when a given path disappears from the tree. ---simplify-merges:: - - Simplify away commits that did not change the given paths, similar - to `--full-history`, and further remove merges none of whose - parent history changes the given paths. - --no-merges:: Do not print commits with more than one parent. @@ -413,6 +407,48 @@ Note that without '\--full-history', this still simplifies merges: if one of the parents is TREESAME, we follow only that one, so the other sides of the merge are never walked. +Finally, there is a fourth simplification mode available: + +--simplify-merges:: + + First, build a history graph in the same way that + '\--full-history' with parent rewriting does (see above). ++ +Then simplify each commit `C` to its replacement `C'` in the final +history according to the following rules: ++ +-- +* Set `C'` to `C`. ++ +* Replace each parent `P` of `C'` with its simplification `P'`. In + the process, drop parents that are ancestors of other parents, and + remove duplicates. ++ +* If after this parent rewriting, `C'` is a root or merge commit (has + zero or >1 parents), a boundary commit, or !TREESAME, it remains. + Otherwise, it is replaced with its only parent. +-- ++ +The effect of this is best shown by way of comparing to +'\--full-history' with parent rewriting. The example turns into: ++ +----------------------------------------------------------------------- + .-A---M---N---O + / / / + I B D + \ / / + `---------' +----------------------------------------------------------------------- ++ +Note the major differences in `N` and `P` over '\--full-history': ++ +-- +* `N`'s parent list had `I` removed, because it is an ancestor of the + other parent `M`. Still, `N` remained because it is !TREESAME. ++ +* `P`'s parent list similarly had `I` removed. `P` was then + removed completely, because it had one parent and is TREESAME. +-- ifdef::git-rev-list[] Bisection Helpers -- cgit v1.2.1 From c99db9d292c5f63c83ae2b441a67121d76553413 Mon Sep 17 00:00:00 2001 From: Brian Downing Date: Thu, 14 Aug 2008 00:36:50 -0500 Subject: Make xdi_diff_outf interface for running xdiff_outf diffs To prepare for the need to initialize and release resources for an xdi_diff with the xdiff_outf output function, make a new function to wrap this usage. Old: ecb.outf = xdiff_outf; ecb.priv = &state; ... xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb); New: xdi_diff_outf(file_p, file_o, &state.xm, &xpp, &xecfg, &ecb); Signed-off-by: Brian Downing Signed-off-by: Junio C Hamano --- builtin-blame.c | 4 +--- combine-diff.c | 5 ++--- diff.c | 20 +++++--------------- xdiff-interface.c | 13 ++++++++++++- xdiff-interface.h | 4 +++- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 4ea343189f..8cca3b16d2 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -528,15 +528,13 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, xpp.flags = xdl_opts; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = context; - ecb.outf = xdiff_outf; - ecb.priv = &state; memset(&state, 0, sizeof(state)); state.xm.consume = process_u_diff; state.ret = xmalloc(sizeof(struct patch)); state.ret->chunks = NULL; state.ret->num = 0; - xdi_diff(file_p, file_o, &xpp, &xecfg, &ecb); + xdi_diff_outf(file_p, file_o, &state.xm, &xpp, &xecfg, &ecb); if (state.ret->num) { struct chunk *chunk; diff --git a/combine-diff.c b/combine-diff.c index 9f80a1c5e3..72dd6d2234 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -217,8 +217,6 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file, parent_file.size = sz; xpp.flags = XDF_NEED_MINIMAL; memset(&xecfg, 0, sizeof(xecfg)); - ecb.outf = xdiff_outf; - ecb.priv = &state; memset(&state, 0, sizeof(state)); state.xm.consume = consume_line; state.nmask = nmask; @@ -227,7 +225,8 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file, state.num_parent = num_parent; state.n = n; - xdi_diff(&parent_file, result_file, &xpp, &xecfg, &ecb); + xdi_diff_outf(&parent_file, result_file, + &state.xm, &xpp, &xecfg, &ecb); free(parent_file.ptr); /* Assign line numbers for this parent. diff --git a/diff.c b/diff.c index bf5d5f15a3..913a92f3e9 100644 --- a/diff.c +++ b/diff.c @@ -459,10 +459,8 @@ static void diff_words_show(struct diff_words_data *diff_words) xpp.flags = XDF_NEED_MINIMAL; xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; - ecb.outf = xdiff_outf; - ecb.priv = diff_words; diff_words->xm.consume = fn_out_diff_words_aux; - xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb); + xdi_diff_outf(&minus, &plus, &diff_words->xm, &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); @@ -1521,15 +1519,13 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - ecb.outf = xdiff_outf; - ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); ecbdata.diff_words->file = o->file; } - xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, &ecbdata.xm, &xpp, &xecfg, &ecb); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); } @@ -1580,9 +1576,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; - ecb.outf = xdiff_outf; - ecb.priv = diffstat; - xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, &diffstat->xm, &xpp, &xecfg, &ecb); } free_and_return: @@ -1628,9 +1622,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL; - ecb.outf = xdiff_outf; - ecb.priv = &data; - xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, &data.xm, &xpp, &xecfg, &ecb); if ((data.ws_rule & WS_TRAILING_SPACE) && data.trailing_blanks_start) { @@ -3128,9 +3120,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xpp.flags = XDF_NEED_MINIMAL; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; - ecb.outf = xdiff_outf; - ecb.priv = &data; - xdi_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, &data.xm, &xpp, &xecfg, &ecb); } SHA1_Final(sha1, &ctx); diff --git a/xdiff-interface.c b/xdiff-interface.c index 61dc5c5470..828b4966a9 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -61,7 +61,7 @@ static void consume_one(void *priv_, char *s, unsigned long size) } } -int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) +static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) { struct xdiff_emit_state *priv = priv_; int i; @@ -141,6 +141,17 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co return xdl_diff(&a, &b, xpp, xecfg, xecb); } +int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, + struct xdiff_emit_state *state, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *xecb) +{ + int ret; + xecb->outf = xdiff_outf; + xecb->priv = state; + ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb); + return ret; +} + int read_mmfile(mmfile_t *ptr, const char *filename) { struct stat st; diff --git a/xdiff-interface.h b/xdiff-interface.h index f7f791d96b..6f3b361a84 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -14,7 +14,9 @@ struct xdiff_emit_state { }; int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); -int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf); +int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, + struct xdiff_emit_state *state, xpparam_t const *xpp, + xdemitconf_t const *xecfg, xdemitcb_t *xecb); int parse_hunk_header(char *line, int len, int *ob, int *on, int *nb, int *nn); -- cgit v1.2.1 From b463776086a12c587f6d91c0347641fb6f7ddd72 Mon Sep 17 00:00:00 2001 From: Brian Downing Date: Thu, 14 Aug 2008 00:36:51 -0500 Subject: Use strbuf for struct xdiff_emit_state's remainder Continually xreallocing and freeing the remainder member of struct xdiff_emit_state was a noticeable performance hit. Use a strbuf instead. This yields a decent performance improvement on "git blame" on certain repositories. For example, before this commit: $ time git blame -M -C -C -p --incremental server.c >/dev/null 101.52user 0.17system 1:41.73elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+39561minor)pagefaults 0swaps With this commit: $ time git blame -M -C -C -p --incremental server.c >/dev/null 80.38user 0.30system 1:20.81elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+50979minor)pagefaults 0swaps Signed-off-by: Brian Downing Signed-off-by: Junio C Hamano --- xdiff-interface.c | 32 ++++++++++---------------------- xdiff-interface.h | 4 ++-- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/xdiff-interface.c b/xdiff-interface.c index 828b4966a9..bf98866c4f 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -69,36 +69,22 @@ static int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf) for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ - priv->remainder = xrealloc(priv->remainder, - priv->remainder_size + - mb[i].size); - memcpy(priv->remainder + priv->remainder_size, - mb[i].ptr, mb[i].size); - priv->remainder_size += mb[i].size; + strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size); continue; } /* we have a complete line */ - if (!priv->remainder) { + if (!priv->remainder.len) { consume_one(priv, mb[i].ptr, mb[i].size); continue; } - priv->remainder = xrealloc(priv->remainder, - priv->remainder_size + - mb[i].size); - memcpy(priv->remainder + priv->remainder_size, - mb[i].ptr, mb[i].size); - consume_one(priv, priv->remainder, - priv->remainder_size + mb[i].size); - free(priv->remainder); - priv->remainder = NULL; - priv->remainder_size = 0; + strbuf_add(&priv->remainder, mb[i].ptr, mb[i].size); + consume_one(priv, priv->remainder.buf, priv->remainder.len); + strbuf_reset(&priv->remainder); } - if (priv->remainder) { - consume_one(priv, priv->remainder, priv->remainder_size); - free(priv->remainder); - priv->remainder = NULL; - priv->remainder_size = 0; + if (priv->remainder.len) { + consume_one(priv, priv->remainder.buf, priv->remainder.len); + strbuf_reset(&priv->remainder); } return 0; } @@ -148,7 +134,9 @@ int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, int ret; xecb->outf = xdiff_outf; xecb->priv = state; + strbuf_init(&state->remainder, 0); ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb); + strbuf_release(&state->remainder); return ret; } diff --git a/xdiff-interface.h b/xdiff-interface.h index 6f3b361a84..f6a1ec2220 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -2,6 +2,7 @@ #define XDIFF_INTERFACE_H #include "xdiff/xdiff.h" +#include "strbuf.h" struct xdiff_emit_state; @@ -9,8 +10,7 @@ typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long); struct xdiff_emit_state { xdiff_emit_consume_fn consume; - char *remainder; - unsigned long remainder_size; + struct strbuf remainder; }; int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); -- cgit v1.2.1 From 13613eac5bf886b37cc583462999419f91c98a17 Mon Sep 17 00:00:00 2001 From: Gustaf Hendeby Date: Wed, 13 Aug 2008 23:32:43 +0200 Subject: Update .gitignore to ignore git-help Signed-off-by: Gustaf Hendeby Signed-off-by: Junio C Hamano --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a213e8e25b..bbaf9de0bc 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ git-gc git-get-tar-commit-id git-grep git-hash-object +git-help git-http-fetch git-http-push git-imap-send -- cgit v1.2.1 From 8a3f524bf2ac534b313a7d8e70cc164cef744949 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 13 Aug 2008 23:18:22 -0700 Subject: xdiff-interface: hide the whole "xdiff_emit_state" business from the caller This further enhances xdi_diff_outf() interface so that it takes two common parameters: the callback function that processes one line at a time, and a pointer to its application specific callback data structure. xdi_diff_outf() creates its own "xdiff_emit_state" structure and stashes these two away inside it, which is used by the lowest level output function in the xdiff_outf() callchain, consume_one(), to call back to the application layer. With this restructuring, we lift the requirement that the caller supplied callback data structure embeds xdiff_emit_state structure as its first member. Signed-off-by: Junio C Hamano --- builtin-blame.c | 4 +--- combine-diff.c | 7 ++----- diff.c | 27 ++++++++++----------------- xdiff-interface.c | 23 ++++++++++++++++++----- xdiff-interface.h | 11 ++--------- 5 files changed, 33 insertions(+), 39 deletions(-) diff --git a/builtin-blame.c b/builtin-blame.c index 8cca3b16d2..e4d12de8a9 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -465,7 +465,6 @@ struct patch { }; struct blame_diff_state { - struct xdiff_emit_state xm; struct patch *ret; unsigned hunk_post_context; unsigned hunk_in_pre_context : 1; @@ -529,12 +528,11 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o, memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = context; memset(&state, 0, sizeof(state)); - state.xm.consume = process_u_diff; state.ret = xmalloc(sizeof(struct patch)); state.ret->chunks = NULL; state.ret->num = 0; - xdi_diff_outf(file_p, file_o, &state.xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(file_p, file_o, process_u_diff, &state, &xpp, &xecfg, &ecb); if (state.ret->num) { struct chunk *chunk; diff --git a/combine-diff.c b/combine-diff.c index 72dd6d2234..31ec0c5165 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -143,8 +143,6 @@ static void append_lost(struct sline *sline, int n, const char *line, int len) } struct combine_diff_state { - struct xdiff_emit_state xm; - unsigned int lno; int ob, on, nb, nn; unsigned long nmask; @@ -218,15 +216,14 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file, xpp.flags = XDF_NEED_MINIMAL; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); - state.xm.consume = consume_line; state.nmask = nmask; state.sline = sline; state.lno = 1; state.num_parent = num_parent; state.n = n; - xdi_diff_outf(&parent_file, result_file, - &state.xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(&parent_file, result_file, consume_line, &state, + &xpp, &xecfg, &ecb); free(parent_file.ptr); /* Assign line numbers for this parent. diff --git a/diff.c b/diff.c index 913a92f3e9..a6c143251c 100644 --- a/diff.c +++ b/diff.c @@ -369,7 +369,6 @@ static void diff_words_append(char *line, unsigned long len, } struct diff_words_data { - struct xdiff_emit_state xm; struct diff_words_buffer minus, plus; FILE *file; }; @@ -459,9 +458,8 @@ static void diff_words_show(struct diff_words_data *diff_words) xpp.flags = XDF_NEED_MINIMAL; xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; - diff_words->xm.consume = fn_out_diff_words_aux; - xdi_diff_outf(&minus, &plus, &diff_words->xm, &xpp, &xecfg, &ecb); - + xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, + &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); diff_words->minus.text.size = diff_words->plus.text.size = 0; @@ -475,7 +473,6 @@ static void diff_words_show(struct diff_words_data *diff_words) typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); struct emit_callback { - struct xdiff_emit_state xm; int nparents, color_diff; unsigned ws_rule; sane_truncate_fn truncate; @@ -706,8 +703,6 @@ static char *pprint_rename(const char *a, const char *b) } struct diffstat_t { - struct xdiff_emit_state xm; - int nr; int alloc; struct diffstat_file { @@ -1129,7 +1124,6 @@ static void free_diffstat_info(struct diffstat_t *diffstat) } struct checkdiff_t { - struct xdiff_emit_state xm; const char *filename; int lineno; struct diff_options *o; @@ -1519,13 +1513,13 @@ static void builtin_diff(const char *name_a, xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10); else if (!prefixcmp(diffopts, "-u")) xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10); - ecbdata.xm.consume = fn_out_consume; if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) { ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); ecbdata.diff_words->file = o->file; } - xdi_diff_outf(&mf1, &mf2, &ecbdata.xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, + &xpp, &xecfg, &ecb); if (DIFF_OPT_TST(o, COLOR_DIFF_WORDS)) free_diff_words_data(&ecbdata); } @@ -1576,7 +1570,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; - xdi_diff_outf(&mf1, &mf2, &diffstat->xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat, + &xpp, &xecfg, &ecb); } free_and_return: @@ -1597,7 +1592,6 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, return; memset(&data, 0, sizeof(data)); - data.xm.consume = checkdiff_consume; data.filename = name_b ? name_b : name_a; data.lineno = 0; data.o = o; @@ -1622,7 +1616,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, memset(&xecfg, 0, sizeof(xecfg)); xpp.flags = XDF_NEED_MINIMAL; - xdi_diff_outf(&mf1, &mf2, &data.xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data, + &xpp, &xecfg, &ecb); if ((data.ws_rule & WS_TRAILING_SPACE) && data.trailing_blanks_start) { @@ -3010,7 +3005,6 @@ static void diff_summary(FILE *file, struct diff_filepair *p) } struct patch_id_t { - struct xdiff_emit_state xm; SHA_CTX *ctx; int patchlen; }; @@ -3055,7 +3049,6 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) SHA1_Init(&ctx); memset(&data, 0, sizeof(struct patch_id_t)); data.ctx = &ctx; - data.xm.consume = patch_id_consume; for (i = 0; i < q->nr; i++) { xpparam_t xpp; @@ -3120,7 +3113,8 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1) xpp.flags = XDF_NEED_MINIMAL; xecfg.ctxlen = 3; xecfg.flags = XDL_EMIT_FUNCNAMES; - xdi_diff_outf(&mf1, &mf2, &data.xm, &xpp, &xecfg, &ecb); + xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data, + &xpp, &xecfg, &ecb); } SHA1_Final(sha1, &ctx); @@ -3197,7 +3191,6 @@ void diff_flush(struct diff_options *options) struct diffstat_t diffstat; memset(&diffstat, 0, sizeof(struct diffstat_t)); - diffstat.xm.consume = diffstat_consume; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) diff --git a/xdiff-interface.c b/xdiff-interface.c index bf98866c4f..944ad9887f 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -1,5 +1,12 @@ #include "cache.h" #include "xdiff-interface.h" +#include "strbuf.h" + +struct xdiff_emit_state { + xdiff_emit_consume_fn consume; + void *consume_callback_data; + struct strbuf remainder; +}; static int parse_num(char **cp_p, int *num_p) { @@ -55,7 +62,7 @@ static void consume_one(void *priv_, char *s, unsigned long size) unsigned long this_size; ep = memchr(s, '\n', size); this_size = (ep == NULL) ? size : (ep - s + 1); - priv->consume(priv, s, this_size); + priv->consume(priv->consume_callback_data, s, this_size); size -= this_size; s += this_size; } @@ -128,15 +135,21 @@ int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t co } int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, - struct xdiff_emit_state *state, xpparam_t const *xpp, + xdiff_emit_consume_fn fn, void *consume_callback_data, + xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *xecb) { int ret; + struct xdiff_emit_state state; + + memset(&state, 0, sizeof(state)); + state.consume = fn; + state.consume_callback_data = consume_callback_data; xecb->outf = xdiff_outf; - xecb->priv = state; - strbuf_init(&state->remainder, 0); + xecb->priv = &state; + strbuf_init(&state.remainder, 0); ret = xdi_diff(mf1, mf2, xpp, xecfg, xecb); - strbuf_release(&state->remainder); + strbuf_release(&state.remainder); return ret; } diff --git a/xdiff-interface.h b/xdiff-interface.h index f6a1ec2220..558492ba38 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -2,20 +2,13 @@ #define XDIFF_INTERFACE_H #include "xdiff/xdiff.h" -#include "strbuf.h" - -struct xdiff_emit_state; typedef void (*xdiff_emit_consume_fn)(void *, char *, unsigned long); -struct xdiff_emit_state { - xdiff_emit_consume_fn consume; - struct strbuf remainder; -}; - int xdi_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, - struct xdiff_emit_state *state, xpparam_t const *xpp, + xdiff_emit_consume_fn fn, void *consume_callback_data, + xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *xecb); int parse_hunk_header(char *line, int len, int *ob, int *on, -- cgit v1.2.1 From faf0156b278d1a760362cda1d294a88be7608de4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Aug 2008 10:59:44 -0700 Subject: revision --simplify-merges: use decoration instead of commit->util field The users of revision walking machinery may want to use the util pointer for their own use. Use decoration to hold the data needed during merge simplification instead. Signed-off-by: Junio C Hamano --- revision.c | 49 +++++++++++++++++++++++++++++++++++++------------ revision.h | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/revision.c b/revision.c index 0aaa4c10b9..33cb207f28 100644 --- a/revision.c +++ b/revision.c @@ -1408,16 +1408,34 @@ static int remove_duplicate_parents(struct commit *commit) return surviving_parents; } -static struct commit_list **simplify_one(struct commit *commit, struct commit_list **tail) +struct merge_simplify_state { + struct commit *simplified; +}; + +static struct merge_simplify_state *locate_simplify_state(struct rev_info *revs, struct commit *commit) +{ + struct merge_simplify_state *st; + + st = lookup_decoration(&revs->merge_simplification, &commit->object); + if (!st) { + st = xcalloc(1, sizeof(*st)); + add_decoration(&revs->merge_simplification, &commit->object, st); + } + return st; +} + +static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail) { struct commit_list *p; + struct merge_simplify_state *st, *pst; int cnt; + st = locate_simplify_state(revs, commit); + /* - * We store which commit each one simplifies to in its util field. * Have we handled this one? */ - if (commit->util) + if (st->simplified) return tail; /* @@ -1426,7 +1444,7 @@ static struct commit_list **simplify_one(struct commit *commit, struct commit_li * anyway. */ if ((commit->object.flags & UNINTERESTING) || !commit->parents) { - commit->util = commit; + st->simplified = commit; return tail; } @@ -1435,7 +1453,8 @@ static struct commit_list **simplify_one(struct commit *commit, struct commit_li * Otherwise we are not ready to rewrite this one yet. */ for (cnt = 0, p = commit->parents; p; p = p->next) { - if (!p->item->util) { + pst = locate_simplify_state(revs, p->item); + if (!pst->simplified) { tail = &commit_list_insert(p->item, tail)->next; cnt++; } @@ -1446,8 +1465,10 @@ static struct commit_list **simplify_one(struct commit *commit, struct commit_li /* * Rewrite our list of parents. */ - for (p = commit->parents; p; p = p->next) - p->item = p->item->util; + for (p = commit->parents; p; p = p->next) { + pst = locate_simplify_state(revs, p->item); + p->item = pst->simplified; + } cnt = remove_duplicate_parents(commit); /* @@ -1482,9 +1503,11 @@ static struct commit_list **simplify_one(struct commit *commit, struct commit_li (commit->object.flags & UNINTERESTING) || !(commit->object.flags & TREESAME) || (1 < cnt)) - commit->util = commit; - else - commit->util = commit->parents->item->util; + st->simplified = commit; + else { + pst = locate_simplify_state(revs, commit->parents->item); + st->simplified = pst->simplified; + } return tail; } @@ -1508,7 +1531,7 @@ static void simplify_merges(struct rev_info *revs) struct commit_list *next = list->next; free(list); list = next; - tail = simplify_one(commit, tail); + tail = simplify_one(revs, commit, tail); } } @@ -1519,9 +1542,11 @@ static void simplify_merges(struct rev_info *revs) while (list) { struct commit *commit = list->item; struct commit_list *next = list->next; + struct merge_simplify_state *st; free(list); list = next; - if (commit->util == commit) + st = locate_simplify_state(revs, commit); + if (st->simplified == commit) tail = &commit_list_insert(commit, tail)->next; } } diff --git a/revision.h b/revision.h index dfa06b5210..765ef6c5e2 100644 --- a/revision.h +++ b/revision.h @@ -110,6 +110,7 @@ struct rev_info { struct reflog_walk_info *reflog_info; struct decoration children; + struct decoration merge_simplification; }; #define REV_TREE_SAME 0 -- cgit v1.2.1 From 16ce2e4c8f9664f8ec5ae2bffed34d093d83a4d3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 16 Aug 2008 23:02:08 -0700 Subject: index: future proof for "extended" index entries We do not have any more bits in the on-disk index flags word, but we would need to have more in the future. Use the last remaining bits as a signal to tell us that the index entry we are looking at is an extended one. Since we do not understand the extended format yet, we will just error out when we see it. Signed-off-by: Junio C Hamano --- cache.h | 1 + read-cache.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/cache.h b/cache.h index 2475de9fa8..7b5cc834ab 100644 --- a/cache.h +++ b/cache.h @@ -126,6 +126,7 @@ struct cache_entry { #define CE_NAMEMASK (0x0fff) #define CE_STAGEMASK (0x3000) +#define CE_EXTENDED (0x4000) #define CE_VALID (0x8000) #define CE_STAGESHIFT 12 diff --git a/read-cache.c b/read-cache.c index 2c03ec3069..f0ba224798 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1118,6 +1118,10 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en ce->ce_size = ntohl(ondisk->size); /* On-disk flags are just 16 bits */ ce->ce_flags = ntohs(ondisk->flags); + + /* For future extension: we do not understand this entry yet */ + if (ce->ce_flags & CE_EXTENDED) + die("Unknown index entry format"); hashcpy(ce->sha1, ondisk->sha1); len = ce->ce_flags & CE_NAMEMASK; -- cgit v1.2.1 From b259f09b181c6650253f2ab60f5375d3ff8e3872 Mon Sep 17 00:00:00 2001 From: Marek Zawirski Date: Sat, 16 Aug 2008 19:58:32 +0200 Subject: Make push more verbose about illegal combination of options It may be unclear that --all, --mirror, --tags and/or explicit refspecs are illegal combinations for git push. Git was silently failing in these cases, while we can complaint more properly about it. Signed-off-by: Marek Zawirski Signed-off-by: Junio C Hamano --- builtin-push.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index c1ed68d938..cc6666f75e 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -59,8 +59,17 @@ static int do_push(const char *repo, int flags) if (remote->mirror) flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); - if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec) - return -1; + if ((flags & TRANSPORT_PUSH_ALL) && refspec) { + if (!strcmp(*refspec, "refs/tags/*")) + return error("--all and --tags are incompatible"); + return error("--all can't be combined with refspecs"); + } + + if ((flags & TRANSPORT_PUSH_MIRROR) && refspec) { + if (!strcmp(*refspec, "refs/tags/*")) + return error("--mirror and --tags are incompatible"); + return error("--mirror can't be combined with refspecs"); + } if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) == (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) { -- cgit v1.2.1 From bfdbee98109c5ad2dbbc392e7eed1ae688acc039 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 8 Aug 2008 02:26:28 -0700 Subject: tests: use $TEST_DIRECTORY to refer to the t/ directory Many test scripts assumed that they will start in a 'trash' subdirectory that is a single level down from the t/ directory, and referred to their test vector files by asking for files like "../t9999/expect". This will break if we move the 'trash' subdirectory elsewhere. To solve this, we earlier introduced "$TEST_DIRECTORY" so that they can refer to t/ directory reliably. This finally makes all the tests use it to refer to the outside environment. With this patch, and a one-liner not included here (because it would contradict with what Dscho really wants to do): | diff --git a/t/test-lib.sh b/t/test-lib.sh | index 70ea7e0..60e69e4 100644 | --- a/t/test-lib.sh | +++ b/t/test-lib.sh | @@ -485,7 +485,7 @@ fi | . ../GIT-BUILD-OPTIONS | | # Test repository | -test="trash directory" | +test="trash directory/another level/yet another" | rm -fr "$test" || { | trap - exit | echo >&5 "FATAL: Cannot prepare test area" all the tests still pass, but we would want extra sets of eyeballs on this type of change to really make sure. [jc: with help from Stephan Beyer on http-push tests I do not run myself; credits for locating silly quoting errors go to Olivier Marin.] Signed-off-by: Junio C Hamano --- t/lib-httpd.sh | 2 +- t/t0022-crlf-rename.sh | 4 ++-- t/t1000-read-tree-m-3way.sh | 2 +- t/t3900-i18n-commit.sh | 18 +++++++------- t/t3901-i18n-patch.sh | 28 +++++++++++----------- t/t4000-diff-format.sh | 2 +- t/t4001-diff-rename.sh | 2 +- t/t4002-diff-basic.sh | 2 +- t/t4003-diff-rename-1.sh | 6 ++--- t/t4004-diff-rename-symlink.sh | 2 +- t/t4005-diff-rename-2.sh | 6 ++--- t/t4007-rename-3.sh | 4 ++-- t/t4008-diff-break-rewrite.sh | 6 ++--- t/t4009-diff-rename-4.sh | 6 ++--- t/t4010-diff-pathspec.sh | 2 +- t/t4011-diff-symlink.sh | 2 +- t/t4012-diff-binary.sh | 2 +- t/t4013-diff-various.sh | 2 +- t/t4015-diff-whitespace.sh | 2 +- t/t4020-diff-external.sh | 2 +- t/t4022-diff-rewrite.sh | 4 ++-- t/t4023-diff-rename-typechange.sh | 14 +++++------ t/t4027-diff-submodule.sh | 2 +- t/t4100-apply-stat.sh | 4 ++-- t/t4101-apply-nonl.sh | 7 +++--- t/t5100-mailinfo.sh | 23 +++++++++--------- t/t5515-fetch-merge-logic.sh | 4 ++-- t/t5540-http-push.sh | 2 +- t/t6002-rev-list-bisect.sh | 2 +- t/t6003-rev-list-topo-order.sh | 2 +- t/t6023-merge-file.sh | 2 +- t/t6027-merge-binary.sh | 2 +- t/t6101-rev-parse-parents.sh | 2 +- t/t6200-fmt-merge-msg.sh | 4 ++-- t/t7001-mv.sh | 4 ++-- t/t7004-tag.sh | 2 +- t/t7101-reset.sh | 10 ++++---- t/t7500-commit.sh | 41 +++++++++++++++++++++++--------- t/t8001-annotate.sh | 2 +- t/t8002-blame.sh | 2 +- t/t9110-git-svn-use-svm-props.sh | 2 +- t/t9111-git-svn-use-svnsync-props.sh | 2 +- t/t9115-git-svn-dcommit-funky-renames.sh | 2 +- t/t9121-git-svn-fetch-renamed-dir.sh | 2 +- t/t9200-git-cvsexportcommit.sh | 14 +++++------ t/t9300-fast-import.sh | 2 +- t/t9301-fast-export.sh | 2 +- t/t9500-gitweb-standalone-no-errors.sh | 16 ++++++------- t/t9700-perl-git.sh | 2 +- t/t9700/test.pl | 3 --- t/test-lib.sh | 2 +- 51 files changed, 152 insertions(+), 134 deletions(-) diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index dc473dfb53..6ac312b905 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -14,7 +14,7 @@ fi LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'} LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'} -TEST_PATH="$PWD"/../lib-httpd +TEST_PATH="$TEST_DIRECTORY"/lib-httpd HTTPD_ROOT_PATH="$PWD"/httpd HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www diff --git a/t/t0022-crlf-rename.sh b/t/t0022-crlf-rename.sh index 7d1ce2d056..f1e1d48869 100755 --- a/t/t0022-crlf-rename.sh +++ b/t/t0022-crlf-rename.sh @@ -6,13 +6,13 @@ test_description='ignore CR in CRLF sequence while computing similiarity' test_expect_success setup ' - cat ../t0022-crlf-rename.sh >sample && + cat "$TEST_DIRECTORY"/t0022-crlf-rename.sh >sample && git add sample && test_tick && git commit -m Initial && - sed -e "s/\$/ /" ../t0022-crlf-rename.sh >elpmas && + sed -e "s/\$/ /" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas && git add elpmas && rm -f sample && diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 807fb83af8..22ba7a5442 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -72,7 +72,7 @@ In addition: ' . ./test-lib.sh -. ../lib-read-tree-m-3way.sh +. "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh ################################################################ # Trivial "majority when 3 stages exist" merge plus #2ALT, #3ALT diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 883281dbd6..f4f41847f3 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -16,7 +16,7 @@ test_expect_success setup ' : >F && git add F && T=$(git write-tree) && - C=$(git commit-tree $T <../t3900/1-UTF-8.txt) && + C=$(git commit-tree $T <"$TEST_DIRECTORY"/t3900/1-UTF-8.txt) && git update-ref HEAD $C && git-tag C0 ' @@ -32,7 +32,7 @@ do git config i18n.commitencoding $H && git-checkout -b $H C0 && echo $H >F && - git-commit -a -F ../t3900/$H.txt + git-commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt ' done @@ -57,13 +57,13 @@ test_expect_success 'config to remove customization' ' ' test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' - compare_with ISO-8859-1 ../t3900/1-UTF-8.txt + compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt ' for H in EUCJP ISO-2022-JP do test_expect_success "$H should be shown in UTF-8 now" ' - compare_with '$H' ../t3900/2-UTF-8.txt + compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt ' done @@ -82,7 +82,7 @@ for H in ISO-8859-1 EUCJP ISO-2022-JP do test_expect_success "$H should be shown in itself now" ' git config i18n.commitencoding '$H' && - compare_with '$H' ../t3900/'$H'.txt + compare_with '$H' "$TEST_DIRECTORY"/t3900/'$H'.txt ' done @@ -91,13 +91,13 @@ test_expect_success 'config to tweak customization' ' ' test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' - compare_with ISO-8859-1 ../t3900/1-UTF-8.txt + compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt ' for H in EUCJP ISO-2022-JP do test_expect_success "$H should be shown in UTF-8 now" ' - compare_with '$H' ../t3900/2-UTF-8.txt + compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt ' done @@ -107,7 +107,7 @@ do for H in EUCJP ISO-2022-JP do test_expect_success "$H should be shown in $J now" ' - compare_with '$H' ../t3900/'$J'.txt + compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt ' done done @@ -115,7 +115,7 @@ done for H in ISO-8859-1 EUCJP ISO-2022-JP do test_expect_success "No conversion with $H" ' - compare_with "--encoding=none '$H'" ../t3900/'$H'.txt + compare_with "--encoding=none '$H'" "$TEST_DIRECTORY"/t3900/'$H'.txt ' done diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 235f372832..f68ff53051 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -35,7 +35,7 @@ test_expect_success setup ' # use UTF-8 in author and committer name to match the # i18n.commitencoding settings - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && test_tick && echo "$GIT_AUTHOR_NAME" >mine && @@ -57,7 +57,7 @@ test_expect_success setup ' # the second one on the side branch is ISO-8859-1 git config i18n.commitencoding ISO-8859-1 && # use author and committer name in ISO-8859-1 to match it. - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && test_tick && echo Yet another >theirs && git add theirs && @@ -101,7 +101,7 @@ test_expect_success 'rebase (U/U)' ' # The result will be committed by GIT_COMMITTER_NAME -- # we want UTF-8 encoded name. - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git checkout -b test && git-rebase master && @@ -111,7 +111,7 @@ test_expect_success 'rebase (U/U)' ' test_expect_success 'rebase (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard side && git-rebase master && @@ -123,7 +123,7 @@ test_expect_success 'rebase (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && git-rebase master && @@ -136,7 +136,7 @@ test_expect_success 'rebase (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding UTF-8 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && git-rebase master && @@ -149,7 +149,7 @@ test_expect_success 'cherry-pick(U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -164,7 +164,7 @@ test_expect_success 'cherry-pick(L/L)' ' git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -179,7 +179,7 @@ test_expect_success 'cherry-pick(U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard master && git cherry-pick side^ && @@ -195,7 +195,7 @@ test_expect_success 'cherry-pick(L/U)' ' git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding UTF-8 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard master && git cherry-pick side^ && @@ -208,7 +208,7 @@ test_expect_success 'cherry-pick(L/U)' ' test_expect_success 'rebase --merge (U/U)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding UTF-8 && - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard side && git-rebase --merge master && @@ -219,7 +219,7 @@ test_expect_success 'rebase --merge (U/U)' ' test_expect_success 'rebase --merge (U/L)' ' git config i18n.commitencoding UTF-8 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-utf8.txt && + . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard side && git-rebase --merge master && @@ -231,7 +231,7 @@ test_expect_success 'rebase --merge (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding ISO-8859-1 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && git-rebase --merge master && @@ -244,7 +244,7 @@ test_expect_success 'rebase --merge (L/U)' ' # to get ISO-8859-1 results. git config i18n.commitencoding ISO-8859-1 && git config i18n.logoutputencoding UTF-8 && - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && git-rebase --merge master && diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh index c44b27aeb2..6ddd46915d 100755 --- a/t/t4000-diff-format.sh +++ b/t/t4000-diff-format.sh @@ -7,7 +7,7 @@ test_description='Test built-in diff output engine. ' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh echo >path0 'Line 1 Line 2 diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index a32692417d..71bac83dd5 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -7,7 +7,7 @@ test_description='Test rename detection in diff engine. ' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh echo >path0 'Line 1 Line 2 diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index a4cfde6b29..56bd3c2be1 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -7,7 +7,7 @@ test_description='Test diff raw-output. ' . ./test-lib.sh -. ../lib-read-tree-m-3way.sh +. "$TEST_DIRECTORY"/lib-read-tree-m-3way.sh cat >.test-plain-OA <<\EOF :000000 100644 0000000000000000000000000000000000000000 ccba72ad3888a3520b39efcf780b9ee64167535d A AA diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh index 8b1f875286..c6130c4019 100755 --- a/t/t4003-diff-rename-1.sh +++ b/t/t4003-diff-rename-1.sh @@ -7,11 +7,11 @@ test_description='More rename detection ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ 'prepare reference tree' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -99,7 +99,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' # tree has COPYING and rezrov. work tree has COPYING and COPYING.1, diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index 3d25be7a67..b35af9b42d 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -10,7 +10,7 @@ copy of symbolic links, but should not produce rename/copy followed by an edit for them. ' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh test_expect_success \ 'prepare reference tree' \ diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 6630017312..1ba359d478 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -7,11 +7,11 @@ test_description='Same rename detection as t4003 but testing diff-raw. ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ 'prepare reference tree' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -71,7 +71,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' git diff-index -C --find-copies-harder $tree >current diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh index 104a4e1492..42072d724e 100755 --- a/t/t4007-rename-3.sh +++ b/t/t4007-rename-3.sh @@ -7,12 +7,12 @@ test_description='Rename interaction with pathspec. ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ 'prepare reference tree' \ 'mkdir path0 path1 && - cp ../../COPYING path0/COPYING && + cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && git update-index --add path0/COPYING && tree=$(git write-tree) && echo $tree' diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 26c2e4aa65..7e343a9cd1 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -22,12 +22,12 @@ four changes in total. Further, with -B and -M together, these should turn into two renames. ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ setup \ - 'cat ../../README >file0 && - cat ../../COPYING >file1 && + 'cat "$TEST_DIRECTORY"/../README >file0 && + cat "$TEST_DIRECTORY"/../COPYING >file1 && git update-index --add file0 file1 && tree=$(git write-tree) && echo "$tree"' diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh index d2b45e7b8f..de3f17478e 100755 --- a/t/t4009-diff-rename-4.sh +++ b/t/t4009-diff-rename-4.sh @@ -7,11 +7,11 @@ test_description='Same rename detection as t4003 but testing diff-raw -z. ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ 'prepare reference tree' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -78,7 +78,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat ../../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' git diff-index -z -C --find-copies-harder $tree >current diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index ad3d9e4845..9322298ecc 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -10,7 +10,7 @@ Prepare: path1/file1 ' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash test_expect_success \ setup \ diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index c6d13693ba..02efecae3a 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -7,7 +7,7 @@ test_description='Test diff of symlinks. ' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh cat > expected << EOF diff --git a/frotz b/frotz diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index eced1f30fb..69934991cb 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -12,7 +12,7 @@ test_expect_success 'prepare repository' \ 'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d && git update-index --add a b c d && echo git >a && - cat ../test4012.png >b && + cat "$TEST_DIRECTORY"/test4012.png >b && echo git >c && cat b b >d' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9337b81064..1a6b52234d 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -99,7 +99,7 @@ do test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` cnt=`expr $test_count + 1` pfx=`printf "%04d" $cnt` - expect="../t4013/diff.$test" + expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" test_expect_success "git $cmd" ' diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a27fccc8dc..6110566aaa 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -7,7 +7,7 @@ test_description='Test special whitespace in diff engine. ' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh # Ray Lehtiniemi's example diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 637b4e19d5..dfe3fbc74b 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -104,7 +104,7 @@ echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file test_expect_success 'force diff with "diff"' ' echo >.gitattributes "file diff" && git diff >actual && - test_cmp ../t4020/diff.NUL actual + test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual ' test_done diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh index bf996fc414..2a537a21e8 100755 --- a/t/t4022-diff-rewrite.sh +++ b/t/t4022-diff-rewrite.sh @@ -6,12 +6,12 @@ test_description='rewrite diff' test_expect_success setup ' - cat ../../COPYING >test && + cat "$TEST_DIRECTORY"/../COPYING >test && git add test && tr \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \ - <../../COPYING >test + <"$TEST_DIRECTORY"/../COPYING >test ' diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh index 4dbfc6e8b7..297ddb5a25 100755 --- a/t/t4023-diff-rename-typechange.sh +++ b/t/t4023-diff-rename-typechange.sh @@ -7,21 +7,21 @@ test_description='typechange rename detection' test_expect_success setup ' rm -f foo bar && - cat ../../COPYING >foo && + cat "$TEST_DIRECTORY"/../COPYING >foo && ln -s linklink bar && git add foo bar && git commit -a -m Initial && git tag one && rm -f foo bar && - cat ../../COPYING >bar && + cat "$TEST_DIRECTORY"/../COPYING >bar && ln -s linklink foo && git add foo bar && git commit -a -m Second && git tag two && rm -f foo bar && - cat ../../COPYING >foo && + cat "$TEST_DIRECTORY"/../COPYING >foo && git add foo && git commit -a -m Third && git tag three && @@ -35,15 +35,15 @@ test_expect_success setup ' # This is purely for sanity check rm -f foo bar && - cat ../../COPYING >foo && - cat ../../Makefile >bar && + cat "$TEST_DIRECTORY"/../COPYING >foo && + cat "$TEST_DIRECTORY"/../Makefile >bar && git add foo bar && git commit -a -m Fifth && git tag five && rm -f foo bar && - cat ../../Makefile >foo && - cat ../../COPYING >bar && + cat "$TEST_DIRECTORY"/../Makefile >foo && + cat "$TEST_DIRECTORY"/../COPYING >bar && git add foo bar && git commit -a -m Sixth && git tag six diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index ba6679c6e4..1c2edebb09 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -3,7 +3,7 @@ test_description='difference in submodules' . ./test-lib.sh -. ../diff-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh _z40=0000000000000000000000000000000000000000 test_expect_success setup ' diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh index e0c67740a5..9b433de836 100755 --- a/t/t4100-apply-stat.sh +++ b/t/t4100-apply-stat.sh @@ -17,13 +17,13 @@ do test_expect_success "$title" ' git apply --stat --summary \ <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current && - test_cmp ../t4100/t-apply-$num.expect current + test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current ' test_expect_success "$title with recount" ' sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" | git apply --recount --stat --summary >current && - test_cmp ../t4100/t-apply-$num.expect current + test_cmp "$TEST_DIRECTORY"/t4100/t-apply-$num.expect current ' done <<\EOF rename diff --git a/t/t4101-apply-nonl.sh b/t/t4101-apply-nonl.sh index da8abcf364..e3443d004d 100755 --- a/t/t4101-apply-nonl.sh +++ b/t/t4101-apply-nonl.sh @@ -21,9 +21,10 @@ do do test $i -eq $j && continue cat frotz.$i >frotz - test_expect_success \ - "apply diff between $i and $j" \ - "git apply <../t4101/diff.$i-$j && diff frotz.$j frotz" + test_expect_success "apply diff between $i and $j" ' + git apply <"$TEST_DIRECTORY"/t4101/diff.$i-$j && + test_cmp frotz.$j frotz + ' done done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 8dfaddda91..3b6c3a9d97 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -8,27 +8,28 @@ test_description='git mailinfo and git mailsplit test' . ./test-lib.sh test_expect_success 'split sample box' \ - 'git mailsplit -o. ../t5100/sample.mbox >last && + 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=`cat last` && echo total is $last && test `cat last` = 11' for mail in `echo 00*` do - test_expect_success "mailinfo $mail" \ - "git mailinfo -u msg$mail patch$mail <$mail >info$mail && + test_expect_success "mailinfo $mail" ' + git mailinfo -u msg$mail patch$mail <$mail >info$mail && echo msg && - diff ../t5100/msg$mail msg$mail && + test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail && echo patch && - diff ../t5100/patch$mail patch$mail && + test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail && echo info && - diff ../t5100/info$mail info$mail" + test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail + ' done test_expect_success 'respect NULs' ' - git mailsplit -d3 -o. ../t5100/nul-plain && - cmp ../t5100/nul-plain 001 && + git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain && + test_cmp "$TEST_DIRECTORY"/t5100/nul-plain 001 && (cat 001 | git mailinfo msg patch) && test 4 = $(wc -l < patch) @@ -36,10 +37,10 @@ test_expect_success 'respect NULs' ' test_expect_success 'Preserve NULs out of MIME encoded message' ' - git mailsplit -d5 -o. ../t5100/nul-b64.in && - cmp ../t5100/nul-b64.in 00001 && + git mailsplit -d5 -o. "$TEST_DIRECTORY"/t5100/nul-b64.in && + test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.in 00001 && git mailinfo msg patch <00001 && - cmp ../t5100/nul-b64.expect patch + test_cmp "$TEST_DIRECTORY"/t5100/nul-b64.expect patch ' diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh index 8becbc3f38..1f4608d8ba 100755 --- a/t/t5515-fetch-merge-logic.sh +++ b/t/t5515-fetch-merge-logic.sh @@ -131,9 +131,9 @@ do test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` cnt=`expr $test_count + 1` pfx=`printf "%04d" $cnt` - expect_f="../../t5515/fetch.$test" + expect_f="$TEST_DIRECTORY/t5515/fetch.$test" actual_f="$pfx-fetch.$test" - expect_r="../../t5515/refs.$test" + expect_r="$TEST_DIRECTORY/t5515/refs.$test" actual_r="$pfx-refs.$test" test_expect_success "$cmd" ' diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index b0d242e3ed..da9588645c 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -19,7 +19,7 @@ then exit fi -. ../lib-httpd.sh +. "$TEST_DIRECTORY"/lib-httpd.sh if ! start_httpd >&3 2>&4 then diff --git a/t/t6002-rev-list-bisect.sh b/t/t6002-rev-list-bisect.sh index 8f5de097ec..b4e8fbaa5e 100755 --- a/t/t6002-rev-list-bisect.sh +++ b/t/t6002-rev-list-bisect.sh @@ -5,7 +5,7 @@ test_description='Tests git rev-list --bisect functionality' . ./test-lib.sh -. ../t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions # usage: test_bisection max-diff bisect-option head ^prune... # diff --git a/t/t6003-rev-list-topo-order.sh b/t/t6003-rev-list-topo-order.sh index 5daa0be8cc..2c73f2da7b 100755 --- a/t/t6003-rev-list-topo-order.sh +++ b/t/t6003-rev-list-topo-order.sh @@ -6,7 +6,7 @@ test_description='Tests git rev-list --topo-order functionality' . ./test-lib.sh -. ../t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions list_duplicates() { diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index f674c48cab..42620e0732 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -136,7 +136,7 @@ test_expect_success "expected conflict markers" "test_cmp expect out" test_expect_success 'binary files cannot be merged' ' test_must_fail git merge-file -p \ - orig.txt ../test4012.png new1.txt 2> merge.err && + orig.txt "$TEST_DIRECTORY"/test4012.png new1.txt 2> merge.err && grep "Cannot merge binary files" merge.err ' diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh index 92ca1f0f8c..b519626ca0 100755 --- a/t/t6027-merge-binary.sh +++ b/t/t6027-merge-binary.sh @@ -6,7 +6,7 @@ test_description='ask merge-recursive to merge binary files' test_expect_success setup ' - cat ../test4012.png >m && + cat "$TEST_DIRECTORY"/test4012.png >m && git add m && git ls-files -s | sed -e "s/ 0 / 1 /" >E1 && test_tick && diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 919552a2fc..f105fab98e 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -6,7 +6,7 @@ test_description='Test git rev-parse with different parent options' . ./test-lib.sh -. ../t6000lib.sh # t6xxx specific functions +. "$TEST_DIRECTORY"/t6000lib.sh # t6xxx specific functions date >path0 git update-index --add path0 diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index bc74349416..8f5a06f7dd 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -83,13 +83,13 @@ test_expect_success 'merge-msg test #1' ' ' cat >expected <actual && test_cmp expected actual diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 910a28c7e2..78167980b3 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -6,7 +6,7 @@ test_description='git mv in subdirs' test_expect_success \ 'prepare reference tree' \ 'mkdir path0 path1 && - cp ../../COPYING path0/COPYING && + cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && git add path0/COPYING && git-commit -m add -a' @@ -40,7 +40,7 @@ test_expect_success \ test_expect_success \ 'adding another file' \ - 'cp ../../README path0/README && + 'cp "$TEST_DIRECTORY"/../README path0/README && git add path0/README && git-commit -m add2 -a' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 8d44c2ed1f..198244c57d 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -625,7 +625,7 @@ esac # Name and email: C O Mitter # No password given, to enable non-interactive operation. -cp -R ../t7004 ./gpghome +cp -R "$TEST_DIRECTORY"/t7004 ./gpghome chmod 0700 gpghome GNUPGHOME="$(pwd)/gpghome" export GNUPGHOME diff --git a/t/t7101-reset.sh b/t/t7101-reset.sh index 0d9874bfd7..ffaeb3983a 100755 --- a/t/t7101-reset.sh +++ b/t/t7101-reset.sh @@ -9,7 +9,7 @@ test_description='git-reset should cull empty subdirs' test_expect_success \ 'creating initial files' \ 'mkdir path0 && - cp ../../COPYING path0/COPYING && + cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && git add path0/COPYING && git-commit -m add -a' @@ -17,10 +17,10 @@ test_expect_success \ 'creating second files' \ 'mkdir path1 && mkdir path1/path2 && - cp ../../COPYING path1/path2/COPYING && - cp ../../COPYING path1/COPYING && - cp ../../COPYING COPYING && - cp ../../COPYING path0/COPYING-TOO && + cp "$TEST_DIRECTORY"/../COPYING path1/path2/COPYING && + cp "$TEST_DIRECTORY"/../COPYING path1/COPYING && + cp "$TEST_DIRECTORY"/../COPYING COPYING && + cp "$TEST_DIRECTORY"/../COPYING path0/COPYING-TOO && git add path1/path2/COPYING && git add path1/COPYING && git add COPYING && diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 809bdba630..7ae0bd0e31 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -46,15 +46,24 @@ test_expect_success 'unedited template with comments should not commit' ' ' test_expect_success 'a Signed-off-by line by itself should not commit' ' - ! GIT_EDITOR=../t7500/add-signed-off git commit --template "$TEMPLATE" + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-signed-off && + test_must_fail git commit --template "$TEMPLATE" + ) ' test_expect_success 'adding comments to a template should not commit' ' - ! GIT_EDITOR=../t7500/add-comments git commit --template "$TEMPLATE" + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-comments && + test_must_fail git commit --template "$TEMPLATE" + ) ' test_expect_success 'adding real content to a template should commit' ' - GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content && + git commit --template "$TEMPLATE" + ) && commit_msg_is "template linecommit message" ' @@ -62,7 +71,10 @@ test_expect_success '-t option should be short for --template' ' echo "short template" > "$TEMPLATE" && echo "new content" >> foo && git add foo && - GIT_EDITOR=../t7500/add-content git commit -t "$TEMPLATE" && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content && + git commit -t "$TEMPLATE" + ) && commit_msg_is "short templatecommit message" ' @@ -71,7 +83,10 @@ test_expect_success 'config-specified template should commit' ' git config commit.template "$TEMPLATE" && echo "more content" >> foo && git add foo && - GIT_EDITOR=../t7500/add-content git commit && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content && + git commit + ) && git config --unset commit.template && commit_msg_is "new templatecommit message" ' @@ -79,7 +94,7 @@ test_expect_success 'config-specified template should commit' ' test_expect_success 'explicit commit message should override template' ' echo "still more content" >> foo && git add foo && - GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" \ + GIT_EDITOR="$TEST_DIRECTORY"/t7500/add-content git commit --template "$TEMPLATE" \ -m "command line msg" && commit_msg_is "command line msg" ' @@ -88,8 +103,10 @@ test_expect_success 'commit message from file should override template' ' echo "content galore" >> foo && git add foo && echo "standard input msg" | - GIT_EDITOR=../t7500/add-content git commit \ - --template "$TEMPLATE" --file - && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content && + git commit --template "$TEMPLATE" --file - + ) && commit_msg_is "standard input msg" ' @@ -132,10 +149,12 @@ EOF test_expect_success '--signoff' ' echo "yet another content *narf*" >> foo && - echo "zort" | - GIT_EDITOR=../t7500/add-content git commit -s -F - foo && + echo "zort" | ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content && + git commit -s -F - foo + ) && git cat-file commit HEAD | sed "1,/^$/d" > output && - diff expect output + test_cmp expect output ' test_expect_success 'commit message from file (1)' ' diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh index eabec2e06e..45cb60ea4b 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -4,7 +4,7 @@ test_description='git annotate' . ./test-lib.sh PROG='git annotate' -. ../annotate-tests.sh +. "$TEST_DIRECTORY"/annotate-tests.sh test_expect_success \ 'Annotating an old revision works' \ diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index 92ece30fa9..597cf0486f 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -4,6 +4,6 @@ test_description='git blame' . ./test-lib.sh PROG='git blame -c' -. ../annotate-tests.sh +. "$TEST_DIRECTORY"/annotate-tests.sh test_done diff --git a/t/t9110-git-svn-use-svm-props.sh b/t/t9110-git-svn-use-svm-props.sh index 04d2a65c08..83bd1cf17a 100755 --- a/t/t9110-git-svn-use-svm-props.sh +++ b/t/t9110-git-svn-use-svm-props.sh @@ -8,7 +8,7 @@ test_description='git-svn useSvmProps test' . ./lib-git-svn.sh test_expect_success 'load svm repo' ' - svnadmin load -q "$rawsvnrepo" < ../t9110/svm.dump && + svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9110/svm.dump && git-svn init --minimize-url -R arr -i bar "$svnrepo"/mirror/arr && git-svn init --minimize-url -R argh -i dir "$svnrepo"/mirror/argh && git-svn init --minimize-url -R argh -i e \ diff --git a/t/t9111-git-svn-use-svnsync-props.sh b/t/t9111-git-svn-use-svnsync-props.sh index a8d74dcd3a..c5dfd61e41 100755 --- a/t/t9111-git-svn-use-svnsync-props.sh +++ b/t/t9111-git-svn-use-svnsync-props.sh @@ -8,7 +8,7 @@ test_description='git-svn useSvnsyncProps test' . ./lib-git-svn.sh test_expect_success 'load svnsync repo' ' - svnadmin load -q "$rawsvnrepo" < ../t9111/svnsync.dump && + svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9111/svnsync.dump && git-svn init --minimize-url -R arr -i bar "$svnrepo"/bar && git-svn init --minimize-url -R argh -i dir "$svnrepo"/dir && git-svn init --minimize-url -R argh -i e "$svnrepo"/dir/a/b/c/d/e && diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh index f0fbd3aff7..b0ba1f0200 100755 --- a/t/t9115-git-svn-dcommit-funky-renames.sh +++ b/t/t9115-git-svn-dcommit-funky-renames.sh @@ -8,7 +8,7 @@ test_description='git-svn dcommit can commit renames of files with ugly names' . ./lib-git-svn.sh test_expect_success 'load repository with strange names' ' - svnadmin load -q "$rawsvnrepo" < ../t9115/funky-names.dump && + svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9115/funky-names.dump && start_httpd gtk+ ' diff --git a/t/t9121-git-svn-fetch-renamed-dir.sh b/t/t9121-git-svn-fetch-renamed-dir.sh index 99230b0810..92e69a2a75 100755 --- a/t/t9121-git-svn-fetch-renamed-dir.sh +++ b/t/t9121-git-svn-fetch-renamed-dir.sh @@ -8,7 +8,7 @@ test_description='git-svn can fetch renamed directories' . ./lib-git-svn.sh test_expect_success 'load repository with renamed directory' ' - svnadmin load -q "$rawsvnrepo" < ../t9121/renamed-dir.dump + svnadmin load -q "$rawsvnrepo" < "$TEST_DIRECTORY"/t9121/renamed-dir.dump ' test_expect_success 'init and fetch repository' ' diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 3e32e84e6c..988c3ac754 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -45,8 +45,8 @@ test_expect_success \ 'mkdir A B C D E F && echo hello1 >A/newfile1.txt && echo hello2 >B/newfile2.txt && - cp ../test9200a.png C/newfile3.png && - cp ../test9200a.png D/newfile4.png && + cp "$TEST_DIRECTORY"/test9200a.png C/newfile3.png && + cp "$TEST_DIRECTORY"/test9200a.png D/newfile4.png && git add A/newfile1.txt && git add B/newfile2.txt && git add C/newfile3.png && @@ -71,8 +71,8 @@ test_expect_success \ rm -f B/newfile2.txt && rm -f C/newfile3.png && echo Hello5 >E/newfile5.txt && - cp ../test9200b.png D/newfile4.png && - cp ../test9200a.png F/newfile6.png && + cp "$TEST_DIRECTORY"/test9200b.png D/newfile4.png && + cp "$TEST_DIRECTORY"/test9200a.png F/newfile6.png && git add E/newfile5.txt && git add F/newfile6.png && git commit -a -m "Test: Remove, add and update" && @@ -160,7 +160,7 @@ test_expect_success \ 'mkdir "G g" && echo ok then >"G g/with spaces.txt" && git add "G g/with spaces.txt" && \ - cp ../test9200a.png "G g/with spaces.png" && \ + cp "$TEST_DIRECTORY"/test9200a.png "G g/with spaces.png" && \ git add "G g/with spaces.png" && git commit -a -m "With spaces" && id=$(git rev-list --max-count=1 HEAD) && @@ -172,7 +172,7 @@ test_expect_success \ test_expect_success \ 'Update file with spaces in file name' \ 'echo Ok then >>"G g/with spaces.txt" && - cat ../test9200a.png >>"G g/with spaces.png" && \ + cat "$TEST_DIRECTORY"/test9200a.png >>"G g/with spaces.png" && \ git add "G g/with spaces.png" && git commit -a -m "Update with spaces" && id=$(git rev-list --max-count=1 HEAD) && @@ -197,7 +197,7 @@ test_expect_success \ 'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö && echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && - cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + cp "$TEST_DIRECTORY"/test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && git commit -a -m "Går det så går det" && \ id=$(git rev-list --max-count=1 HEAD) && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index c6bc0a607f..bd5d5af661 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -5,7 +5,7 @@ test_description='test git-fast-import utility' . ./test-lib.sh -. ../diff-lib.sh ;# test-lib chdir's into trash +. "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash file2_data='file2 second line of EOF' diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh index c19b4a2bab..3cb9f80708 100755 --- a/t/t9301-fast-export.sh +++ b/t/t9301-fast-export.sh @@ -67,7 +67,7 @@ test_expect_success 'iso-8859-1' ' git config i18n.commitencoding ISO-8859-1 && # use author and committer name in ISO-8859-1 to match it. - . ../t3901-8859-1.txt && + . "$TEST_DIRECTORY"/t3901-8859-1.txt && test_tick && echo rosten >file && git commit -s -m den file && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index ae7082be1d..46ba19be7d 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -25,9 +25,9 @@ our \$site_name = "[localhost]"; our \$site_header = ""; our \$site_footer = ""; our \$home_text = "indextext.html"; -our @stylesheets = ("file:///$safe_pwd/../../gitweb/gitweb.css"); -our \$logo = "file:///$safe_pwd/../../gitweb/git-logo.png"; -our \$favicon = "file:///$safe_pwd/../../gitweb/git-favicon.png"; +our @stylesheets = ("file:///$TEST_DIRECTORY/../gitweb/gitweb.css"); +our \$logo = "file:///$TEST_DIRECTORY/../gitweb/git-logo.png"; +our \$favicon = "file:///$TEST_DIRECTORY/../gitweb/git-favicon.png"; our \$projects_list = ""; our \$export_ok = ""; our \$strict_export = ""; @@ -54,7 +54,7 @@ gitweb_run () { # written to web server logs, so we are not interested in that: # we are interested only in properly formatted errors/warnings rm -f gitweb.log && - perl -- "$(pwd)/../../gitweb/gitweb.perl" \ + perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \ >/dev/null 2>gitweb.log && if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi @@ -525,20 +525,20 @@ test_debug 'cat gitweb.log' test_expect_success \ 'encode(commit): utf8' \ - '. ../t3901-utf8.txt && + '. "$TEST_DIRECTORY"/t3901-utf8.txt && echo "UTF-8" >> file && git add file && - git commit -F ../t3900/1-UTF-8.txt && + git commit -F "$TEST_DIRECTORY"/t3900/1-UTF-8.txt && gitweb_run "p=.git;a=commit"' test_debug 'cat gitweb.log' test_expect_success \ 'encode(commit): iso-8859-1' \ - '. ../t3901-8859-1.txt && + '. "$TEST_DIRECTORY"/t3901-8859-1.txt && echo "ISO-8859-1" >> file && git add file && git config i18n.commitencoding ISO-8859-1 && - git commit -F ../t3900/ISO-8859-1.txt && + git commit -F "$TEST_DIRECTORY"/t3900/ISO-8859-1.txt && git config --unset i18n.commitencoding && gitweb_run "p=.git;a=commit"' test_debug 'cat gitweb.log' diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index 9706ee5773..0f04ba094b 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -39,6 +39,6 @@ test_expect_success \ test_external_without_stderr \ 'Perl API' \ - perl ../t9700/test.pl + perl "$TEST_DIRECTORY"/t9700/test.pl test_done diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 4d2312548a..851cea4a4b 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -14,10 +14,7 @@ use File::Temp; BEGIN { use_ok('Git') } # set up -our $repo_dir = "trash directory"; our $abs_repo_dir = Cwd->cwd; -die "this must be run by calling the t/t97* shell script(s)\n" - if basename(Cwd->cwd) ne $repo_dir; ok(our $r = Git->repository(Directory => "."), "open repository"); # config diff --git a/t/test-lib.sh b/t/test-lib.sh index 11c027571b..70ea7e089e 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -406,7 +406,7 @@ test_create_repo () { error "bug in the test script: not 1 parameter to test-create-repo" owd=`pwd` repo="$1" - mkdir "$repo" + mkdir -p "$repo" cd "$repo" || error "Cannot setup test environment" "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 || error "cannot run git init -- have you built things yet?" -- cgit v1.2.1 From f223824943e23e593b5e9cc647417a4267acc82d Mon Sep 17 00:00:00 2001 From: Marcus Griep Date: Fri, 15 Aug 2008 00:20:20 -0400 Subject: count-objects: Add total pack size to verbose output Adds the total pack size (including indexes) the verbose count-objects output, floored to the nearest kilobyte. Updates documentation to match this addition. Signed-off-by: Marcus Griep Signed-off-by: Junio C Hamano --- Documentation/git-count-objects.txt | 5 +++-- builtin-count-objects.c | 3 +++ t/t5500-fetch-pack.sh | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt index 75a8da1ca9..6bc1c21e62 100644 --- a/Documentation/git-count-objects.txt +++ b/Documentation/git-count-objects.txt @@ -21,8 +21,9 @@ OPTIONS --verbose:: In addition to the number of loose objects and disk space consumed, it reports the number of in-pack - objects, number of packs, and number of objects that can be - removed by running `git prune-packed`. + objects, number of packs, disk space consumed by those packs, + and number of objects that can be removed by running + `git prune-packed`. Author diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 91b5487478..249040b96a 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -104,6 +104,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) if (verbose) { struct packed_git *p; unsigned long num_pack = 0; + unsigned long size_pack = 0; if (!packed_git) prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -112,12 +113,14 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) if (open_pack_index(p)) continue; packed += p->num_objects; + size_pack += p->pack_size + p->index_size; num_pack++; } printf("count: %lu\n", loose); printf("size: %lu\n", loose_size / 2); printf("in-pack: %lu\n", packed); printf("packs: %lu\n", num_pack); + printf("size-pack: %lu\n", size_pack / 1024); printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); } diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 362cf7e928..7125baebb8 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -137,7 +137,7 @@ test_expect_success "clone shallow object count" \ "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\"" count_output () { - sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/: 0$/d' "$1" + sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/^size-pack:/d' -e '/: 0$/d' "$1" } test_expect_success "clone shallow object count (part 2)" ' -- cgit v1.2.1 From dba9194a49452b5f093b96872e19c91b50e526aa Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Aug 2008 15:44:11 -0700 Subject: Start 1.6.0.X maintenance series Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.0.1.txt | 15 +++++++++++++++ RelNotes | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Documentation/RelNotes-1.6.0.1.txt diff --git a/Documentation/RelNotes-1.6.0.1.txt b/Documentation/RelNotes-1.6.0.1.txt new file mode 100644 index 0000000000..3ee85a7993 --- /dev/null +++ b/Documentation/RelNotes-1.6.0.1.txt @@ -0,0 +1,15 @@ +GIT v1.6.0.1 Release Notes +========================== + +Fixes since v1.6.0 +------------------ + +* ... + +Contains other various documentation fixes. + +-- +exec >/var/tmp/1 +O=v1.6.0 +echo O=$(git describe maint) +git shortlog --no-merges $O..maint diff --git a/RelNotes b/RelNotes index b9a53c3416..8f32997335 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.0.txt \ No newline at end of file +Documentation/RelNotes-1.6.0.1.txt \ No newline at end of file -- cgit v1.2.1 From 2ebc02d32a4360da2cf69c2b5f5bfad0716d42b0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 17 Aug 2008 15:48:51 -0700 Subject: Start 1.6.1 cycle Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.1.txt | 42 ++++++++++++++++++++++++++++++++++++++++ RelNotes | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 Documentation/RelNotes-1.6.1.txt diff --git a/Documentation/RelNotes-1.6.1.txt b/Documentation/RelNotes-1.6.1.txt new file mode 100644 index 0000000000..efaf9ac4f7 --- /dev/null +++ b/Documentation/RelNotes-1.6.1.txt @@ -0,0 +1,42 @@ +GIT v1.6.1 Release Notes +======================== + +Updates since v1.6.0 +-------------------- + +(subsystems) + +* ... + +(portability) + +* ... + +(documentation) + +* ... + +(performance) + +* ... + +(usability, bells and whistles) + +* ... + +(internal) + +* ... + + +Fixes since v1.6.0 +------------------ + +All of the fixes in v1.6.0.X maintenance series are included in this +release, unless otherwise noted. + +-- +exec >/var/tmp/1 +O=v1.6.0 +echo O=$(git describe master) +git shortlog --no-merges $O..master ^maint diff --git a/RelNotes b/RelNotes index 8f32997335..3d420845b1 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.0.1.txt \ No newline at end of file +Documentation/RelNotes-1.6.1.txt \ No newline at end of file -- cgit v1.2.1 From 90fb46ec83cd837364592ce4d95055a3b6fe89a4 Mon Sep 17 00:00:00 2001 From: Pieter de Bie Date: Sun, 10 Aug 2008 22:22:21 +0200 Subject: builtin-reflog: Allow reflog expire to name partial ref This allows you to specify 'git reflog expire master' without needing to give the full refname like 'git reflog expire refs/heads/master' Signed-off-by: Pieter de Bie Signed-off-by: Junio C Hamano --- builtin-reflog.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin-reflog.c b/builtin-reflog.c index 196fa03b7f..6b3667ef0e 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -540,11 +540,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) free(collected.e); } - while (i < argc) { - const char *ref = argv[i++]; + for (; i < argc; i++) { + char *ref; unsigned char sha1[20]; - if (!resolve_ref(ref, sha1, 1, NULL)) { - status |= error("%s points nowhere!", ref); + if (!dwim_log(argv[i], strlen(argv[i]), sha1, &ref)) { + status |= error("%s points nowhere!", argv[i]); continue; } set_reflog_expiry_param(&cb, explicit_expiry, ref); -- cgit v1.2.1 From 036d17feda327c509c712dd1054a12d067166667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Valdemar=20M=C3=B8rch?= Date: Mon, 11 Aug 2008 08:46:24 +0200 Subject: Teach git log --check to return an appropriate exit code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Valdemar Mørch Signed-off-by: Junio C Hamano --- builtin-log.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/builtin-log.c b/builtin-log.c index f4975cf35f..ae71540546 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -217,6 +217,11 @@ static int cmd_log_walk(struct rev_info *rev) if (rev->early_output) finish_early_output(rev); + /* + * For --check, the exit code is based on CHECK_FAILED being + * accumulated in rev->diffopt, so be careful to retain that state + * information if replacing rev->diffopt in this loop + */ while ((commit = get_revision(rev)) != NULL) { log_tree_commit(rev, commit); if (!rev->reflog_info) { @@ -227,6 +232,10 @@ static int cmd_log_walk(struct rev_info *rev) free_commit_list(commit->parents); commit->parents = NULL; } + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && + DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { + return 02; + } return 0; } -- cgit v1.2.1 From 84102a338df08a365ed0336304322adc05bc1581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Valdemar=20M=C3=B8rch?= Date: Mon, 11 Aug 2008 08:46:25 +0200 Subject: Teach git log --exit-code to return an appropriate exit code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Peter Valdemar Mørch Signed-off-by: Junio C Hamano --- builtin-log.c | 8 ++++---- log-tree.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin-log.c b/builtin-log.c index ae71540546..3a79574830 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -218,9 +218,9 @@ static int cmd_log_walk(struct rev_info *rev) finish_early_output(rev); /* - * For --check, the exit code is based on CHECK_FAILED being - * accumulated in rev->diffopt, so be careful to retain that state - * information if replacing rev->diffopt in this loop + * For --check and --exit-code, the exit code is based on CHECK_FAILED + * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to + * retain that state information if replacing rev->diffopt in this loop */ while ((commit = get_revision(rev)) != NULL) { log_tree_commit(rev, commit); @@ -236,7 +236,7 @@ static int cmd_log_walk(struct rev_info *rev) DIFF_OPT_TST(&rev->diffopt, CHECK_FAILED)) { return 02; } - return 0; + return diff_result_code(&rev->diffopt, 0); } static int git_log_config(const char *var, const char *value, void *cb) diff --git a/log-tree.c b/log-tree.c index bd8b9e45ab..30cd5bb228 100644 --- a/log-tree.c +++ b/log-tree.c @@ -432,7 +432,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log struct commit_list *parents; unsigned const char *sha1 = commit->object.sha1; - if (!opt->diff) + if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS)) return 0; /* Root commit? */ -- cgit v1.2.1 From ffa9fd957039902c9a08946d82ccb729a34ed68b Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Tue, 12 Aug 2008 00:35:11 +0200 Subject: Fix commit_tree() buffer leak The commit_tree() strbuf has a minimum size of 8k and it has not been released yet. This patch releases the buffer. Signed-off-by: Stephan Beyer Signed-off-by: Junio C Hamano --- builtin-commit-tree.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 7a9a309be0..f773db596c 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -48,6 +48,7 @@ static const char commit_utf8_warn[] = int commit_tree(const char *msg, unsigned char *tree, struct commit_list *parents, unsigned char *ret) { + int result; int encoding_is_utf8; struct strbuf buffer; @@ -86,7 +87,9 @@ int commit_tree(const char *msg, unsigned char *tree, if (encoding_is_utf8 && !is_utf8(buffer.buf)) fprintf(stderr, commit_utf8_warn); - return write_sha1_file(buffer.buf, buffer.len, commit_type, ret); + result = write_sha1_file(buffer.buf, buffer.len, commit_type, ret); + strbuf_release(&buffer); + return result; } int cmd_commit_tree(int argc, const char **argv, const char *prefix) -- cgit v1.2.1 From 19a31f9c1a6b18abd8a7f20d616516afca36a6a3 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Sun, 10 Aug 2008 19:10:04 -0400 Subject: git-submodule - Add 'foreach' subcommand submodule foreach will execute the list of commands in each currently checked out submodule directory. The list of commands is arbitrary as long as it is acceptable to sh. The variables '$path' and '$sha1' are availble to the command-list, defining the submodule path relative to the superproject and the submodules's commitID as recorded in the superproject (this may be different than HEAD in the submodule). This utility is inspired by a number of threads on the mailing list looking for ways to better integrate submodules in a tree and work with them as a unit. This could include fetching a new branch in each from a given source, or possibly checking out a given named branch in each. Currently, there is no consensus as to what additional commands should be implemented in the porcelain, requiring all users whose needs exceed that of git-submodule to do their own scripting. The foreach command is intended to support such scripting, and in particular does no error checking and produces no output, thus allowing end users complete control over any information printed out and over what constitutes an error. The processing does terminate if the command-list returns an error, but processing can easily be forced for all submodules be terminating the list with ';true'. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 17 +++++++++++++++++ git-submodule.sh | 24 ++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index bf33b0cba0..abbd5b72de 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -14,6 +14,7 @@ SYNOPSIS 'git submodule' [--quiet] init [--] [...] 'git submodule' [--quiet] update [--init] [--] [...] 'git submodule' [--quiet] summary [--summary-limit ] [commit] [--] [...] +'git submodule' [--quiet] foreach DESCRIPTION @@ -123,6 +124,22 @@ summary:: in the submodule between the given super project commit and the index or working tree (switched by --cached) are shown. +foreach:: + Evaluates an arbitrary shell command in each checked out submodule. + The command has access to the variables $path and $sha1: + $path is the name of the submodule directory relative to the + superproject, and $sha1 is the commit as recorded in the superproject. + Any submodules defined in the superproject but not checked out are + ignored by this command. Unless given --quiet, foreach prints the name + of each submodule before evaluating the command. + A non-zero return from the command in any submodule causes + the processing to terminate. This can be overridden by adding '|| :' + to the end of the command. ++ +As an example, "git submodule foreach 'echo $path `git rev-parse HEAD`' will +show the path and currently checked out commit for each submodule. + + OPTIONS ------- -q:: diff --git a/git-submodule.sh b/git-submodule.sh index b40f876a2c..2d57d60458 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -6,7 +6,7 @@ USAGE="[--quiet] [--cached] \ [add [-b branch] ]|[status|init|update [-i|--init]|summary [-n|--summary-limit ] []] \ -[--] [...]" +[--] [...]|[foreach ]" OPTIONS_SPEC= . git-sh-setup require_work_tree @@ -198,6 +198,26 @@ cmd_add() die "Failed to register submodule '$path'" } +# +# Execute an arbitrary command sequence in each checked out +# submodule +# +# $@ = command to execute +# +cmd_foreach() +{ + git ls-files --stage | grep '^160000 ' | + while read mode sha1 stage path + do + if test -e "$path"/.git + then + say "Entering '$path'" + (cd "$path" && eval "$@") || + die "Stopping at '$path'; script returned non-zero status." + fi + done +} + # # Register submodules in .git/config # @@ -583,7 +603,7 @@ cmd_status() while test $# != 0 && test -z "$command" do case "$1" in - add | init | update | status | summary) + add | foreach | init | update | status | summary) command=$1 ;; -q|--quiet) -- cgit v1.2.1 From 9f2b6d2936a7c4bb3155de8efec7b10869ca935e Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 16 Aug 2008 21:25:40 -0700 Subject: date/time: do not get confused by fractional seconds The date/time parsing code was confused if the input time HH:MM:SS is followed by fractional seconds. Since we do not record anything finer grained than seconds, we could just drop fractional part, but there is a twist. We have taught people that not just spaces but dot can be used as word separators when spelling things like: $ git log --since 2.days $ git show @{12:34:56.7.days.ago} and we shouldn't mistake "7" in the latter example as a fraction and discard it. The rules are: - valid days of month/mday are always single or double digits. - valid years are either two or four digits No, we don't support the year 600 _anyway_, since our encoding is based on the UNIX epoch, and the day we worry about the year 10,000 is far away and we can raise the limit to five digits when we get closer. - Other numbers (eg "600 days ago") can have any number of digits, but they cannot start with a zero. Again, the only exception is for two-digit numbers, since that is fairly common for dates ("Dec 01" is not unheard of) So that means that any milli- or micro-second would be thrown out just because the number of digits shows that it cannot be an interesting date. A milli- or micro-second can obviously be a perfectly fine number according to the rules above, as long as it doesn't start with a '0'. So if we have 12:34:56.123 then that '123' gets parsed as a number, and we remember it. But because it's bigger than 31, we'll never use it as such _unless_ there is something after it to trigger that use. So you can say "12:34:56.123.days.ago", and because of the "days", that 123 will actually be meaninful now. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- date.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/date.c b/date.c index 35a52576c5..950b88fdcf 100644 --- a/date.c +++ b/date.c @@ -402,6 +402,15 @@ static int match_multi_number(unsigned long num, char c, const char *date, char return end - date; } +/* Have we filled in any part of the time/date yet? */ +static inline int nodate(struct tm *tm) +{ + return tm->tm_year < 0 && + tm->tm_mon < 0 && + tm->tm_mday < 0 && + !(tm->tm_hour | tm->tm_min | tm->tm_sec); +} + /* * We've seen a digit. Time? Year? Date? */ @@ -418,7 +427,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt * more than 8 digits. This is because we don't want to rule out * numbers like 20070606 as a YYYYMMDD date. */ - if (num >= 100000000) { + if (num >= 100000000 && nodate(tm)) { time_t time = num; if (gmtime_r(&time, tm)) { *tm_gmt = 1; @@ -462,6 +471,13 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt return n; } + /* + * Ignore lots of numerals. We took care of 4-digit years above. + * Days or months must be one or two digits. + */ + if (n > 2) + return n; + /* * NOTE! We will give precedence to day-of-month over month or * year numbers in the 1-12 range. So 05 is always "mday 5", @@ -488,10 +504,6 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt if (num > 0 && num < 32) { tm->tm_mday = num; - } else if (num > 1900) { - tm->tm_year = num - 1900; - } else if (num > 70) { - tm->tm_year = num; } else if (num > 0 && num < 13) { tm->tm_mon = num-1; } @@ -823,7 +835,9 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) } } - *num = number; + /* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */ + if (date[0] != '0' || end - date <= 2) + *num = number; return end; } -- cgit v1.2.1 From f5b904db6b02e0ded76732eabe106069a87859fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 16 Aug 2008 21:56:24 -0700 Subject: Makefile: Allow CC_LD_DYNPATH to be overriden Current Makefile does not allow config.mak to override CC_LD_DYNPATH; it only lets it affect indirectly via NO_R_TO_GCC_LINKER. If the command line, config.mak or config.mak.autogen wants to set CC_LD_DYNPATH differently, we should just allow it. Signed-off-by: Junio C Hamano --- Makefile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 53ab4b5536..2b6b66a883 100644 --- a/Makefile +++ b/Makefile @@ -688,8 +688,7 @@ ifeq ($(uname_S),NetBSD) NEEDS_LIBICONV = YesPlease endif BASIC_CFLAGS += -I/usr/pkg/include - BASIC_LDFLAGS += -L/usr/pkg/lib - ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib + BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease @@ -781,12 +780,14 @@ ifeq ($(uname_S),Darwin) endif endif -ifdef NO_R_TO_GCC_LINKER - # Some gcc does not accept and pass -R to the linker to specify - # the runtime dynamic library path. - CC_LD_DYNPATH = -Wl,-rpath= -else - CC_LD_DYNPATH = -R +ifndef CC_LD_DYNPATH + ifdef NO_R_TO_GCC_LINKER + # Some gcc does not accept and pass -R to the linker to specify + # the runtime dynamic library path. + CC_LD_DYNPATH = -Wl,-rpath, + else + CC_LD_DYNPATH = -R + endif endif ifdef NO_CURL -- cgit v1.2.1 From 798a94500230e4d2a1a18f005fe9592454fe451b Mon Sep 17 00:00:00 2001 From: Giovanni Funchal Date: Sat, 16 Aug 2008 15:01:23 +0200 Subject: configure: auto detect dynamic library path switches Most systems (e.g. Linux gcc) use "-Wl,-rpath," to pass to the linker the runtime dynamic library paths. Some other systems (e.g. Sun, some BSD) use "-R" etc. This patch adds tests in configure for the three most common switches (to my best knowledge) which should cover all current platforms where Git is used. Signed-Off-By: Giovanni Funchal Signed-off-by: Junio C Hamano --- config.mak.in | 1 + configure.ac | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/config.mak.in b/config.mak.in index b776149531..467b4aaeea 100644 --- a/config.mak.in +++ b/config.mak.in @@ -3,6 +3,7 @@ CC = @CC@ CFLAGS = @CFLAGS@ +CC_LD_DYNPATH = @CC_LD_DYNPATH@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources diff --git a/configure.ac b/configure.ac index 7c2856efc9..27bab00a45 100644 --- a/configure.ac +++ b/configure.ac @@ -103,6 +103,38 @@ GIT_PARSE_WITH(tcltk)) AC_MSG_NOTICE([CHECKS for programs]) # AC_PROG_CC([cc gcc]) +# which switch to pass runtime path to dynamic libraries to the linker +AC_CACHE_CHECK([if linker supports -R], ld_dashr, [ + SAVE_LDFLAGS="${LDFLAGS}" + LDFLAGS="${SAVE_LDFLAGS} -R /" + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_dashr=yes], [ld_dashr=no]) + LDFLAGS="${SAVE_LDFLAGS}" +]) +if test "$ld_dashr" = "yes"; then + AC_SUBST(CC_LD_DYNPATH, [-R]) +else + AC_CACHE_CHECK([if linker supports -Wl,-rpath,], ld_wl_rpath, [ + SAVE_LDFLAGS="${LDFLAGS}" + LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/" + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no]) + LDFLAGS="${SAVE_LD_FLAGS}" + ]) + if test "$ld_wl_rpath" = "yes"; then + AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,]) + else + AC_CACHE_CHECK([if linker supports -rpath], ld_rpath, [ + SAVE_LDFLAGS="${LDFLAGS}" + LDFLAGS="${SAVE_LDFLAGS} -rpath /" + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no]) + LDFLAGS="${SAVE_LD_FLAGS}" + ]) + if test "$ld_rpath" = "yes"; then + AC_SUBST(CC_LD_DYNPATH, [-rpath]) + else + AC_MSG_WARN([linker does not support runtime path to dynamic libraries]) + fi + fi +fi #AC_PROG_INSTALL # needs install-sh or install.sh in sources AC_CHECK_TOOLS(AR, [gar ar], :) AC_CHECK_PROGS(TAR, [gtar tar]) -- cgit v1.2.1 From df0daf8ac0fce4bb98d5aff9295535a1606d2fad Mon Sep 17 00:00:00 2001 From: "Stephen R. van den Berg" Date: Thu, 14 Aug 2008 20:02:20 +0200 Subject: git-daemon: call logerror() instead of error() Use logerror(), not error(), so that the messages won't be lost, especially when running the daemon with its log sent to the syslog facility. Signed-off-by: Stephen R. van den Berg Signed-off-by: Junio C Hamano --- daemon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon.c b/daemon.c index 8dcde73200..93e1106538 100644 --- a/daemon.c +++ b/daemon.c @@ -836,7 +836,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) if (sockfd < 0) continue; if (sockfd >= FD_SETSIZE) { - error("too large socket descriptor."); + logerror("Socket descriptor too large"); close(sockfd); continue; } @@ -955,7 +955,7 @@ static int service_loop(int socknum, int *socklist) if (poll(pfd, socknum + 1, -1) < 0) { if (errno != EINTR) { - error("poll failed, resuming: %s", + logerror("Poll failed, resuming: %s", strerror(errno)); sleep(1); } -- cgit v1.2.1 From 6a992e9e1da08af3e36ab90d5f5b7ee8077630ce Mon Sep 17 00:00:00 2001 From: "Stephen R. van den Berg" Date: Thu, 14 Aug 2008 20:02:20 +0200 Subject: git-daemon: use LOG_PID, simplify logging code Make git-daemon use LOG_PID like most daemons, instead of prepending the pid to the message ourselves, when using syslog(3). Simplify the logging code by setting stderr to line buffered, instead of building a single string and writing it out with a single write(2). Give an extra log message at the daemon start-up. Signed-off-by: Stephen R. van den Berg Signed-off-by: Junio C Hamano --- daemon.c | 47 ++++++++++++++++------------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/daemon.c b/daemon.c index 93e1106538..3e663a877c 100644 --- a/daemon.c +++ b/daemon.c @@ -78,38 +78,19 @@ static struct interp interp_table[] = { static void logreport(int priority, const char *err, va_list params) { - /* We should do a single write so that it is atomic and output - * of several processes do not get intermingled. */ - char buf[1024]; - int buflen; - int maxlen, msglen; - - /* sizeof(buf) should be big enough for "[pid] \n" */ - buflen = snprintf(buf, sizeof(buf), "[%ld] ", (long) getpid()); - - maxlen = sizeof(buf) - buflen - 1; /* -1 for our own LF */ - msglen = vsnprintf(buf + buflen, maxlen, err, params); - if (log_syslog) { + char buf[1024]; + vsnprintf(buf, sizeof(buf), err, params); syslog(priority, "%s", buf); - return; } - - /* maxlen counted our own LF but also counts space given to - * vsnprintf for the terminating NUL. We want to make sure that - * we have space for our own LF and NUL after the "meat" of the - * message, so truncate it at maxlen - 1. - */ - if (msglen > maxlen - 1) - msglen = maxlen - 1; - else if (msglen < 0) - msglen = 0; /* Protect against weird return values. */ - buflen += msglen; - - buf[buflen++] = '\n'; - buf[buflen] = '\0'; - - write_in_full(2, buf, buflen); + else { + /* Since stderr is set to linebuffered mode, the + * logging of different processes will not overlap + */ + fprintf(stderr, "[%d] ", (int)getpid()); + vfprintf(stderr, err, params); + fputc('\n', stderr); + } } static void logerror(const char *err, ...) @@ -1178,9 +1159,11 @@ int main(int argc, char **argv) } if (log_syslog) { - openlog("git-daemon", 0, LOG_DAEMON); + openlog("git-daemon", LOG_PID, LOG_DAEMON); set_die_routine(daemon_die); } + else + setlinebuf(stderr); /* avoid splitting a message in the middle */ if (inetd_mode && (group_name || user_name)) die("--user and --group are incompatible with --inetd"); @@ -1233,8 +1216,10 @@ int main(int argc, char **argv) return execute(peer); } - if (detach) + if (detach) { daemonize(); + loginfo("Ready to rumble"); + } else sanitize_stdfds(); -- cgit v1.2.1 From 695605b5080e1957bd9dab1fed35a7fee9814297 Mon Sep 17 00:00:00 2001 From: "Stephen R. van den Berg" Date: Thu, 14 Aug 2008 20:02:20 +0200 Subject: git-daemon: Simplify dead-children reaping logic Move almost all code out of the child_handler() into check_dead_children(). The fact that systemcalls get interrupted by signals allows us to make the SIGCHLD signal handler almost a no-op by simply running check_dead_children() right before waiting on poll(). In case some systems do not interrupt systemcalls upon signal receipt, all zombies will eventually be collected before the next poll() cycle. Signed-off-by: Stephen R. van den Berg Signed-off-by: Junio C Hamano --- daemon.c | 55 +++++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/daemon.c b/daemon.c index 3e663a877c..3abacfd9d9 100644 --- a/daemon.c +++ b/daemon.c @@ -16,7 +16,6 @@ static int log_syslog; static int verbose; static int reuseaddr; -static int child_handler_pipe[2]; static const char daemon_usage[] = "git daemon [--verbose] [--syslog] [--export-all]\n" @@ -680,6 +679,21 @@ static void check_dead_children(void) { unsigned spawned, reaped, deleted; + for (;;) { + int status; + pid_t pid = waitpid(-1, &status, WNOHANG); + + if (pid > 0) { + unsigned reaped = children_reaped; + if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + pid = -pid; + dead_child[reaped % MAX_CHILDREN] = pid; + children_reaped = reaped + 1; + continue; + } + break; + } + spawned = children_spawned; reaped = children_reaped; deleted = children_deleted; @@ -760,21 +774,10 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) static void child_handler(int signo) { - for (;;) { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - - if (pid > 0) { - unsigned reaped = children_reaped; - if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) - pid = -pid; - dead_child[reaped % MAX_CHILDREN] = pid; - children_reaped = reaped + 1; - write(child_handler_pipe[1], &status, 1); - continue; - } - break; - } + /* Otherwise empty handler because systemcalls will get interrupted + * upon signal receipt + * SysV needs the handler to be rearmed + */ signal(SIGCHLD, child_handler); } @@ -917,24 +920,21 @@ static int service_loop(int socknum, int *socklist) struct pollfd *pfd; int i; - if (pipe(child_handler_pipe) < 0) - die ("Could not set up pipe for child handler"); - - pfd = xcalloc(socknum + 1, sizeof(struct pollfd)); + pfd = xcalloc(socknum, sizeof(struct pollfd)); for (i = 0; i < socknum; i++) { pfd[i].fd = socklist[i]; pfd[i].events = POLLIN; } - pfd[socknum].fd = child_handler_pipe[0]; - pfd[socknum].events = POLLIN; signal(SIGCHLD, child_handler); for (;;) { int i; - if (poll(pfd, socknum + 1, -1) < 0) { + check_dead_children(); + + if (poll(pfd, socknum, -1) < 0) { if (errno != EINTR) { logerror("Poll failed, resuming: %s", strerror(errno)); @@ -942,10 +942,6 @@ static int service_loop(int socknum, int *socklist) } continue; } - if (pfd[socknum].revents & POLLIN) { - read(child_handler_pipe[0], &i, 1); - check_dead_children(); - } for (i = 0; i < socknum; i++) { if (pfd[i].revents & POLLIN) { @@ -1036,11 +1032,6 @@ int main(int argc, char **argv) gid_t gid = 0; int i; - /* Without this we cannot rely on waitpid() to tell - * what happened to our children. - */ - signal(SIGCHLD, SIG_DFL); - for (i = 1; i < argc; i++) { char *arg = argv[i]; -- cgit v1.2.1 From 3bd62c21760f92996569bb9335b399a9545a5c41 Mon Sep 17 00:00:00 2001 From: "Stephen R. van den Berg" Date: Thu, 14 Aug 2008 20:02:20 +0200 Subject: git-daemon: rewrite kindergarden, new option --max-connections Get rid of the fixed array of children and make max-connections dynamic and configurable. Fix the killing code to actually kill the newest connections from duplicate IP-addresses. Avoid forking if too busy already. Signed-off-by: Stephen R. van den Berg Signed-off-by: Junio C Hamano --- Documentation/git-daemon.txt | 9 +- daemon.c | 213 +++++++++++++++---------------------------- 2 files changed, 82 insertions(+), 140 deletions(-) diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 4ba4b75c11..b08a08cd95 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -9,8 +9,9 @@ SYNOPSIS -------- [verse] 'git daemon' [--verbose] [--syslog] [--export-all] - [--timeout=n] [--init-timeout=n] [--strict-paths] - [--base-path=path] [--user-path | --user-path=path] + [--timeout=n] [--init-timeout=n] [--max-connections=n] + [--strict-paths] [--base-path=path] [--base-path-relaxed] + [--user-path | --user-path=path] [--interpolated-path=pathtemplate] [--reuseaddr] [--detach] [--pid-file=file] [--enable=service] [--disable=service] @@ -99,6 +100,10 @@ OPTIONS it takes for the server to process the sub-request and time spent waiting for next client's request. +--max-connections:: + Maximum number of concurrent clients, defaults to 32. Set it to + zero for no limit. + --syslog:: Log to syslog instead of stderr. Note that this option does not imply --verbose, thus by default only error conditions will be logged. diff --git a/daemon.c b/daemon.c index 3abacfd9d9..79f0388204 100644 --- a/daemon.c +++ b/daemon.c @@ -19,8 +19,8 @@ static int reuseaddr; static const char daemon_usage[] = "git daemon [--verbose] [--syslog] [--export-all]\n" -" [--timeout=n] [--init-timeout=n] [--strict-paths]\n" -" [--base-path=path] [--base-path-relaxed]\n" +" [--timeout=n] [--init-timeout=n] [--max-connections=n]\n" +" [--strict-paths] [--base-path=path] [--base-path-relaxed]\n" " [--user-path | --user-path=path]\n" " [--interpolated-path=path]\n" " [--reuseaddr] [--detach] [--pid-file=file]\n" @@ -584,40 +584,35 @@ static int execute(struct sockaddr *addr) return -1; } +static int max_connections = 32; -/* - * We count spawned/reaped separately, just to avoid any - * races when updating them from signals. The SIGCHLD handler - * will only update children_reaped, and the fork logic will - * only update children_spawned. - * - * MAX_CHILDREN should be a power-of-two to make the modulus - * operation cheap. It should also be at least twice - * the maximum number of connections we will ever allow. - */ -#define MAX_CHILDREN 128 - -static int max_connections = 25; - -/* These are updated by the signal handler */ -static volatile unsigned int children_reaped; -static pid_t dead_child[MAX_CHILDREN]; - -/* These are updated by the main loop */ -static unsigned int children_spawned; -static unsigned int children_deleted; +static unsigned int live_children; static struct child { + struct child *next; pid_t pid; - int addrlen; struct sockaddr_storage address; -} live_child[MAX_CHILDREN]; +} *firstborn; -static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen) +static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) { - live_child[idx].pid = pid; - live_child[idx].addrlen = addrlen; - memcpy(&live_child[idx].address, addr, addrlen); + struct child *newborn; + newborn = xcalloc(1, sizeof *newborn); + if (newborn) { + struct child **cradle; + + live_children++; + newborn->pid = pid; + memcpy(&newborn->address, addr, addrlen); + for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next) + if (!memcmp(&(*cradle)->address, &newborn->address, + sizeof newborn->address)) + break; + newborn->next = *cradle; + *cradle = newborn; + } + else + logerror("Out of memory spawning new child"); } /* @@ -626,142 +621,78 @@ static void add_child(int idx, pid_t pid, struct sockaddr *addr, int addrlen) * We move everything up by one, since the new "deleted" will * be one higher. */ -static void remove_child(pid_t pid, unsigned deleted, unsigned spawned) +static void remove_child(pid_t pid) { - struct child n; + struct child **cradle, *blanket; - deleted %= MAX_CHILDREN; - spawned %= MAX_CHILDREN; - if (live_child[deleted].pid == pid) { - live_child[deleted].pid = -1; - return; - } - n = live_child[deleted]; - for (;;) { - struct child m; - deleted = (deleted + 1) % MAX_CHILDREN; - if (deleted == spawned) - die("could not find dead child %d\n", pid); - m = live_child[deleted]; - live_child[deleted] = n; - if (m.pid == pid) - return; - n = m; - } + for (cradle = &firstborn; (blanket = *cradle); cradle = &blanket->next) + if (blanket->pid == pid) { + *cradle = blanket->next; + live_children--; + free(blanket); + break; + } } /* * This gets called if the number of connections grows * past "max_connections". * - * We _should_ start off by searching for connections - * from the same IP, and if there is some address wth - * multiple connections, we should kill that first. - * - * As it is, we just "randomly" kill 25% of the connections, - * and our pseudo-random generator sucks too. I have no - * shame. - * - * Really, this is just a place-holder for a _real_ algorithm. + * We kill the newest connection from a duplicate IP. */ -static void kill_some_children(int signo, unsigned start, unsigned stop) +static void kill_some_child(void) { - start %= MAX_CHILDREN; - stop %= MAX_CHILDREN; - while (start != stop) { - if (!(start & 3)) - kill(live_child[start].pid, signo); - start = (start + 1) % MAX_CHILDREN; - } -} - -static void check_dead_children(void) -{ - unsigned spawned, reaped, deleted; - - for (;;) { - int status; - pid_t pid = waitpid(-1, &status, WNOHANG); - - if (pid > 0) { - unsigned reaped = children_reaped; - if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) - pid = -pid; - dead_child[reaped % MAX_CHILDREN] = pid; - children_reaped = reaped + 1; - continue; - } - break; - } - - spawned = children_spawned; - reaped = children_reaped; - deleted = children_deleted; - - while (deleted < reaped) { - pid_t pid = dead_child[deleted % MAX_CHILDREN]; - const char *dead = pid < 0 ? " (with error)" : ""; + const struct child *blanket; - if (pid < 0) - pid = -pid; + if ((blanket = firstborn)) { + const struct child *next; - /* XXX: Custom logging, since we don't wanna getpid() */ - if (verbose) { - if (log_syslog) - syslog(LOG_INFO, "[%d] Disconnected%s", - pid, dead); - else - fprintf(stderr, "[%d] Disconnected%s\n", - pid, dead); - } - remove_child(pid, deleted, spawned); - deleted++; + for (; (next = blanket->next); blanket = next) + if (!memcmp(&blanket->address, &next->address, + sizeof next->address)) { + kill(blanket->pid, SIGTERM); + break; + } } - children_deleted = deleted; } -static void check_max_connections(void) +static void check_dead_children(void) { - for (;;) { - int active; - unsigned spawned, deleted; - - check_dead_children(); - - spawned = children_spawned; - deleted = children_deleted; - - active = spawned - deleted; - if (active <= max_connections) - break; - - /* Kill some unstarted connections with SIGTERM */ - kill_some_children(SIGTERM, deleted, spawned); - if (active <= max_connections << 1) - break; + int status; + pid_t pid; - /* If the SIGTERM thing isn't helping use SIGKILL */ - kill_some_children(SIGKILL, deleted, spawned); - sleep(1); + while ((pid = waitpid(-1, &status, WNOHANG))>0) { + const char *dead = ""; + remove_child(pid); + if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + dead = " (with error)"; + loginfo("[%d] Disconnected%s", (int)pid, dead); } } static void handle(int incoming, struct sockaddr *addr, int addrlen) { - pid_t pid = fork(); + pid_t pid; - if (pid) { - unsigned idx; + if (max_connections && live_children >= max_connections) { + kill_some_child(); + sleep(1); /* give it some time to die */ + check_dead_children(); + if (live_children >= max_connections) { + close(incoming); + logerror("Too many children, dropping connection"); + return; + } + } + if ((pid = fork())) { close(incoming); - if (pid < 0) + if (pid < 0) { + logerror("Couldn't fork %s", strerror(errno)); return; + } - idx = children_spawned % MAX_CHILDREN; - children_spawned++; - add_child(idx, pid, addr, addrlen); - - check_max_connections(); + add_child(pid, addr, addrlen); return; } @@ -1077,6 +1008,12 @@ int main(int argc, char **argv) init_timeout = atoi(arg+15); continue; } + if (!prefixcmp(arg, "--max-connections=")) { + max_connections = atoi(arg+18); + if (max_connections < 0) + max_connections = 0; /* unlimited */ + continue; + } if (!strcmp(arg, "--strict-paths")) { strict_paths = 1; continue; -- cgit v1.2.1 From 53030f8d1199ef9da86e7ddc2b8a659a355e5f69 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Aug 2008 00:37:34 -0700 Subject: revision --simplify-merges: do not leave commits unprocessed When we still do not know how parents of a commit simplify to, we should defer processing of the commit, not discard it. Signed-off-by: Junio C Hamano --- revision.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/revision.c b/revision.c index 33cb207f28..8cd39da2ef 100644 --- a/revision.c +++ b/revision.c @@ -1459,8 +1459,10 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c cnt++; } } - if (cnt) + if (cnt) { + tail = &commit_list_insert(commit, tail)->next; return tail; + } /* * Rewrite our list of parents. -- cgit v1.2.1 From 5eac739e05620e491555850d5b513ef60595c016 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 14 Aug 2008 13:52:36 -0700 Subject: revision --simplify-merges: make it a no-op without pathspec When we are not pruning there is no reason to run the merge simplification. Also avoid running topo-order sort twice. Signed-off-by: Junio C Hamano --- revision.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/revision.c b/revision.c index 8cd39da2ef..db2ab2b11a 100644 --- a/revision.c +++ b/revision.c @@ -1518,7 +1518,10 @@ static void simplify_merges(struct rev_info *revs) struct commit_list *list; struct commit_list *yet_to_do, **tail; - sort_in_topological_order(&revs->commits, revs->lifo); + if (!revs->topo_order) + sort_in_topological_order(&revs->commits, revs->lifo); + if (!revs->prune) + return; /* feed the list reversed */ yet_to_do = NULL; -- cgit v1.2.1 From 289796dd29dd656734cfd59b657deb943a71cf6a Mon Sep 17 00:00:00 2001 From: Don Zickus Date: Thu, 14 Aug 2008 11:35:42 -0400 Subject: mailinfo: re-fix MIME multipart boundary parsing Recent changes to is_multipart_boundary() caused git-mailinfo to segfault. The reason was after handling the end of the boundary the code tried to look for another boundary. Because the boundary list was empty, dereferencing the pointer to the top of the boundary caused the program to go boom. The fix is to check to see if the list is empty and if so go on its merry way instead of looking for another boundary. I also fixed a couple of increments and decrements that didn't look correct relating to content_top. The boundary test case was updated to catch future problems like this again. Signed-off-by: Don Zickus Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 6 +++--- t/t5100/sample.mbox | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 3577382d70..26d3e5d7af 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -175,7 +175,7 @@ static void handle_content_type(struct strbuf *line) message_type = TYPE_OTHER; if (slurp_attr(line->buf, "boundary=", boundary)) { strbuf_insert(boundary, 0, "--", 2); - if (content_top++ >= &content[MAX_BOUNDARIES]) { + if (++content_top > &content[MAX_BOUNDARIES]) { fprintf(stderr, "Too many boundaries to handle\n"); exit(1); } @@ -603,7 +603,7 @@ static void handle_filter(struct strbuf *line); static int find_boundary(void) { while (!strbuf_getline(&line, fin, '\n')) { - if (is_multipart_boundary(&line)) + if (*content_top && is_multipart_boundary(&line)) return 1; } return 0; @@ -626,7 +626,7 @@ again: /* technically won't happen as is_multipart_boundary() will fail first. But just in case.. */ - if (content_top-- < content) { + if (--content_top < content) { fprintf(stderr, "Detected mismatched boundaries, " "can't recover\n"); exit(1); diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index d7ca79b1fc..4bf7947b41 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -500,3 +500,4 @@ index 3e5fe51..aabfe5c 100644 1.6.0.rc2 --=-=-=-- + -- cgit v1.2.1 From da9973c6f9600d90e64aac647f3ed22dfd692f70 Mon Sep 17 00:00:00 2001 From: Robert Schiele Date: Mon, 18 Aug 2008 16:17:04 +0200 Subject: adapt git-cvsserver manpage to dash-free syntax Signed-off-by: Robert Schiele Signed-off-by: Junio C Hamano --- Documentation/git-cvsserver.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index c2d3c90d27..785779e221 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -11,7 +11,7 @@ SYNOPSIS SSH: [verse] -export CVS_SERVER=git-cvsserver +export CVS_SERVER="git cvsserver" 'cvs' -d :ext:user@server/path/repo.git co pserver (/etc/inetd.conf): @@ -109,7 +109,7 @@ Note: Newer CVS versions (>= 1.12.11) also support specifying CVS_SERVER directly in CVSROOT like ------ -cvs -d ":ext;CVS_SERVER=git-cvsserver:user@server/path/repo.git" co +cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co ------ This has the advantage that it will be saved in your 'CVS/Root' files and you don't need to worry about always setting the correct environment @@ -158,7 +158,7 @@ allowing access over SSH. -- ------ export CVSROOT=:ext:user@server:/var/git/project.git - export CVS_SERVER=git-cvsserver + export CVS_SERVER="git cvsserver" ------ -- 4. For SSH clients that will make commits, make sure their server-side @@ -283,7 +283,7 @@ To get a checkout with the Eclipse CVS client: Protocol notes: If you are using anonymous access via pserver, just select that. Those using SSH access should choose the 'ext' protocol, and configure 'ext' access on the Preferences->Team->CVS->ExtConnection pane. Set CVS_SERVER to -'git-cvsserver'. Note that password support is not good when using 'ext', +"'git cvsserver'". Note that password support is not good when using 'ext', you will definitely want to have SSH keys setup. Alternatively, you can just use the non-standard extssh protocol that Eclipse -- cgit v1.2.1 From fdb2a2a600969598fd80f01b009fbbb020d596ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Aug 2008 21:57:16 +0200 Subject: compat: introduce on_disk_bytes() Some platforms do not have st_blocks member in "struct stat"; mingw already emulates it by rounding it up to closest 512-byte blocks (even though it could overcount when a file has holes). The reason to use the member is only to figure out how many kilobytes the files occupy on-disk, so give a helper function in git-compat-util.h to compute this value. Signed-off-by: Junio C Hamano Acked-by: Johannes Sixt --- Makefile | 7 +++++++ builtin-count-objects.c | 4 ++-- compat/mingw.c | 8 -------- compat/mingw.h | 1 - git-compat-util.h | 6 ++++++ 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 53ab4b5536..8b1829c9b5 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,9 @@ all:: # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-index perspective. # +# Define NO_ST_BLOCKS_IN_STRUCT_STAT if your platform does not have st_blocks +# field that counts the on-disk footprint in 512-byte blocks. +# # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8 # # Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72. @@ -749,6 +752,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease NO_POSIX_ONLY_PROGRAMS = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" @@ -863,6 +867,9 @@ endif ifdef NO_D_INO_IN_DIRENT BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT endif +ifdef NO_ST_BLOCKS_IN_STRUCT_STAT + BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT +endif ifdef NO_C99_FORMAT BASIC_CFLAGS += -DNO_C99_FORMAT endif diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 91b5487478..a1c9eb41cb 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -43,7 +43,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, if (lstat(path, &st) || !S_ISREG(st.st_mode)) bad = 1; else - (*loose_size) += xsize_t(st.st_blocks); + (*loose_size) += xsize_t(on_disk_bytes(st)); } if (bad) { if (verbose) { @@ -115,7 +115,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) num_pack++; } printf("count: %lu\n", loose); - printf("size: %lu\n", loose_size / 2); + printf("size: %lu\n", loose_size / 1024); printf("in-pack: %lu\n", packed); printf("packs: %lu\n", num_pack); printf("prune-packable: %lu\n", packed_loose); diff --git a/compat/mingw.c b/compat/mingw.c index 772cad510d..798fb6178a 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -31,11 +31,6 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) return (time_t)winTime; } -static inline size_t size_to_blocks(size_t s) -{ - return (s+511)/512; -} - extern int _getdrive( void ); /* We keep the do_lstat code in a separate function to avoid recursion. * When a path ends with a slash, the stat will fail with ENOENT. In @@ -59,7 +54,6 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_uid = 0; buf->st_mode = fMode; buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ - buf->st_blocks = size_to_blocks(buf->st_size); buf->st_dev = _getdrive() - 1; buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); @@ -142,7 +136,6 @@ int mingw_fstat(int fd, struct mingw_stat *buf) buf->st_uid = st.st_uid; buf->st_mode = st.st_mode; buf->st_size = st.st_size; - buf->st_blocks = size_to_blocks(buf->st_size); buf->st_dev = st.st_dev; buf->st_atime = st.st_atime; buf->st_mtime = st.st_mtime; @@ -164,7 +157,6 @@ int mingw_fstat(int fd, struct mingw_stat *buf) buf->st_uid = 0; buf->st_mode = fMode; buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ - buf->st_blocks = size_to_blocks(buf->st_size); buf->st_dev = _getdrive() - 1; buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); diff --git a/compat/mingw.h b/compat/mingw.h index a52e657c51..1472d59772 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -169,7 +169,6 @@ struct mingw_stat { time_t st_mtime, st_atime, st_ctime; unsigned st_dev, st_ino, st_uid, st_gid; size_t st_size; - size_t st_blocks; }; int mingw_lstat(const char *file_name, struct mingw_stat *buf); int mingw_fstat(int fd, struct mingw_stat *buf); diff --git a/git-compat-util.h b/git-compat-util.h index cf89cdf459..1b40dbd535 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -192,6 +192,12 @@ extern int git_munmap(void *start, size_t length); #endif /* NO_MMAP */ +#ifdef NO_ST_BLOCKS_IN_STRUCT_STAT +#define on_disk_bytes(st) ((st).st_size) +#else +#define on_disk_bytes(st) ((st).st_blocks * 512) +#endif + #define DEFAULT_PACKED_GIT_LIMIT \ ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256)) -- cgit v1.2.1 From 180964f0b98344f3127d6b8167cec8a07ef663ad Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Mon, 18 Aug 2008 22:01:06 +0200 Subject: Revert "Windows: Use a customized struct stat that also has the st_blocks member." This reverts commit fc2ded5b08e071beed974117c0148781b1acc94a. As we do not need the member in struct stat, we do not need to have a custom "struct mingw_stat" anymore. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/mingw.c | 28 ++++++++-------------------- compat/mingw.h | 15 +++------------ 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 798fb6178a..ccfa2a0a3d 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -52,9 +52,10 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; + buf->st_nlink = 1; buf->st_mode = fMode; buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ - buf->st_dev = _getdrive() - 1; + buf->st_dev = buf->st_rdev = (_getdrive() - 1); buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); @@ -88,7 +89,7 @@ static int do_lstat(const char *file_name, struct stat *buf) * complete. Note that Git stat()s are redirected to mingw_lstat() * too, since Windows doesn't really handle symlinks that well. */ -int mingw_lstat(const char *file_name, struct mingw_stat *buf) +int mingw_lstat(const char *file_name, struct stat *buf) { int namelen; static char alt_name[PATH_MAX]; @@ -116,8 +117,7 @@ int mingw_lstat(const char *file_name, struct mingw_stat *buf) } #undef fstat -#undef stat -int mingw_fstat(int fd, struct mingw_stat *buf) +int mingw_fstat(int fd, struct stat *buf) { HANDLE fh = (HANDLE)_get_osfhandle(fd); BY_HANDLE_FILE_INFORMATION fdata; @@ -127,21 +127,8 @@ int mingw_fstat(int fd, struct mingw_stat *buf) return -1; } /* direct non-file handles to MS's fstat() */ - if (GetFileType(fh) != FILE_TYPE_DISK) { - struct stat st; - if (fstat(fd, &st)) - return -1; - buf->st_ino = st.st_ino; - buf->st_gid = st.st_gid; - buf->st_uid = st.st_uid; - buf->st_mode = st.st_mode; - buf->st_size = st.st_size; - buf->st_dev = st.st_dev; - buf->st_atime = st.st_atime; - buf->st_mtime = st.st_mtime; - buf->st_ctime = st.st_ctime; - return 0; - } + if (GetFileType(fh) != FILE_TYPE_DISK) + return fstat(fd, buf); if (GetFileInformationByHandle(fh, &fdata)) { int fMode = S_IREAD; @@ -155,9 +142,10 @@ int mingw_fstat(int fd, struct mingw_stat *buf) buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; + buf->st_nlink = 1; buf->st_mode = fMode; buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ - buf->st_dev = _getdrive() - 1; + buf->st_dev = buf->st_rdev = (_getdrive() - 1); buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); diff --git a/compat/mingw.h b/compat/mingw.h index 1472d59772..4f275cb8e6 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -162,21 +162,12 @@ int mingw_rename(const char*, const char*); /* Use mingw_lstat() instead of lstat()/stat() and * mingw_fstat() instead of fstat() on Windows. - * struct stat is redefined because it lacks the st_blocks member. */ -struct mingw_stat { - unsigned st_mode; - time_t st_mtime, st_atime, st_ctime; - unsigned st_dev, st_ino, st_uid, st_gid; - size_t st_size; -}; -int mingw_lstat(const char *file_name, struct mingw_stat *buf); -int mingw_fstat(int fd, struct mingw_stat *buf); +int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat -#define stat mingw_stat -static inline int mingw_stat(const char *file_name, struct mingw_stat *buf) -{ return mingw_lstat(file_name, buf); } +#define stat(x,y) mingw_lstat(x,y) int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime -- cgit v1.2.1 From 54514f1f144a0e79b6c39d4a865927a18c17ed17 Mon Sep 17 00:00:00 2001 From: Marcus Griep Date: Mon, 18 Aug 2008 12:25:40 -0400 Subject: Update t/.gitignore to ignore all trash directories The current .gitignore only ignores the old "trash directory" and not the new "trash directory.[test]". This ignores both forms. Signed-off-by: Marcus Griep Signed-off-by: Junio C Hamano --- t/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/.gitignore b/t/.gitignore index b27e280083..7dcbb232cd 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -1,2 +1,2 @@ -/trash directory +/trash directory* /test-results -- cgit v1.2.1 From 26e08a0190cb3354e43bab13ea693a5c826a8fe1 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Mon, 18 Aug 2008 18:17:53 -0500 Subject: t1002-read-tree-m-u-2way.sh: use 'git diff -U0' rather than 'diff -U0' Some old platforms have an old diff which doesn't have the -U option. 'git diff' can be used in its place. Adjust the comparison function to strip git's additional header lines to make this possible. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- t/t1002-read-tree-m-u-2way.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index aa9dd580a6..5e40cec530 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -14,6 +14,8 @@ _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" compare_change () { sed >current \ + -e '1{/^diff --git /d;}' \ + -e '2{/^index /d;}' \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1" test_cmp expected current @@ -75,7 +77,7 @@ test_expect_success \ git update-index --add yomin && git read-tree -m -u $treeH $treeM && git ls-files --stage >4.out || return 1 - diff -U0 M.out 4.out >4diff.out + git diff -U0 --no-index M.out 4.out >4diff.out compare_change 4diff.out expected && check_cache_at yomin clean && sum bozbar frotz nitfol >actual4.sum && @@ -94,7 +96,7 @@ test_expect_success \ echo yomin yomin >yomin && git read-tree -m -u $treeH $treeM && git ls-files --stage >5.out || return 1 - diff -U0 M.out 5.out >5diff.out + git diff -U0 --no-index M.out 5.out >5diff.out compare_change 5diff.out expected && check_cache_at yomin dirty && sum bozbar frotz nitfol >actual5.sum && @@ -206,7 +208,7 @@ test_expect_success \ git update-index --add nitfol && git read-tree -m -u $treeH $treeM && git ls-files --stage >14.out || return 1 - diff -U0 M.out 14.out >14diff.out + git diff -U0 --no-index M.out 14.out >14diff.out compare_change 14diff.out expected && sum bozbar frotz >actual14.sum && grep -v nitfol M.sum > expected14.sum && @@ -227,7 +229,7 @@ test_expect_success \ echo nitfol nitfol nitfol >nitfol && git read-tree -m -u $treeH $treeM && git ls-files --stage >15.out || return 1 - diff -U0 M.out 15.out >15diff.out + git diff -U0 --no-index M.out 15.out >15diff.out compare_change 15diff.out expected && check_cache_at nitfol dirty && sum bozbar frotz >actual15.sum && -- cgit v1.2.1 From 4cfc24afc9ffa4d3f1623be8990eea118e82d4fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Aug 2008 18:05:39 -0700 Subject: shell: do not play duplicated definition games to shrink the executable Playing with linker games to shrink git-shell did not go well with various other platforms and compilers. Signed-off-by: Junio C Hamano --- Makefile | 9 +-------- shell.c | 8 -------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 53ab4b5536..71339e1098 100644 --- a/Makefile +++ b/Makefile @@ -333,7 +333,6 @@ endif export PERL_PATH LIB_FILE=libgit.a -COMPAT_LIB = compat/lib.a XDIFF_LIB=xdiff/lib.a LIB_H += archive.h @@ -1223,12 +1222,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -$(COMPAT_LIB): $(COMPAT_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(COMPAT_OBJS) - -git-shell$X: abspath.o ctype.o exec_cmd.o quote.o strbuf.o usage.o wrapper.o shell.o $(COMPAT_LIB) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(COMPAT_LIB) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h @@ -1441,7 +1434,7 @@ distclean: clean clean: $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \ - $(LIB_FILE) $(XDIFF_LIB) $(COMPAT_LIB) + $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* diff --git a/shell.c b/shell.c index 6a48de05ff..0f6a727a8c 100644 --- a/shell.c +++ b/shell.c @@ -3,14 +3,6 @@ #include "exec_cmd.h" #include "strbuf.h" -/* Stubs for functions that make no sense for git-shell. These stubs - * are provided here to avoid linking in external redundant modules. - */ -void release_pack_memory(size_t need, int fd){} -void trace_argv_printf(const char **argv, const char *fmt, ...){} -void trace_printf(const char *fmt, ...){} - - static int do_generic_cmd(const char *me, char *arg) { const char *my_argv[4]; -- cgit v1.2.1 From c8c4450e1949055cb57e32425b125f45f3481742 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Tue, 19 Aug 2008 20:42:04 +0200 Subject: git format-patch: avoid underrun when format.headers is empty or all NLs * builtin-log.c (add_header): Avoid a buffer underrun when format.headers is empty or all newlines. Reproduce with this: git config format.headers '' && git format-patch -1 Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- builtin-log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-log.c b/builtin-log.c index f4975cf35f..911fd65990 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -461,7 +461,7 @@ static int extra_cc_alloc; static void add_header(const char *value) { int len = strlen(value); - while (value[len - 1] == '\n') + while (len && value[len - 1] == '\n') len--; if (!strncasecmp(value, "to: ", 4)) { ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc); -- cgit v1.2.1 From a624eaa7820f4f9814e41e911c665a0aba2fce34 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Fri, 15 Aug 2008 13:39:26 +0200 Subject: add boolean diff.suppress-blank-empty config option GNU diff's --suppress-blank-empty option makes it so that diff no longer outputs trailing white space unless the input data has it. With this option, empty context lines are now empty also in diff -u output. Before, they would have a single trailing space. * diff.c (diff_suppress_blank_empty): New global. (git_diff_basic_config): Set it. (fn_out_consume): Honor it. * t/t4029-diff-trailing-space.sh: New file. * Documentation/config.txt: Document it. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ diff.c | 13 +++++++++++++ t/t4029-diff-trailing-space.sh | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100755 t/t4029-diff-trailing-space.sh diff --git a/Documentation/config.txt b/Documentation/config.txt index 676c39bb84..9020675866 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -567,6 +567,10 @@ diff.autorefreshindex:: affects only 'git-diff' Porcelain, and not lower level 'diff' commands, such as 'git-diff-files'. +diff.suppress-blank-empty:: + A boolean to inhibit the standard behavior of printing a space + before each empty output line. Defaults to false. + diff.external:: If this config variable is set, diff generation is not performed using the internal diff machinery, but using the diff --git a/diff.c b/diff.c index bf5d5f15a3..fe43407b78 100644 --- a/diff.c +++ b/diff.c @@ -20,6 +20,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 200; +static int diff_suppress_blank_empty; int diff_use_color_default = -1; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; @@ -176,6 +177,12 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) return 0; } + /* like GNU diff's --suppress-blank-empty option */ + if (!strcmp(var, "diff.suppress-blank-empty")) { + diff_suppress_blank_empty = git_config_bool(var, value); + return 0; + } + if (!prefixcmp(var, "diff.")) { const char *ep = strrchr(var, '.'); if (ep != var + 4) { @@ -580,6 +587,12 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } + if (diff_suppress_blank_empty + && len == 2 && line[0] == ' ' && line[1] == '\n') { + line[0] = '\n'; + len = 1; + } + /* This is not really necessary for now because * this codepath only deals with two-way diffs. */ diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh new file mode 100755 index 0000000000..4ca65e0332 --- /dev/null +++ b/t/t4029-diff-trailing-space.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# +# Copyright (c) Jim Meyering +# +test_description='diff honors config option, diff.suppress-blank-empty' + +. ./test-lib.sh + +cat <<\EOF > exp || +diff --git a/f b/f +index 5f6a263..8cb8bae 100644 +--- a/f ++++ b/f +@@ -1,2 +1,2 @@ + +-x ++y +EOF +exit 1 + +test_expect_success \ + "$test_description" \ + 'printf "\nx\n" > f && + git add f && + git commit -q -m. f && + printf "\ny\n" > f && + git config --bool diff.suppress-blank-empty true && + git diff f > actual && + test_cmp exp actual && + perl -i.bak -p -e "s/^\$/ /" exp && + git config --bool diff.suppress-blank-empty false && + git diff f > actual && + test_cmp exp actual && + git config --bool --unset diff.suppress-blank-empty && + git diff f > actual && + test_cmp exp actual + ' + +test_done -- cgit v1.2.1 From 8b1d88e87adcff001a72706bda8dcdefdfb8da67 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Tue, 1 Apr 2008 14:53:51 +0200 Subject: SubmittingPatches: fix a typo Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- Documentation/SubmittingPatches | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 841bead9db..a1e9100f9e 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -71,7 +71,7 @@ run git diff --check on your changes before you commit. (1a) Try to be nice to older C compilers -We try to support wide range of C compilers to compile +We try to support a wide range of C compilers to compile git with. That means that you should not use C99 initializers, even if a lot of compilers grok it. -- cgit v1.2.1 From 6457e58c8fc00ef1c72724dad5f8baf54cfee5a2 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Wed, 2 Jul 2008 09:49:59 +0200 Subject: reword --full-index description Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- Documentation/diff-options.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index cba90fd27c..1759386404 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -107,9 +107,9 @@ endif::git-format-patch[] --exit-code. --full-index:: - Instead of the first handful characters, show full - object name of pre- and post-image blob on the "index" - line when generating a patch format output. + Instead of the first handful of characters, show the full + pre- and post-image blob object names on the "index" + line when generating patch format output. --binary:: In addition to --full-index, output "binary diff" that -- cgit v1.2.1 From e9d7d10a7f17fb9cc6a4d37b6fdf27170f4deede Mon Sep 17 00:00:00 2001 From: Jeff King Date: Tue, 19 Aug 2008 13:28:24 -0400 Subject: mailinfo: avoid violating strbuf assertion In handle_from, we calculate the end boundary of a section to remove from a strbuf using strcspn like this: el = strcspn(buf, set_of_end_boundaries); strbuf_remove(&sb, start, el + 1); This works fine if "el" is the offset of the boundary character, meaning we remove up to and including that character. But if the end boundary didn't match (that is, we hit the end of the string as the boundary instead) then we want just "el". Asking for "el+1" caught an out-of-bounds assertion in the strbuf library. This manifested itself when we got a 'From' header that had just an email address with nothing else in it (the end of the string was the end of the address, rather than, e.g., a trailing '>' character), causing git-mailinfo to barf. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-mailinfo.c | 2 +- t/t5100-mailinfo.sh | 11 +++++++++++ t/t5100/info-from.expect | 5 +++++ t/t5100/info-from.in | 8 ++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 t/t5100/info-from.expect create mode 100644 t/t5100/info-from.in diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 26d3e5d7af..e890f7a6d1 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -107,7 +107,7 @@ static void handle_from(const struct strbuf *from) el = strcspn(at, " \n\t\r\v\f>"); strbuf_reset(&email); strbuf_add(&email, at, el); - strbuf_remove(&f, at - f.buf, el + 1); + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); /* The remainder is name. It could be "John Doe " * or "john.doe@xz (John Doe)", but we have removed the diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index 8dfaddda91..198e3503d5 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -43,4 +43,15 @@ test_expect_success 'Preserve NULs out of MIME encoded message' ' ' +test_expect_success 'mailinfo on from header without name works' ' + + mkdir info-from && + git mailsplit -oinfo-from "$TEST_DIRECTORY"/t5100/info-from.in && + test_cmp "$TEST_DIRECTORY"/t5100/info-from.in info-from/0001 && + git mailinfo info-from/msg info-from/patch \ + info-from/out && + test_cmp "$TEST_DIRECTORY"/t5100/info-from.expect info-from/out + +' + test_done diff --git a/t/t5100/info-from.expect b/t/t5100/info-from.expect new file mode 100644 index 0000000000..c31d2eb550 --- /dev/null +++ b/t/t5100/info-from.expect @@ -0,0 +1,5 @@ +Author: bare@example.com +Email: bare@example.com +Subject: testing bare address in from header +Date: Sun, 25 May 2008 00:38:18 -0700 + diff --git a/t/t5100/info-from.in b/t/t5100/info-from.in new file mode 100644 index 0000000000..4f082093fc --- /dev/null +++ b/t/t5100/info-from.in @@ -0,0 +1,8 @@ +From 667d8940e719cddee1cfe237cbbe215e20270b09 Mon Sep 17 00:00:00 2001 +From: bare@example.com +Date: Sun, 25 May 2008 00:38:18 -0700 +Subject: [PATCH] testing bare address in from header + +commit message +--- +patch -- cgit v1.2.1 From c71e917975ecd5fdc4caef245cf18b244213e3f6 Mon Sep 17 00:00:00 2001 From: Jim Meyering Date: Tue, 19 Aug 2008 20:46:30 +0200 Subject: remote.c: remove useless if-before-free test We removed a handful of these useless if-before-free tests several months ago. This change removes a new one that snuck back in. Signed-off-by: Jim Meyering Signed-off-by: Junio C Hamano --- remote.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/remote.c b/remote.c index f61a3ab399..105668f8a3 100644 --- a/remote.c +++ b/remote.c @@ -579,8 +579,7 @@ int valid_fetch_refspec(const char *fetch_refspec_str) struct refspec *refspec; refspec = parse_refspec_internal(1, fetch_refspec, 1, 1); - if (refspec) - free(refspec); + free(refspec); return !!refspec; } -- cgit v1.2.1 From daa0cc9a92c9c2c714aa5f7da6d0ff65b93e0698 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Aug 2008 18:05:43 -0700 Subject: Build-in "git-shell" This trivially makes "git-shell" a built-in. It makes the executable even fatter, though. And MinGW removed git-shell only because of the funny dependencies; there is no reason to do so anymore. Signed-off-by: Junio C Hamano Tested-on-MinGW-by: Johannes Sixt --- Makefile | 2 +- builtin-shell.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ builtin.h | 1 + git.c | 1 + shell.c | 89 -------------------------------------------------------- 5 files changed, 93 insertions(+), 90 deletions(-) create mode 100644 builtin-shell.c delete mode 100644 shell.c diff --git a/Makefile b/Makefile index 71339e1098..7c585e866c 100644 --- a/Makefile +++ b/Makefile @@ -546,6 +546,7 @@ BUILTIN_OBJS += builtin-rev-parse.o BUILTIN_OBJS += builtin-revert.o BUILTIN_OBJS += builtin-rm.o BUILTIN_OBJS += builtin-send-pack.o +BUILTIN_OBJS += builtin-shell.o BUILTIN_OBJS += builtin-shortlog.o BUILTIN_OBJS += builtin-show-branch.o BUILTIN_OBJS += builtin-show-ref.o @@ -821,7 +822,6 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X PROGRAMS += git-imap-send$X - PROGRAMS += git-shell$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl diff --git a/builtin-shell.c b/builtin-shell.c new file mode 100644 index 0000000000..3cf97d4f5d --- /dev/null +++ b/builtin-shell.c @@ -0,0 +1,90 @@ +#include "cache.h" +#include "quote.h" +#include "exec_cmd.h" +#include "strbuf.h" +#include "builtin.h" + +static int do_generic_cmd(const char *me, char *arg) +{ + const char *my_argv[4]; + + setup_path(); + if (!arg || !(arg = sq_dequote(arg))) + die("bad argument"); + if (prefixcmp(me, "git-")) + die("bad command"); + + my_argv[0] = me + 4; + my_argv[1] = arg; + my_argv[2] = NULL; + + return execv_git_cmd(my_argv); +} + +static int do_cvs_cmd(const char *me, char *arg) +{ + const char *cvsserver_argv[3] = { + "cvsserver", "server", NULL + }; + + if (!arg || strcmp(arg, "server")) + die("git-cvsserver only handles server: %s", arg); + + setup_path(); + return execv_git_cmd(cvsserver_argv); +} + + +static struct commands { + const char *name; + int (*exec)(const char *me, char *arg); +} cmd_list[] = { + { "git-receive-pack", do_generic_cmd }, + { "git-upload-pack", do_generic_cmd }, + { "cvs", do_cvs_cmd }, + { NULL }, +}; + +int cmd_shell(int argc, const char **argv, const char *prefix) +{ + char *prog; + struct commands *cmd; + + /* + * Special hack to pretend to be a CVS server + */ + if (argc == 2 && !strcmp(argv[1], "cvs server")) + argv--; + + /* + * We do not accept anything but "-c" followed by "cmd arg", + * where "cmd" is a very limited subset of git commands. + */ + else if (argc != 3 || strcmp(argv[1], "-c")) + die("What do you think I am? A shell?"); + + prog = xstrdup(argv[2]); + if (!strncmp(prog, "git", 3) && isspace(prog[3])) + /* Accept "git foo" as if the caller said "git-foo". */ + prog[3] = '-'; + + for (cmd = cmd_list ; cmd->name ; cmd++) { + int len = strlen(cmd->name); + char *arg; + if (strncmp(cmd->name, prog, len)) + continue; + arg = NULL; + switch (prog[len]) { + case '\0': + arg = NULL; + break; + case ' ': + arg = prog + len + 1; + break; + default: + continue; + } + exit(cmd->exec(cmd->name, arg)); + } + die("unrecognized command '%s'", prog); +} diff --git a/builtin.h b/builtin.h index f3502d305e..2b57a5eb6b 100644 --- a/builtin.h +++ b/builtin.h @@ -88,6 +88,7 @@ extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_send_pack(int argc, const char **argv, const char *prefix); +extern int cmd_shell(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 37b1d76a08..89e4645736 100644 --- a/git.c +++ b/git.c @@ -338,6 +338,7 @@ static void handle_internal_command(int argc, const char **argv) { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, + { "shell", cmd_shell }, { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, diff --git a/shell.c b/shell.c deleted file mode 100644 index 0f6a727a8c..0000000000 --- a/shell.c +++ /dev/null @@ -1,89 +0,0 @@ -#include "cache.h" -#include "quote.h" -#include "exec_cmd.h" -#include "strbuf.h" - -static int do_generic_cmd(const char *me, char *arg) -{ - const char *my_argv[4]; - - setup_path(); - if (!arg || !(arg = sq_dequote(arg))) - die("bad argument"); - if (prefixcmp(me, "git-")) - die("bad command"); - - my_argv[0] = me + 4; - my_argv[1] = arg; - my_argv[2] = NULL; - - return execv_git_cmd(my_argv); -} - -static int do_cvs_cmd(const char *me, char *arg) -{ - const char *cvsserver_argv[3] = { - "cvsserver", "server", NULL - }; - - if (!arg || strcmp(arg, "server")) - die("git-cvsserver only handles server: %s", arg); - - setup_path(); - return execv_git_cmd(cvsserver_argv); -} - - -static struct commands { - const char *name; - int (*exec)(const char *me, char *arg); -} cmd_list[] = { - { "git-receive-pack", do_generic_cmd }, - { "git-upload-pack", do_generic_cmd }, - { "cvs", do_cvs_cmd }, - { NULL }, -}; - -int main(int argc, char **argv) -{ - char *prog; - struct commands *cmd; - - /* - * Special hack to pretend to be a CVS server - */ - if (argc == 2 && !strcmp(argv[1], "cvs server")) - argv--; - - /* - * We do not accept anything but "-c" followed by "cmd arg", - * where "cmd" is a very limited subset of git commands. - */ - else if (argc != 3 || strcmp(argv[1], "-c")) - die("What do you think I am? A shell?"); - - prog = argv[2]; - if (!strncmp(prog, "git", 3) && isspace(prog[3])) - /* Accept "git foo" as if the caller said "git-foo". */ - prog[3] = '-'; - - for (cmd = cmd_list ; cmd->name ; cmd++) { - int len = strlen(cmd->name); - char *arg; - if (strncmp(cmd->name, prog, len)) - continue; - arg = NULL; - switch (prog[len]) { - case '\0': - arg = NULL; - break; - case ' ': - arg = prog + len + 1; - break; - default: - continue; - } - exit(cmd->exec(cmd->name, arg)); - } - die("unrecognized command '%s'", prog); -} -- cgit v1.2.1 From c35539eb10b0ab2a180e523f03ff65dc061bd47e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 11:47:55 -0700 Subject: diff --check: do not get confused by new blank lines in the middle The code remembered that the last diff output it saw was an empty line, and tried to reset that state whenever it sees a context line, a non-blank new line, or a new hunk. However, this codepath asks the underlying diff engine to feed diff without any context, and the "just saw an empty line" state was not reset if you added a new blank line in the last hunk of your patch, even if it is not the last line of the file. Signed-off-by: Junio C Hamano --- diff.c | 1 + t/t4015-diff-whitespace.sh | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/diff.c b/diff.c index bf5d5f15a3..f70e6b4912 100644 --- a/diff.c +++ b/diff.c @@ -1627,6 +1627,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, xdemitcb_t ecb; memset(&xecfg, 0, sizeof(xecfg)); + xecfg.ctxlen = 1; /* at least one context line */ xpp.flags = XDF_NEED_MINIMAL; ecb.outf = xdiff_outf; ecb.priv = &data; diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index a27fccc8dc..ec98509fd2 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -341,4 +341,15 @@ test_expect_success 'checkdiff detects trailing blank lines' ' git diff --check | grep "ends with blank" ' +test_expect_success 'checkdiff allows new blank lines' ' + git checkout x && + mv x y && + ( + echo "/* This is new */" && + echo "" && + cat y + ) >x && + git diff --check +' + test_done -- cgit v1.2.1 From e276c26b4b65711c27e3ef37e732d41eeae42094 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 12:29:27 -0700 Subject: for-each-ref: cope with tags with incomplete lines If you have a tag with a single, incomplete line as its payload, asking git-for-each-ref for its %(body) element accessed a NULL pointer. Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 4 +++- t/t6300-for-each-ref.sh | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 445039e19c..4d25ec51d0 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -459,8 +459,10 @@ static void find_subpos(const char *buf, unsigned long sz, const char **sub, con return; *sub = buf; /* first non-empty line */ buf = strchr(buf, '\n'); - if (!buf) + if (!buf) { + *body = ""; return; /* no body */ + } while (*buf == '\n') buf++; /* skip blank between subject and body */ *body = buf; diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index a3c8941c72..8ced59321e 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -262,4 +262,14 @@ for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do " done +test_expect_success 'an unusual tag with an incomplete line' ' + + git tag -m "bogo" bogo && + bogo=$(git cat-file tag bogo) && + bogo=$(printf "%s" "$bogo" | git mktag) && + git tag -f bogo "$bogo" && + git for-each-ref --format "%(body)" refs/tags/bogo + +' + test_done -- cgit v1.2.1 From 54988bdad7dc3f09e40752221c144bf470d73aa7 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Wed, 20 Aug 2008 13:55:33 -0400 Subject: decorate: allow const objects to be decorated We don't actually modify the struct object, so there is no reason not to accept const versions (and this allows other callsites, like the next patch, to use the decoration machinery). Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- decorate.c | 11 ++++++----- decorate.h | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/decorate.c b/decorate.c index d9668d2ef9..82d9e221ea 100644 --- a/decorate.c +++ b/decorate.c @@ -6,13 +6,13 @@ #include "object.h" #include "decorate.h" -static unsigned int hash_obj(struct object *obj, unsigned int n) +static unsigned int hash_obj(const struct object *obj, unsigned int n) { unsigned int hash = *(unsigned int *)obj->sha1; return hash % n; } -static void *insert_decoration(struct decoration *n, struct object *base, void *decoration) +static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration) { int size = n->size; struct object_decoration *hash = n->hash; @@ -44,7 +44,7 @@ static void grow_decoration(struct decoration *n) n->nr = 0; for (i = 0; i < old_size; i++) { - struct object *base = old_hash[i].base; + const struct object *base = old_hash[i].base; void *decoration = old_hash[i].decoration; if (!base) @@ -55,7 +55,8 @@ static void grow_decoration(struct decoration *n) } /* Add a decoration pointer, return any old one */ -void *add_decoration(struct decoration *n, struct object *obj, void *decoration) +void *add_decoration(struct decoration *n, const struct object *obj, + void *decoration) { int nr = n->nr + 1; @@ -65,7 +66,7 @@ void *add_decoration(struct decoration *n, struct object *obj, void *decoration) } /* Lookup a decoration pointer */ -void *lookup_decoration(struct decoration *n, struct object *obj) +void *lookup_decoration(struct decoration *n, const struct object *obj) { int j; diff --git a/decorate.h b/decorate.h index 1fa4ad9beb..e7328044ff 100644 --- a/decorate.h +++ b/decorate.h @@ -2,7 +2,7 @@ #define DECORATE_H struct object_decoration { - struct object *base; + const struct object *base; void *decoration; }; @@ -12,7 +12,7 @@ struct decoration { struct object_decoration *hash; }; -extern void *add_decoration(struct decoration *n, struct object *obj, void *decoration); -extern void *lookup_decoration(struct decoration *n, struct object *obj); +extern void *add_decoration(struct decoration *n, const struct object *obj, void *decoration); +extern void *lookup_decoration(struct decoration *n, const struct object *obj); #endif -- cgit v1.2.1 From 25b3d4d6f39d70c4d46dee48570ae7aeeb4a6b58 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 14:13:42 -0700 Subject: completion: find out supported merge strategies correctly "git-merge" is a binary executable these days, and looking for assignment to $all_strategies variable with grep/sed does not work well. When asked for an unknown strategy, pre-1.6.0 and post-1.6.0 "git merge" commands respectively say: $ $HOME/git-snap-v1.5.6.5/bin/git merge -s help available strategies are: recur recursive octopus resolve stupid ours subtree $ $HOME/git-snap-v1.6.0/bin/git merge -s help Could not find merge strategy 'help'. Available strategies are: recursive octopus resolve ours subtree. both on their standard error stream. We can use this to learn what strategies are supported. The sed script is written in such a way that it catches both old and new message styles ("Available" vs "available", and the full stop at the end). It also allows future versions of "git merge" to line-wrap the list of strategies, and add extra comments, like this: $ $HOME/git-snap-v1.6.1/bin/git merge -s help Could not find merge strategy 'help'. Available strategies are: blame recursive octopus resolve ours subtree. Also you have custom strategies: theirs Make sure you spell strategy names correctly. Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 158b912841..a31004088a 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -271,15 +271,17 @@ __git_merge_strategies () echo "$__git_merge_strategylist" return fi - sed -n "/^all_strategies='/{ - s/^all_strategies='// - s/'// + git merge -s help 2>&1 | + sed -n -e '/[Aa]vailable strategies are: /,/^$/{ + s/\.$// + s/.*:// + s/^[ ]*// + s/[ ]*$// p - q - }" "$(git --exec-path)/git-merge" + }' } __git_merge_strategylist= -__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)" +__git_merge_strategylist=$(__git_merge_strategies 2>/dev/null) __git_complete_file () { -- cgit v1.2.1 From 9ca8f6079cdb199851636c719900472a9745885f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 15:09:28 -0700 Subject: "git-merge": allow fast-forwarding in a stat-dirty tree We used to refresh the index to clear stat-dirtyness before a fast-forward merge. Recent C rewrite forgot to do this. Signed-off-by: Junio C Hamano --- builtin-merge.c | 2 +- t/t7600-merge.sh | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/builtin-merge.c b/builtin-merge.c index dde0c7ed33..a201c6628d 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -566,6 +566,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote) if (read_cache_unmerged()) die("you need to resolve your current index first"); + refresh_cache(REFRESH_QUIET); fd = hold_locked_index(lock_file, 1); @@ -936,7 +937,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) hex, find_unique_abbrev(remoteheads->item->object.sha1, DEFAULT_ABBREV)); - refresh_cache(REFRESH_QUIET); strbuf_init(&msg, 0); strbuf_addstr(&msg, "Fast forward"); if (have_message) diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 5eeb6c2b27..fee8fb77d4 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -488,4 +488,14 @@ test_expect_success 'merge c1 with c1 and c2' ' test_debug 'gitk --all' +test_expect_success 'merge fast-forward in a dirty tree' ' + git reset --hard c0 && + mv file file1 && + cat file1 >file && + rm -f file1 && + git merge c2 +' + +test_debug 'gitk --all' + test_done -- cgit v1.2.1 From 71f463773a310de016da20136fd7160685f97faa Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Wed, 20 Aug 2008 17:36:25 +0200 Subject: Install templates with the user and group of the installing personality If 'make install' was run with sufficient privileges, then the installed templates, which are copied using 'tar', would receive the user and group of whoever built git. This instructs 'tar' to ignore the user and group that are recorded in the archive. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- templates/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/Makefile b/templates/Makefile index 9f3f1fc352..cc3fc3094c 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -48,4 +48,4 @@ clean: install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(template_instdir_SQ)' (cd blt && $(TAR) cf - .) | \ - (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xf -) + (cd '$(DESTDIR_SQ)$(template_instdir_SQ)' && umask 022 && $(TAR) xfo -) -- cgit v1.2.1 From 3a634dcf51fb0fbb66b5c9287de08c6230c90903 Mon Sep 17 00:00:00 2001 From: Tarmigan Casebolt Date: Tue, 19 Aug 2008 12:50:31 -0700 Subject: Add hints to revert documentation about other ways to undo changes Based on its name, people may read the 'git revert' documentation when they want to undo local changes, especially people who have used other SCM's. 'git revert' may not be what they had in mind, but git provides several other ways to undo changes to files. We can help them by pointing them towards the git commands that do what they might want to do. Cc: Daniel Barkalow Cc: Lea Wiemann Signed-off-by: Tarmigan Casebolt Signed-off-by: Junio C Hamano --- Documentation/git-revert.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index 98cfa3c0d0..caa07298a6 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -15,6 +15,15 @@ Given one existing commit, revert the change the patch introduces, and record a new commit that records it. This requires your working tree to be clean (no modifications from the HEAD commit). +Note: 'git revert' is used to record a new commit to reverse the +effect of an earlier commit (often a faulty one). If you want to +throw away all uncommitted changes in your working directory, you +should see linkgit:git-reset[1], particularly the '--hard' option. If +you want to extract specific files as they were in another commit, you +should see linkgit:git-checkout[1], specifically the 'git checkout + -- ' syntax. Take care with these alternatives as +both will discard uncommitted changes in your working directory. + OPTIONS ------- :: -- cgit v1.2.1 From 9b99e641c11044dba661f574f9098d362a3f0ef8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 15:19:00 -0700 Subject: Update draft release notes for 1.6.0.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.0.1.txt | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Documentation/RelNotes-1.6.0.1.txt b/Documentation/RelNotes-1.6.0.1.txt index 3ee85a7993..bac117e89d 100644 --- a/Documentation/RelNotes-1.6.0.1.txt +++ b/Documentation/RelNotes-1.6.0.1.txt @@ -4,12 +4,28 @@ GIT v1.6.0.1 Release Notes Fixes since v1.6.0 ------------------ -* ... +* "git diff --check" incorrectly detected new trailing blank lines when + whitespace check was in effect. + +* "git for-each-ref" tried to dereference NULL when asked for '%(body)" on + a tag with a single incomplete line as its payload. + +* "git format-patch" peeked before the beginning of a string when + "format.headers" variable is empty (a misconfiguration). + +* "git mailinfo" (hence "git am") was unhappy when MIME multipart message + contained garbage after the finishing boundary. + +* "git mailinfo" also was unhappy when the "From: " line only had a bare + e-mail address. + +* "git merge" did not refresh the index correctly when a merge resulted in + a fast-forward. Contains other various documentation fixes. -- exec >/var/tmp/1 -O=v1.6.0 +O=v1.6.0-14-g3a634dc echo O=$(git describe maint) git shortlog --no-merges $O..maint -- cgit v1.2.1 From ea3594e04184475226109a21e71c539ff5f139fd Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 16:32:15 -0700 Subject: Update draft release notes for 1.6.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.1.txt | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/Documentation/RelNotes-1.6.1.txt b/Documentation/RelNotes-1.6.1.txt index efaf9ac4f7..d37da039f6 100644 --- a/Documentation/RelNotes-1.6.1.txt +++ b/Documentation/RelNotes-1.6.1.txt @@ -4,6 +4,13 @@ GIT v1.6.1 Release Notes Updates since v1.6.0 -------------------- +When some commands (e.g. "git log", "git diff") spawn pager internally, we +used to make the pager the parent process of the git command that produces +output. This meant that the exit status of the whole thing comes from the +pager, not the underlying git command. We swapped the order of the +processes around and you will see the exit code from the command from now +on. + (subsystems) * ... @@ -18,16 +25,25 @@ Updates since v1.6.0 (performance) -* ... +* The underlying diff machinery to produce textual output has been + optimized, which would result in faster "git blame" processing. (usability, bells and whistles) -* ... +* "git checkout --track origin/hack" used to be a syntax error. It now + DWIMs to create a corresponding local branch "hack", i.e. acts as if you + said "git checkout --track -b hack origin/hack". -(internal) +* "git diff" learned to mimick --suppress-blank-empty from GNU diff via a + configuration option. -* ... +* "git imap-send" can optionally talk SSL. + +(internal) +* "git hash-object" learned to lie about the path being hashed, so that + correct gitattributes processing can be done while hashing contents + stored in a temporary file. Fixes since v1.6.0 ------------------ @@ -37,6 +53,6 @@ release, unless otherwise noted. -- exec >/var/tmp/1 -O=v1.6.0 +O=v1.6.0-48-ge28a867 echo O=$(git describe master) git shortlog --no-merges $O..master ^maint -- cgit v1.2.1 From 4dc1db0bd1fc4eedea1ae8ceeee43cb0ea3322ca Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Wed, 20 Aug 2008 19:34:30 -0500 Subject: revision.h: make show_early_output an extern which is defined in revision.c The variable show_early_output is defined in revision.c and should be declared extern in revision.h so that the linker does not complain about multiply defined variables. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- revision.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/revision.h b/revision.h index f64e8ce7ff..1b045669ae 100644 --- a/revision.h +++ b/revision.h @@ -119,7 +119,7 @@ struct rev_info { void read_revisions_from_stdin(struct rev_info *revs); typedef void (*show_early_output_fn_t)(struct rev_info *, struct commit_list *); -volatile show_early_output_fn_t show_early_output; +extern volatile show_early_output_fn_t show_early_output; extern void init_revisions(struct rev_info *revs, const char *prefix); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); -- cgit v1.2.1 From 131f9a108bba5a8b0bcba072696653ab3199911a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 20 Aug 2008 22:07:55 -0700 Subject: Fix "git-merge -s bogo" help text It does not make much sense to reuse the output code from "git help" to show the list of commands to the standard output while giving the error message before that to the standard error stream. This makes the output consistent to that of the 1.6.0 version of "git merge". Signed-off-by: Junio C Hamano --- builtin-merge.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index 1f9389bfd7..3e8db0d383 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -110,9 +110,17 @@ static struct strategy *get_strategy(const char *name) } } if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) { - - fprintf(stderr, "Could not find merge strategy '%s'.\n\n", name); - list_commands("strategies", longest, &main_cmds, &other_cmds); + fprintf(stderr, "Could not find merge strategy '%s'.\n", name); + fprintf(stderr, "Available strategies are:"); + for (i = 0; i < main_cmds.cnt; i++) + fprintf(stderr, " %s", main_cmds.names[i]->name); + fprintf(stderr, ".\n"); + if (other_cmds.cnt) { + fprintf(stderr, "Available custom strategies are:"); + for (i = 0; i < other_cmds.cnt; i++) + fprintf(stderr, " %s", other_cmds.names[i]->name); + fprintf(stderr, ".\n"); + } exit(1); } -- cgit v1.2.1 From 7c695619868d0b867c87b0bb83303e058e010ac5 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Tue, 19 Aug 2008 22:18:23 -0400 Subject: git-submodule.sh - Remove trailing / from URL if found git clone does not complain if a trailing '/' is included in the origin URL, but doing so causes resolution of a submodule's URL relative to the superproject to fail. Regardless of whether git is changed to remove the trailing / before recording the URL, we should avoid this issue in submodule as existing repositories can have this problem. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- git-submodule.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-submodule.sh b/git-submodule.sh index b40f876a2c..e4c31fb7db 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -35,7 +35,7 @@ resolve_relative_url () remote="${remote:-origin}" remoteurl=$(git config "remote.$remote.url") || die "remote ($remote) does not have a url defined in .git/config" - url="$1" + url="${1%/}" while test -n "$url" do case "$url" in -- cgit v1.2.1 From 711521e246ea7d7b9bb10a1607cdd0a6de6a9927 Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Wed, 20 Aug 2008 00:30:06 -0700 Subject: git-svn: fix dcommit to urls with embedded usernames Don't rely on the extracted URL from working_head_info since that has the username removed. Instead use the $gs->full_url method (as before with ba24e74 (git-svn: add ability to specify --commit-url for dcommit, 2008-08-07)) to give us the URL to commit to if --commit-url is not specified. Aditionally, since we clean usernames from URLs, checking the URL after rebase can fail because it doesn't match the URL we used to commit; so unconditionally provide a username-free URL for checking the result of the refetch. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 099fd02b3f..7a1d26db8b 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -421,7 +421,7 @@ sub cmd_dcommit { $head ||= 'HEAD'; my @refs; my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs); - $url = $_commit_url if defined $_commit_url; + $url = defined $_commit_url ? $_commit_url : $gs->full_url; my $last_rev = $_revision if defined $_revision; if ($url) { print "Committing to $url ...\n"; @@ -437,6 +437,8 @@ sub cmd_dcommit { "If these changes depend on each other, re-running ", "without --no-rebase may be required." } + my $expect_url = $url; + Git::SVN::remove_username($expect_url); while (1) { my $d = shift @$linear_refs or last; unless (defined $last_rev) { @@ -511,9 +513,9 @@ sub cmd_dcommit { $gs->refname, "\nBefore dcommitting"; } - if ($url_ ne $url) { + if ($url_ ne $expect_url) { fatal "URL mismatch after rebase: ", - "$url_ != $url"; + "$url_ != $expect_url"; } if ($uuid_ ne $uuid) { fatal "uuid mismatch after rebase: ", -- cgit v1.2.1 From 7c17205b64a0e668d8a6e59109043bd4f2af3a0c Mon Sep 17 00:00:00 2001 From: Kirill Smelkov Date: Wed, 20 Aug 2008 19:57:07 +0400 Subject: Teach "git diff -p" Python funcname patterns Find classes, functions, and methods definitions. Signed-off-by: Kirill Smelkov Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 2 ++ diff.c | 1 + 2 files changed, 3 insertions(+) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index db16b0ca5b..9279df5349 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -316,6 +316,8 @@ patterns are available: - `pascal` suitable for source code in the Pascal/Delphi language. +- `python` suitable for source code in the Python language. + - `ruby` suitable for source code in the Ruby language. - `tex` suitable for source code for LaTeX documents. diff --git a/diff.c b/diff.c index 5923fe281b..2215416441 100644 --- a/diff.c +++ b/diff.c @@ -1394,6 +1394,7 @@ static struct builtin_funcname_pattern { }, { "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$" }, { "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$" }, + { "python", "^\\s*\\(\\(class\\|def\\)\\s.*\\)$" }, { "ruby", "^\\s*\\(\\(class\\|module\\|def\\)\\s.*\\)$" }, }; -- cgit v1.2.1 From 1a1fcf4abeb2ed4ef6075970788711cf62405158 Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Wed, 20 Aug 2008 19:49:15 +0200 Subject: Teach "git diff -p" HTML funcname patterns Find lines with

..

tags. [jc: while at it, reordered entries to sort alphabetically.] Signed-off-by: Johan Herland Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 2 ++ diff.c | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 9279df5349..5495d695c6 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -322,6 +322,8 @@ patterns are available: - `tex` suitable for source code for LaTeX documents. +- `html` suitable for HTML/XHTML documents. + Performing a three-way merge ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/diff.c b/diff.c index 2215416441..18fa7a712c 100644 --- a/diff.c +++ b/diff.c @@ -1381,6 +1381,8 @@ static struct builtin_funcname_pattern { const char *name; const char *pattern; } builtin_funcname_pattern[] = { + { "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$" }, + { "html", "^\\s*\\(<[Hh][1-6]\\s.*>.*\\)$" }, { "java", "!^[ ]*\\(catch\\|do\\|for\\|if\\|instanceof\\|" "new\\|return\\|switch\\|throw\\|while\\)\n" "^[ ]*\\(\\([ ]*" @@ -1392,10 +1394,9 @@ static struct builtin_funcname_pattern { "\\|" "^\\(.*=[ \t]*\\(class\\|record\\).*\\)$" }, - { "bibtex", "\\(@[a-zA-Z]\\{1,\\}[ \t]*{\\{0,1\\}[ \t]*[^ \t\"@',\\#}{~%]*\\).*$" }, - { "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$" }, { "python", "^\\s*\\(\\(class\\|def\\)\\s.*\\)$" }, { "ruby", "^\\s*\\(\\(class\\|module\\|def\\)\\s.*\\)$" }, + { "tex", "^\\(\\\\\\(\\(sub\\)*section\\|chapter\\|part\\)\\*\\{0,1\\}{.*\\)$" }, }; static const char *diff_funcname_pattern(struct diff_filespec *one) -- cgit v1.2.1 From a81892dd8c37b6f13793739721b520fee3ce4c2c Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Wed, 20 Aug 2008 20:53:50 -0500 Subject: compat/snprintf.c: handle snprintf's that always return the # chars transmitted Some platforms provide a horribly broken snprintf. More broken than the platforms that return -1 when there is too little space in the target buffer for the formatted string. Some platforms provide an snprintf which _always_ returns the number of characters transmitted to the buffer, regardless of whether there was enough space or not. IRIX 6.5 is such a platform. IRIX does have a working snprintf(), but it is only provided when _NO_XOPEN5 evaluates to zero, and this only happens if _XOPEN_SOURCE is defined, but definition of _XOPEN_SOURCE prevents inclusion of many other common functions and defines. So it must be avoided. Work around these horribly broken snprintf implementations by detecting an snprintf call which results in the number of transmitted characters exactly equal to the length of our buffer and retrying with a larger buffer just to be safe. Signed-off-by: Brandon Casey Acked-by: Johannes Sixt Signed-off-by: Junio C Hamano --- compat/snprintf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compat/snprintf.c b/compat/snprintf.c index 580966e56a..357e733074 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -17,6 +17,8 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) if (maxsize > 0) { ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap); + if (ret == maxsize-1) + ret = -1; /* Windows does not NUL-terminate if result fills buffer */ str[maxsize-1] = 0; } @@ -34,6 +36,8 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) break; s = str; ret = vsnprintf(str, maxsize-SNPRINTF_SIZE_CORR, format, ap); + if (ret == maxsize-1) + ret = -1; } free(s); return ret; -- cgit v1.2.1 From 26463c8f7ca1d54a480ea9baf0d98da2b6454204 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 21 Aug 2008 16:21:48 +0200 Subject: Fix 'git help help' git help foo invokes man git-foo if foo is a git command, otherwise it invokes man gitfoo. 'help' is not a git command, but the manual page is called git-help, so add this special exception. Signed-off-by: Miklos Vajna Acked-by: Christian Couder Signed-off-by: Junio C Hamano --- help.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/help.c b/help.c index 3cb1962896..dc0786d800 100644 --- a/help.c +++ b/help.c @@ -555,7 +555,8 @@ static int is_git_command(const char *s) { load_command_list(); return is_in_cmdlist(&main_cmds, s) || - is_in_cmdlist(&other_cmds, s); + is_in_cmdlist(&other_cmds, s) || + !strcmp(s, "help"); } static const char *prepend(const char *prefix, const char *cmd) -- cgit v1.2.1 From 99b120af7081ea9eb03a5f2a605d2bab771cf634 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Thu, 21 Aug 2008 19:54:01 -0400 Subject: git-submodule.sh - Remove trailing / from URL if found git clone does not complain if a trailing '/' is included in the origin URL, but doing so causes resolution of a submodule's URL relative to the superproject to fail. Trailing /'s are likely when cloning locally using tab-completion, so the slash may appear in either superproject or submodule URL. So, ignore the trailing slash if it already exists in the superproject's URL, and don't record one for the submodule (which could itself have submodules...). The problem I'm trying to fix is that a number of folks have superprojects checked out where the recorded origin URL has a trailing /, and a submodule has its origin in a directory sitting right next to the superproject on the server. Thus, we have: superproject url = server:/public/super submodoule url = server:/public/sub1 However, in the checked out superproject's .git/config [remote "origin"] url = server:/public/super/ and for similar reasons, the submodule has its URL recorded in .gitmodules as [submodule "sub"] path = submodule1 url = ../sub1/ resolve_relative_url gets the submodule's recorded url as $1, which the caller retrieved from .gitmodules, and retrieves the superprojects origin from .git/config. So in this case resolve_relative_url has that: url = ../sub1/ remoteurl = server:/public/super/ So, without any patch, resolve_relative_url computes the submodule's URL as: server:/public/super/sub1/ rather than server:/public/sub1 In summary, it is essential that resolve_relative_url strip the trailing / from the superproject's url before starting, and beneficial if it assures that the result does not contain a trailing / as the submodule may itself also be a superproject. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- git-submodule.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index e4c31fb7db..46d75aba31 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -35,7 +35,8 @@ resolve_relative_url () remote="${remote:-origin}" remoteurl=$(git config "remote.$remote.url") || die "remote ($remote) does not have a url defined in .git/config" - url="${1%/}" + url="$1" + remoteurl=${remoteurl%/} while test -n "$url" do case "$url" in @@ -50,7 +51,7 @@ resolve_relative_url () break;; esac done - echo "$remoteurl/$url" + echo "$remoteurl/${url%/}" } # -- cgit v1.2.1 From 1352fdbe3b16f30bfad308b737bc79e7b980318a Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Thu, 21 Aug 2008 20:38:23 +0100 Subject: config.mak.in: Pass on LDFLAGS from configure The configure script allows you to specify flags to pass to the linker step in the LDFLAGS environment variable but this was being ignored in the Makefile. Now a make variable gets set to the value passed down from the configure script. Signed-off-by: Neil Roberts Signed-off-by: Junio C Hamano --- config.mak.in | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mak.in b/config.mak.in index b776149531..f67d673d01 100644 --- a/config.mak.in +++ b/config.mak.in @@ -3,6 +3,7 @@ CC = @CC@ CFLAGS = @CFLAGS@ +LDFLAGS = @LDFLAGS@ AR = @AR@ TAR = @TAR@ #INSTALL = @INSTALL@ # needs install-sh or install.sh in sources -- cgit v1.2.1 From ea360dd0538d03d25f512efe2f100beb3e7c2130 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 21 Aug 2008 08:40:44 -0700 Subject: Make reflog query '@{1219188291}' act as '@{2008.8.19.16:24:51.-0700}' As we support seconds-since-epoch in $GIT_COMMITTER_TIME we should also support it in a reflog @{...} style notation. We can easily tell this part from @{nth} style notation by looking to see if the value is unreasonably large for an @{nth} style notation. The value 100000000 was chosen as it is already used by date.c to disambiguate yyyymmdd format from a seconds-since-epoch time value. A reflog with 100,000,000 record entries is also simply not valid. Such a reflog would require at least 7.7 GB to store just the old and new SHA-1 values. So our randomly chosen upper limit for @{nth} notation is "big enough". Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_name.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 4fb77f8863..41b680915d 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -349,7 +349,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) else nth = -1; } - if (0 <= nth) + if (100000000 <= nth) { + at_time = nth; + nth = -1; + } else if (0 <= nth) at_time = 0; else { char *tmp = xstrndup(str + at + 2, reflog_len); -- cgit v1.2.1 From 4be636f42cd75f865204817c307eb1a7b464109c Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Thu, 21 Aug 2008 14:14:18 +0200 Subject: provide more errors for the "merge into empty head" case A squash merge into an unborn branch could be implemented by building the index from the merged-from branch, and doing a single commit, but this is not supported yet. A non-fast-forward merge into an unborn branch does not make any sense, because you cannot make a merge commit if you don't have a commit to use as the parent. Signed-off-by: Paolo Bonzini Signed-off-by: Junio C Hamano --- builtin-merge.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin-merge.c b/builtin-merge.c index a201c6628d..7759a0b1e9 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -833,6 +833,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (argc != 1) die("Can merge only exactly one commit into " "empty head"); + if (squash) + die("Squash commit into empty head not supported yet"); + if (!allow_fast_forward) + die("Non-fast-forward commit does not make sense into " + "an empty head"); remote_head = peel_to_type(argv[0], 0, NULL, OBJ_COMMIT); if (!remote_head) die("%s - not something we can merge", argv[0]); -- cgit v1.2.1 From 5a4a088add3bdcbe86ae7e87964ce4025ddbc389 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 21 Aug 2008 09:49:12 +0200 Subject: test-lib: do not remove trash_directory if called with --debug Sometimes you want to keep the trash directory, even if all tests passed. For example, when extending tests, it comes it quite handy. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- t/test-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/test-lib.sh b/t/test-lib.sh index 6212c46cc1..e2b106cb6a 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -491,7 +491,7 @@ fi # Test repository test="trash directory.$(basename "$0" .sh)" -remove_trash="$TEST_DIRECTORY/$test" +test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test" rm -fr "$test" || { trap - exit echo >&5 "FATAL: Cannot prepare test area" -- cgit v1.2.1 From a9da1663dfc869141749c768e9e0f52bb48218e3 Mon Sep 17 00:00:00 2001 From: Johannes Sixt Date: Thu, 21 Aug 2008 16:45:11 +0200 Subject: filter-branch: Grok special characters in tag names The tag rewriting code used a 'sed' expression to substitute the new tag name into the corresponding field of the annotated tag object. But this is problematic if the tag name contains special characters. In particular, if the tag name contained a slash, then the 'sed' expression had a syntax error. We now protect against this by using 'printf' to assemble the tag header. Signed-off-by: Johannes Sixt Signed-off-by: Junio C Hamano --- git-filter-branch.sh | 12 +++++++----- t/t7003-filter-branch.sh | 8 ++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index a324cf0596..2871a59e32 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -416,15 +416,17 @@ if [ "$filter_tag_name" ]; then echo "$ref -> $new_ref ($sha1 -> $new_sha1)" if [ "$type" = "tag" ]; then - new_sha1=$(git cat-file tag "$ref" | + new_sha1=$( ( printf 'object %s\ntype commit\ntag %s\n' \ + "$new_sha1" "$new_ref" + git cat-file tag "$ref" | sed -n \ -e "1,/^$/{ - s/^object .*/object $new_sha1/ - s/^type .*/type commit/ - s/^tag .*/tag $new_ref/ + /^object /d + /^type /d + /^tag /d }" \ -e '/^-----BEGIN PGP SIGNATURE-----/q' \ - -e 'p' | + -e 'p' ) | git mktag) || die "Could not create new tag object for $ref" if git cat-file tag "$ref" | \ diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index a0ab096c8f..f92d414e63 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -250,4 +250,12 @@ test_expect_success 'Tag name filtering strips gpg signature' ' test_cmp expect actual ' +test_expect_success 'Tag name filtering allows slashes in tag names' ' + git tag -m tag-with-slash X/1 && + git cat-file tag X/1 | sed -e s,X/1,X/2, > expect && + git filter-branch -f --tag-name-filter "echo X/2" && + git cat-file tag X/2 > actual && + test_cmp expect actual +' + test_done -- cgit v1.2.1 From 2cb1f36d5098060a4bac182da16ceed3197a57c2 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Thu, 21 Aug 2008 19:16:30 -0500 Subject: remote.c: add a function for deleting a refspec array and use it (twice) A number of call sites allocate memory for a refspec array, populate its members with heap memory, and then free only the refspec pointer while leaking the memory allocated for the member elements. Provide a function for freeing the elements of a refspec array and the array itself. Caution to callers: code paths must be checked to ensure that the refspec members "src" and "dst" can be passed to free. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- remote.c | 29 +++++++++++++++++++++++++++-- remote.h | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/remote.c b/remote.c index 105668f8a3..3ef09a44a1 100644 --- a/remote.c +++ b/remote.c @@ -449,6 +449,26 @@ static int verify_refname(char *name, int is_glob) return result; } +/* + * This function frees a refspec array. + * Warning: code paths should be checked to ensure that the src + * and dst pointers are always freeable pointers as well + * as the refspec pointer itself. + */ +void free_refspecs(struct refspec *refspec, int nr_refspec) +{ + int i; + + if (!refspec) + return; + + for (i = 0; i < nr_refspec; i++) { + free(refspec[i].src); + free(refspec[i].dst); + } + free(refspec); +} + static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify) { int i; @@ -567,7 +587,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp invalid: if (verify) { - free(rs); + /* + * nr_refspec must be greater than zero and i must be valid + * since it is only possible to reach this point from within + * the for loop above. + */ + free_refspecs(rs, i+1); return NULL; } die("Invalid refspec '%s'", refspec[i]); @@ -579,7 +604,7 @@ int valid_fetch_refspec(const char *fetch_refspec_str) struct refspec *refspec; refspec = parse_refspec_internal(1, fetch_refspec, 1, 1); - free(refspec); + free_refspecs(refspec, 1); return !!refspec; } diff --git a/remote.h b/remote.h index 091b1d041f..2601f6e76d 100644 --- a/remote.h +++ b/remote.h @@ -78,6 +78,7 @@ void ref_remove_duplicates(struct ref *ref_map); int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); struct refspec *parse_push_refspec(int nr_refspec, const char **refspec); +void free_refspecs(struct refspec *refspec, int nr_refspec); int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int nr_refspec, const char **refspec, int all); -- cgit v1.2.1 From 96cda0b1067b4b5a2f87785cc49e1e648d68ae1d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 21 Aug 2008 19:31:50 -0500 Subject: templates/Makefile: install is unnecessary, just use mkdir -p The native install on some platforms (namely IRIX 6.5) treats non-absolute paths as being relative to the root directory rather than relative to the current directory. Work around this by avoiding install in this case since it is unnecessary, and instead depend on the local umask setting and use mkdir. Tested-by: Brandon Casey Signed-off-by: Junio C Hamano --- templates/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/Makefile b/templates/Makefile index cc3fc3094c..0722a926f7 100644 --- a/templates/Makefile +++ b/templates/Makefile @@ -23,13 +23,13 @@ all: boilerplates.made custom bpsrc = $(filter-out %~,$(wildcard *--*)) boilerplates.made : $(bpsrc) - $(QUIET)ls *--* 2>/dev/null | \ + $(QUIET)umask 022 && ls *--* 2>/dev/null | \ while read boilerplate; \ do \ case "$$boilerplate" in *~) continue ;; esac && \ dst=`echo "$$boilerplate" | sed -e 's|^this|.|;s|--|/|g'` && \ dir=`expr "$$dst" : '\(.*\)/'` && \ - $(INSTALL) -d -m 755 blt/$$dir && \ + mkdir -p blt/$$dir && \ case "$$boilerplate" in \ *--) ;; \ *) cp -p $$boilerplate blt/$$dst ;; \ -- cgit v1.2.1 From f135aacb5ae30b54bac0dde7462b532d19e4c0d6 Mon Sep 17 00:00:00 2001 From: Eric Raible Date: Fri, 22 Aug 2008 10:25:06 -0700 Subject: Completion: add missing '=' for 'diff --diff-filter' Signed-off-by: Eric Raible Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index a31004088a..89858c237e 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -771,7 +771,7 @@ _git_diff () __gitcomp "--cached --stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check - --full-index --binary --abbrev --diff-filter + --full-index --binary --abbrev --diff-filter= --find-copies-harder --pickaxe-all --pickaxe-regex --text --ignore-space-at-eol --ignore-space-change --ignore-all-space --exit-code --quiet --ext-diff -- cgit v1.2.1 From a19a424010970a076a51afb4b378c9edcd908ff9 Mon Sep 17 00:00:00 2001 From: Jonathan del Strother Date: Fri, 22 Aug 2008 18:18:44 +0100 Subject: Revert "Convert output messages in merge-recursive to past tense." During a conflicting merge, you would typically see: Auto-merged foo.txt CONFLICT (content): Merge conflict in foo.txt Recorded preimage for 'foo.txt' Automatic merge failed; fix conflicts and then commit the result. and left wondering what happened to "foo.txt". Did it succeed, and then conflicted, and then what? This is because historically there was a progress bar displayed before the auto-merge is mentioned, and it was expected to take long time, before we can say "Auto-merged foo.txt". It turns out it was not the case, and the original wording "Auto-merging foo.txt" we used to have before 89f40be (Convert output messages in merge-recursive to past tense., 2007-01-14) is better. Acked-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-merge-recursive.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 43e55bf901..dfb363ee2f 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -729,13 +729,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (string_list_has_string(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s added as %s instead", + output(1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (string_list_has_string(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s added as %s instead", + output(1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -760,7 +760,7 @@ static void conflict_rename_dir(struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path); + output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); @@ -773,7 +773,7 @@ static void conflict_rename_rename_2(struct rename *ren1, { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renamed %s to %s and %s to %s instead", + output(1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path, 0); @@ -887,10 +887,10 @@ static int process_renames(struct string_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output(1, "Renamed %s->%s", src, ren1_dst); + output(1, "Renaming %s->%s", src, ren1_dst); if (mfi.merge) - output(2, "Auto-merged %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (content): merge conflict in %s", @@ -924,14 +924,14 @@ static int process_renames(struct string_list *a_renames, if (string_list_has_string(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s " + output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); conflict_rename_dir(ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { clean_merge = 0; - output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s " + output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -940,19 +940,19 @@ static int process_renames(struct string_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. " + output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output(1, "Added as %s instead", new_path); + output(1, "Adding as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. " - "Renamed %s->%s in %s", + output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", ren1_src, ren1_dst, branch1, ren2->pair->one->path, ren2->pair->two->path, branch2); conflict_rename_rename_2(ren1, branch1, ren2, branch2); @@ -986,9 +986,9 @@ static int process_renames(struct string_list *a_renames, output(3, "Skipped %s (merged same as existing)", ren1_dst); else { if (mfi.merge || !mfi.clean) - output(1, "Renamed %s => %s", ren1_src, ren1_dst); + output(1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output(2, "Auto-merged %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); @@ -1039,7 +1039,7 @@ static int process_entry(const char *path, struct stage_data *entry, /* Deleted in both or deleted in one and * unchanged in the other */ if (a_sha) - output(2, "Removed %s", path); + output(2, "Removing %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { @@ -1086,12 +1086,12 @@ static int process_entry(const char *path, struct stage_data *entry, const char *new_path = unique_path(path, add_branch); clean_merge = 0; output(1, "CONFLICT (%s): There is a directory with name %s in %s. " - "Added %s as %s", + "Adding %s as %s", conf, path, other_branch, path, new_path); remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output(2, "Added %s", path); + output(2, "Adding %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -1105,7 +1105,7 @@ static int process_entry(const char *path, struct stage_data *entry, reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output(2, "Auto-merged %s", path); + output(2, "Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); o.mode = o_mode; -- cgit v1.2.1 From 9188ed8962ed47c0f41c24eb1317aa07037da545 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 21 Aug 2008 19:23:20 +0200 Subject: Extend "checkout --track" DWIM to support more cases The code handles additionally "refs/remotes//name", "remotes//name", and "refs//name". Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 13 ++++++++++--- builtin-checkout.c | 26 +++++++++++++------------- cache.h | 1 + t/t7201-co.sh | 23 ++++++++++++++++++++++- 4 files changed, 46 insertions(+), 17 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 43d4502547..be54a0299f 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -64,9 +64,16 @@ OPTIONS given. Set it to `always` if you want this behavior when the start-point is either a local or remote branch. + -If no '-b' option was given, a name will be made up for you, by stripping -the part up to the first slash of the tracked branch. For example, if you -called 'git checkout --track origin/next', the branch name will be 'next'. +If no '-b' option was given, the name of the new branch will be +derived from the remote branch, by attempting to guess the name +of the branch on remote system. If "remotes/" or "refs/remotes/" +are prefixed, it is stripped away, and then the part up to the +next slash (which would be the nickname of the remote) is removed. +This would tell us to use "hack" as the local branch when branching +off of "origin/hack" (or "remotes/origin/hack", or even +"refs/remotes/origin/hack"). If the given name has no slash, or the above +guessing results in an empty name, the guessing is aborted. You can +exlicitly give a name with '-b' in such a case. --no-track:: Ignore the branch.autosetupmerge configuration variable. diff --git a/builtin-checkout.c b/builtin-checkout.c index e95eab9b1b..b380ad6e80 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -157,7 +157,7 @@ struct checkout_opts { int force; int writeout_error; - char *new_branch; + const char *new_branch; int new_branch_log; enum branch_track track; }; @@ -437,27 +437,27 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - opts.track = -1; + opts.track = BRANCH_TRACK_UNSPECIFIED; argc = parse_options(argc, argv, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); /* --track without -b should DWIM */ - if (opts.track && opts.track != -1 && !opts.new_branch) { - char *slash; - if (!argc || !strcmp(argv[0], "--")) + if (0 < opts.track && !opts.new_branch) { + const char *argv0 = argv[0]; + if (!argc || !strcmp(argv0, "--")) die ("--track needs a branch name"); - slash = strchr(argv[0], '/'); - if (slash && !prefixcmp(argv[0], "refs/")) - slash = strchr(slash + 1, '/'); - if (slash && !prefixcmp(argv[0], "remotes/")) - slash = strchr(slash + 1, '/'); - if (!slash || !slash[1]) + if (!prefixcmp(argv0, "refs/")) + argv0 += 5; + if (!prefixcmp(argv0, "remotes/")) + argv0 += 8; + argv0 = strchr(argv0, '/'); + if (!argv0 || !argv0[1]) die ("Missing branch name; try -b"); - opts.new_branch = slash + 1; + opts.new_branch = argv0 + 1; } - if (opts.track == -1) + if (opts.track == BRANCH_TRACK_UNSPECIFIED) opts.track = git_branch_track; if (opts.force && opts.merge) diff --git a/cache.h b/cache.h index 928ae9f148..a097a959bd 100644 --- a/cache.h +++ b/cache.h @@ -451,6 +451,7 @@ enum safe_crlf { extern enum safe_crlf safe_crlf; enum branch_track { + BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, BRANCH_TRACK_REMOTE, BRANCH_TRACK_ALWAYS, diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 943dd57aac..1dff84d2fd 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -340,9 +340,30 @@ test_expect_success \ test_expect_success \ 'checkout with --track fakes a sensible -b ' ' git update-ref refs/remotes/origin/koala/bear renamer && + git update-ref refs/new/koala/bear renamer && + git checkout --track origin/koala/bear && test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && - test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"' + test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" && + + git checkout master && git branch -D koala/bear && + + git checkout --track refs/remotes/origin/koala/bear && + test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && + test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" && + + git checkout master && git branch -D koala/bear && + + git checkout --track remotes/origin/koala/bear && + test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && + test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" && + + git checkout master && git branch -D koala/bear && + + git checkout --track refs/new/koala/bear && + test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" && + test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" +' test_expect_success \ 'checkout with --track, but without -b, fails with too short tracked name' ' -- cgit v1.2.1 From 34ad1afa712da208f29d8300b73a6753516564fb Mon Sep 17 00:00:00 2001 From: Dan Hensgen Date: Thu, 21 Aug 2008 23:32:00 -0400 Subject: git-merge documentation: more details about resolving conflicts Signed-off-by: Dan Hensgen Signed-off-by: Junio C Hamano --- Documentation/git-merge.txt | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 17a15acb07..685e1fed58 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -126,13 +126,25 @@ After seeing a conflict, you can do two things: up working tree changes made by 2. and 3.; 'git-reset --hard' can be used for this. - * Resolve the conflicts. `git diff` would report only the - conflicting paths because of the above 2. and 3. - Edit the working tree files into a desirable shape - ('git mergetool' can ease this task), 'git-add' or 'git-rm' - them, to make the index file contain what the merge result - should be, and run 'git-commit' to commit the result. + * Resolve the conflicts. Git will mark the conflicts in + the working tree. Edit the files into shape and + 'git-add' to the index. 'git-commit' to seal the deal. +You can work through the conflict with a number of tools: + + * Use a mergetool. 'git mergetool' to launch a graphical + mergetool which will work you through the merge. + + * Look at the diffs. 'git diff' will show a three-way diff, + highlighting changes from both the HEAD and remote versions. + + * Look at the diffs on their own. 'git log --merge -p ' + will show diffs first for the HEAD version and then the + remote version. + + * Look at the originals. 'git show :1:filename' shows the + common ancestor, 'git show :2:filename' shows the HEAD + version and 'git show :3:filename' shows the remote version. SEE ALSO -------- -- cgit v1.2.1 From 4b480c6716a7d8e20e7e510827ea81e7939f335a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 22 Aug 2008 15:45:53 -0400 Subject: discard revindex data when pack list changes This is needed to fix verify-pack -v with multiple pack arguments. Also, in theory, revindex data (if any) must be discarded whenever reprepare_packed_git() is called. In practice this is hard to trigger though. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-verify-pack.c | 3 ++- pack-revindex.c | 12 ++++++++++++ pack-revindex.h | 1 + sha1_file.c | 1 + t/t5300-pack-object.sh | 6 ++++++ 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c index f4ac595695..25a29f11a4 100644 --- a/builtin-verify-pack.c +++ b/builtin-verify-pack.c @@ -1,7 +1,7 @@ #include "builtin.h" #include "cache.h" #include "pack.h" - +#include "pack-revindex.h" #define MAX_CHAIN 50 @@ -129,6 +129,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) else { if (verify_one_pack(argv[1], verbose)) err = 1; + discard_revindex(); nothing_done = 0; } argc--; argv++; diff --git a/pack-revindex.c b/pack-revindex.c index cd300bdff5..6096b6224a 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -142,3 +142,15 @@ struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs) } while (lo < hi); die("internal error: pack revindex corrupt"); } + +void discard_revindex(void) +{ + if (pack_revindex_hashsz) { + int i; + for (i = 0; i < pack_revindex_hashsz; i++) + if (pack_revindex[i].revindex) + free(pack_revindex[i].revindex); + free(pack_revindex); + pack_revindex_hashsz = 0; + } +} diff --git a/pack-revindex.h b/pack-revindex.h index 36a514a6cf..8d5027ad91 100644 --- a/pack-revindex.h +++ b/pack-revindex.h @@ -7,5 +7,6 @@ struct revindex_entry { }; struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs); +void discard_revindex(void); #endif diff --git a/sha1_file.c b/sha1_file.c index 32e4664b1b..477d3fb4b0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -990,6 +990,7 @@ void prepare_packed_git(void) void reprepare_packed_git(void) { + discard_revindex(); prepare_packed_git_run_once = 0; prepare_packed_git(); } diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 645583f9d7..83abe5f25e 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -186,6 +186,12 @@ test_expect_success \ test-2-${packname_2}.idx \ test-3-${packname_3}.idx' +test_expect_success \ + 'verify pack -v' \ + 'git verify-pack -v test-1-${packname_1}.idx \ + test-2-${packname_2}.idx \ + test-3-${packname_3}.idx' + test_expect_success \ 'verify-pack catches mismatched .idx and .pack files' \ 'cat test-1-${packname_1}.idx >test-3.idx && -- cgit v1.2.1 From a7b3269c4b9acde052d75b6dc54c8f869b77eb44 Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Fri, 22 Aug 2008 00:30:50 -0700 Subject: git-submodule: replace duplicated code with a module_list function Several call sites in git-submodule.sh used the same idiom for getting submodule information: git ls-files --stage -- "$@" | grep '^160000 ' This patch removes this duplication by introducing a module_list function. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- git-submodule.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index 2d57d60458..2a3a197d10 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -53,6 +53,15 @@ resolve_relative_url () echo "$remoteurl/$url" } +# +# Get submodule info for registered submodules +# $@ = path to limit submodule list +# +module_list() +{ + git ls-files --stage -- "$@" | grep '^160000 ' +} + # # Map submodule path to submodule name # @@ -206,7 +215,7 @@ cmd_add() # cmd_foreach() { - git ls-files --stage | grep '^160000 ' | + module_list | while read mode sha1 stage path do if test -e "$path"/.git @@ -246,7 +255,7 @@ cmd_init() shift done - git ls-files --stage -- "$@" | grep '^160000 ' | + module_list "$@" | while read mode sha1 stage path do # Skip already registered paths @@ -304,7 +313,7 @@ cmd_update() esac done - git ls-files --stage -- "$@" | grep '^160000 ' | + module_list "$@" | while read mode sha1 stage path do name=$(module_name "$path") || exit @@ -569,7 +578,7 @@ cmd_status() shift done - git ls-files --stage -- "$@" | grep '^160000 ' | + module_list "$@" | while read mode sha1 stage path do name=$(module_name "$path") || exit -- cgit v1.2.1 From 893d340f2c735dc85b9360556ccd46cc0bf27fc0 Mon Sep 17 00:00:00 2001 From: Tor Arvid Lund Date: Thu, 21 Aug 2008 23:11:40 +0200 Subject: git-p4: Fix one-liner in p4_write_pipe function. The function built a p4 command string via the p4_build_cmd function, but ignored the result. Signed-off-by: Tor Arvid Lund Signed-off-by: Junio C Hamano --- contrib/fast-import/git-p4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index f9865b444f..46136d49bf 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -76,7 +76,7 @@ def write_pipe(c, str): def p4_write_pipe(c, str): real_cmd = p4_build_cmd(c) - return write_pipe(c, str) + return write_pipe(real_cmd, str) def read_pipe(c, ignore_error=False): if verbose: -- cgit v1.2.1 From 64ca23afda5bd7514115a4457abf37db21b989ac Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Aug 2008 13:05:10 -0700 Subject: discard_cache: reset lazy name_hash bit We forgot to reset name_hash_initialized bit when discarding the in-core index. Signed-off-by: Junio C Hamano --- read-cache.c | 1 + 1 file changed, 1 insertion(+) diff --git a/read-cache.c b/read-cache.c index 2c03ec3069..6c57095f73 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1243,6 +1243,7 @@ int discard_index(struct index_state *istate) istate->cache_nr = 0; istate->cache_changed = 0; istate->timestamp = 0; + istate->name_hash_initialized = 0; free_hash(&istate->name_hash); cache_tree_free(&(istate->cache_tree)); free(istate->alloc); -- cgit v1.2.1 From 913e0e99b6a6e63af6a062622a1f94bd78fd8052 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Aug 2008 12:57:30 -0700 Subject: unpack_trees(): protect the handcrafted in-core index from read_cache() unpack_trees() rebuilds the in-core index from scratch by allocating a new structure and finishing it off by copying the built one to the final index. The resulting in-core index is Ok for most use, but read_cache() does not recognize it as such. The function is meant to be no-op if you already have loaded the index, until you call discard_cache(). This change the way read_cache() detects an already initialized in-core index, by introducing an extra bit, and marks the handcrafted in-core index as initialized, to avoid this problem. A better fix in the longer term would be to change the read_cache() API so that it will always discard and re-read from the on-disk index to avoid confusion. But there are higher level API that have relied on the current semantics, and they and their users all need to get converted, which is outside the scope of 'maint' track. An example of such a higher level API is write_cache_as_tree(), which is used by git-write-tree as well as later Porcelains like git-merge, revert and cherry-pick. In the longer term, we should remove read_cache() from there and add one to cmd_write_tree(); other callers expect that the in-core index they prepared is what gets written as a tree so no other change is necessary for this particular codepath. The original version of this patch marked the index by pointing an otherwise wasted malloc'ed memory with o->result.alloc, but this version uses Linus's idea to use a new "initialized" bit, which is conceptually much cleaner. Signed-off-by: Junio C Hamano --- cache.h | 3 ++- read-cache.c | 4 +++- unpack-trees.c | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cache.h b/cache.h index 2475de9fa8..884fae826c 100644 --- a/cache.h +++ b/cache.h @@ -222,7 +222,8 @@ struct index_state { struct cache_tree *cache_tree; time_t timestamp; void *alloc; - unsigned name_hash_initialized : 1; + unsigned name_hash_initialized : 1, + initialized : 1; struct hash_table name_hash; }; diff --git a/read-cache.c b/read-cache.c index 2c03ec3069..35fec468b1 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1155,7 +1155,7 @@ int read_index_from(struct index_state *istate, const char *path) size_t mmap_size; errno = EBUSY; - if (istate->alloc) + if (istate->initialized) return istate->cache_nr; errno = ENOENT; @@ -1195,6 +1195,7 @@ int read_index_from(struct index_state *istate, const char *path) * index size */ istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr)); + istate->initialized = 1; src_offset = sizeof(*hdr); dst_offset = 0; @@ -1247,6 +1248,7 @@ int discard_index(struct index_state *istate) cache_tree_free(&(istate->cache_tree)); free(istate->alloc); istate->alloc = NULL; + istate->initialized = 0; /* no need to throw away allocated active_cache */ return 0; diff --git a/unpack-trees.c b/unpack-trees.c index cba0aca062..ef21c62195 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -376,6 +376,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options state.refresh_cache = 1; memset(&o->result, 0, sizeof(o->result)); + o->result.initialized = 1; if (o->src_index) o->result.timestamp = o->src_index->timestamp; o->merge_size = len; -- cgit v1.2.1 From 446247db78a733f44d2470afb1f1983d28058159 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Aug 2008 12:56:57 -0700 Subject: merge: fix numerus bugs around "trivial merge" area The "trivial merge" codepath wants to optimize itself by making an internal call to the read-tree machinery, but it does not read the index before doing so, and the codepath is never exercised. Incidentally, this failure to read the index upfront means that the safety to refuse doing anything when the index is unmerged does not kick in, either. These two problem are fixed by using read_cache_unmerged() that does read the index before checking if it is unmerged at the beginning of cmd_merge(). The primary logic of the merge, however, assumes that the process never reads the index in-core, and the call to write_cache_as_tree() it makes from write_tree_trivial() will always read from the on-disk index that is prepared the strategy back-ends. This assumption is now broken by the above fix. To fix this issue, we now call discard_cache() before calling write_tree_trivial() when it wants to write the on-disk index as a tree. When multiple strategies are tried, their results are evaluated by reading the resulting index and inspecting it. The codepath needs to make a call to read_cache() for each successful strategy, and for that to work, they need to discard_cache() the one read by the previous round. Also the "trivial merge" forgot that the current commit is one of the parents of the resulting commit. Signed-off-by: Junio C Hamano --- builtin-merge.c | 16 +++++++++------- t/t3030-merge-recursive.sh | 11 +++++++++++ t/t7600-merge.sh | 9 +++++++++ t/t7605-merge-resolve.sh | 4 +++- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index a201c6628d..b280444e10 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -564,8 +564,6 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote) struct dir_struct dir; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - if (read_cache_unmerged()) - die("you need to resolve your current index first"); refresh_cache(REFRESH_QUIET); fd = hold_locked_index(lock_file, 1); @@ -651,13 +649,15 @@ static void add_strategies(const char *string, unsigned attr) static int merge_trivial(void) { unsigned char result_tree[20], result_commit[20]; - struct commit_list parent; + struct commit_list *parent = xmalloc(sizeof(struct commit_list *)); write_tree_trivial(result_tree); printf("Wonderful.\n"); - parent.item = remoteheads->item; - parent.next = NULL; - commit_tree(merge_msg.buf, result_tree, &parent, result_commit); + parent->item = lookup_commit(head); + parent->next = xmalloc(sizeof(struct commit_list *)); + parent->next->item = remoteheads->item; + parent->next->next = NULL; + commit_tree(merge_msg.buf, result_tree, parent, result_commit); finish(result_commit, "In-index merge"); drop_save(); return 0; @@ -743,6 +743,7 @@ static int evaluate_result(void) int cnt = 0; struct rev_info rev; + discard_cache(); if (read_cache() < 0) die("failed to read the cache"); @@ -776,7 +777,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit_list **remotes = &remoteheads; setup_work_tree(); - if (unmerged_cache()) + if (read_cache_unmerged()) die("You are in the middle of a conflicted merge."); /* @@ -1073,6 +1074,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } /* Automerge succeeded. */ + discard_cache(); write_tree_trivial(result_tree); automerge_was_ok = 1; break; diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index aff360303a..f2880152b0 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -269,6 +269,17 @@ test_expect_success 'merge-recursive result' ' ' +test_expect_success 'fail if the index has unresolved entries' ' + + rm -fr [abcd] && + git checkout -f "$c1" && + + test_must_fail git merge "$c5" && + test_must_fail git merge "$c5" 2> out && + grep "You are in the middle of a conflicted merge" out + +' + test_expect_success 'merge-recursive remove conflict' ' rm -fr [abcd] && diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index fee8fb77d4..dbc90bc416 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -498,4 +498,13 @@ test_expect_success 'merge fast-forward in a dirty tree' ' test_debug 'gitk --all' +test_expect_success 'in-index merge' ' + git reset --hard c0 && + git merge --no-ff -s resolve c1 > out && + grep "Wonderful." out && + verify_parents $c0 $c1 +' + +test_debug 'gitk --all' + test_done diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh index ee21a107fd..f1f86ddb23 100755 --- a/t/t7605-merge-resolve.sh +++ b/t/t7605-merge-resolve.sh @@ -36,7 +36,9 @@ test_expect_success 'merge c1 to c2' ' git diff --exit-code && test -f c0.c && test -f c1.c && - test -f c2.c + test -f c2.c && + test 3 = $(git ls-tree -r HEAD | wc -l) && + test 3 = $(git ls-files | wc -l) ' test_expect_success 'merge c2 to c3 (fails)' ' -- cgit v1.2.1 From e596cdddf349b244dca854bf5a7aa9428b48fc50 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 23 Aug 2008 19:23:22 -0700 Subject: t7606: fix custom merge test Custom merge strategy does not even kick in when the merge is truly trivial. The test depended on the behaviour in the git-merge rewritten in C that broke the trivial merge completely. Make the test to work on a non-trivial merge to make sure the strategy kicks in. Signed-off-by: Junio C Hamano --- t/t7606-merge-custom.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh index 07e0fc7974..de9b6ed5ba 100755 --- a/t/t7606-merge-custom.sh +++ b/t/t7606-merge-custom.sh @@ -24,8 +24,9 @@ test_expect_success 'setup' ' git commit -m c1 && git tag c1 && git reset --hard c0 && + echo c1c1 >c1.c && echo c2 >c2.c && - git add c2.c && + git add c1.c c2.c && git commit -m c2 && git tag c2 ' @@ -38,8 +39,10 @@ test_expect_success 'merge c2 with a custom strategy' ' test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && test "$(git rev-parse c2^{tree})" = "$(git rev-parse HEAD^{tree})" && git diff --exit-code && + git diff --exit-code c2 HEAD && + git diff --exit-code c2 && test -f c0.c && - test ! -f c1.c && + grep c1c1 c1.c && test -f c2.c ' -- cgit v1.2.1 From 7d770163271db1ec3608d3b2b77e306346e24a4a Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Sun, 24 Aug 2008 00:07:55 +0200 Subject: Makefile: enable SNPRINTF_RETURNS_BOGUS for HP-UX In 81cc66a, customization has been added to Makefile for supporting HP-UX, but git commit is still problematic. This should fix the issue. Signed-off-by: Miklos Vajna Acked-by: Robert Schiele Signed-off-by: Junio C Hamano --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 53ab4b5536..2cef0187d1 100644 --- a/Makefile +++ b/Makefile @@ -727,6 +727,7 @@ ifeq ($(uname_S),HP-UX) NO_UNSETENV = YesPlease NO_HSTRERROR = YesPlease NO_SYS_SELECT_H = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease endif ifneq (,$(findstring MINGW,$(uname_S))) NO_MMAP = YesPlease -- cgit v1.2.1 From 5e568f9e3027797842807213ce590140c9daf9ce Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 23:21:21 +0400 Subject: Respect core.autocrlf in combined diff Fix git-diff to make it produce useful 3-way diffs for merge conflicts in repositories with autocrlf enabled. Otherwise it always reports that the whole file was changed, because it uses the contents from the working tree without necessary conversion. Signed-off-by: Alexander Gavrilov Signed-off-by: Junio C Hamano --- combine-diff.c | 12 ++++++++++++ t/t4015-diff-whitespace.sh | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/combine-diff.c b/combine-diff.c index 9f80a1c5e3..4dfc330867 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -727,6 +727,18 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, die("early EOF '%s'", elem->path); result[len] = 0; + + /* If not a fake symlink, apply filters, e.g. autocrlf */ + if (is_file) { + struct strbuf buf; + + strbuf_init(&buf, 0); + if (convert_to_git(elem->path, result, len, &buf, safe_crlf)) { + free(result); + result = strbuf_detach(&buf, &len); + result_size = len; + } + } } else { deleted_file: diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index ec98509fd2..b1cbd36d17 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -352,4 +352,20 @@ test_expect_success 'checkdiff allows new blank lines' ' git diff --check ' +test_expect_success 'combined diff with autocrlf conversion' ' + + git reset --hard && + echo >x hello && + git commit -m "one side" x && + git checkout HEAD^ && + echo >x goodbye && + git commit -m "the other side" x && + git config core.autocrlf true && + test_must_fail git merge master && + + git diff | sed -e "1,/^@@@/d" >actual && + ! grep "^-" actual + +' + test_done -- cgit v1.2.1 From f5f7e4a18cf656747be8f8447ca304ddf716c742 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Sun, 24 Aug 2008 16:12:23 +0200 Subject: Clean up the git-p4 documentation This patch massages the documentation a bit for improved readability and cleans it up from outdated options/commands. Signed-off-by: Simon Hausmann Signed-off-by: Junio C Hamano --- contrib/fast-import/git-p4.txt | 69 +++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt index ac551d45f1..49b335921a 100644 --- a/contrib/fast-import/git-p4.txt +++ b/contrib/fast-import/git-p4.txt @@ -3,14 +3,16 @@ git-p4 - Perforce <-> Git converter using git-fast-import Usage ===== -git-p4 supports two main modes: Importing from Perforce to a Git repository is -done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back -to Perforce is done using "git-p4 submit". +git-p4 can be used in two different ways: + +1) To import changes from Perforce to a Git repository, using "git-p4 sync". + +2) To submit changes from Git back to Perforce, using "git-p4 submit". Importing ========= -You can simply start with +Simply start with git-p4 clone //depot/path/project @@ -18,11 +20,18 @@ or git-p4 clone //depot/path/project myproject -This will create an empty git repository in a subdirectory called "project" (or -"myproject" with the second command), import the head revision from the -specified perforce path into a git "p4" branch (remotes/p4 actually), create a -master branch off it and check it out. If you want the entire history (not just -the head revision) then you can simply append a "@all" to the depot path: +This will: + +1) Create an empty git repository in a subdirectory called "project" (or +"myproject" with the second command) + +2) Import the head revision from the given Perforce path into a git branch +called "p4" (remotes/p4 actually) + +3) Create a master branch based on it and check it out. + +If you want the entire history (not just the head revision) then you can simply +append a "@all" to the depot path: git-p4 clone //depot/project/main@all myproject @@ -37,31 +46,40 @@ If you want more control you can also use the git-p4 sync command directly: This will import the current head revision of the specified depot path into a "remotes/p4/master" branch of your git repository. You can use the ---branch=mybranch option to use a different branch. +--branch=mybranch option to import into a different branch. -If you want to import the entire history of a given depot path just use +If you want to import the entire history of a given depot path simply use: git-p4 sync //path/in/depot@all + +Note: + To achieve optimal compression you may want to run 'git repack -a -d -f' after a big import. This may take a while. -Support for Perforce integrations is still work in progress. Don't bother -trying it unless you want to hack on it :) - Incremental Imports =================== -After an initial import you can easily synchronize your git repository with -newer changes from the Perforce depot by just calling +After an initial import you can continue to synchronize your git repository +with newer changes from the Perforce depot by just calling git-p4 sync in your git repository. By default the "remotes/p4/master" branch is updated. -It is recommended to run 'git repack -a -d -f' from time to time when using -incremental imports to optimally combine the individual git packs that each -incremental import creates through the use of git-fast-import. +Advanced Setup +============== + +Suppose you have a periodically updated git repository somewhere, containing a +complete import of a Perforce project. This repository can be cloned and used +with git-p4. When updating the cloned repository with the "sync" command, +git-p4 will try to fetch changes from the original repository first. The git +protocol used with this is usually faster than importing from Perforce +directly. + +This behaviour can be disabled by setting the "git-p4.syncFromOrigin" git +configuration variable to "false". Updating ======== @@ -79,7 +97,7 @@ Submitting ========== git-p4 has support for submitting changes from a git repository back to the -Perforce depot. This requires a Perforce checkout separate to your git +Perforce depot. This requires a Perforce checkout separate from your git repository. To submit all changes that are in the current git branch but not in the "p4" branch (or "origin" if "p4" doesn't exist) simply call @@ -97,17 +115,6 @@ continue importing the remaining changes with git-p4 submit --continue -After submitting you should sync your perforce import branch ("p4" or "origin") -from Perforce using git-p4's sync command. - -If you have changes in your working directory that you haven't committed into -git yet but that you want to commit to Perforce directly ("quick fixes") then -you do not have to go through the intermediate step of creating a git commit -first but you can just call - - git-p4 submit --direct - - Example ======= -- cgit v1.2.1 From 32818085ee88dd98c48a0838fba3c4673d5e819c Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 24 Aug 2008 00:38:06 -0500 Subject: Documentation: clarify pager. configuration It was not obvious from the text that pager. is a boolean setting. While we're changing the description, make some other improvements: lest we forget and fret, clarify that -p and pager. do not kick in when stdout is not a tty; point to related core.pager and GIT_PAGER settings; use renamed --paginate option. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 676c39bb84..167958282b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -979,9 +979,11 @@ pack.packSizeLimit:: linkgit:git-repack[1]. pager.:: - Allows to set your own pager preferences for each command, overriding - the default. If `\--pager` or `\--no-pager` is specified on the command - line, it takes precedence over this option. + Allows turning on or off pagination of the output of a + particular git subcommand when writing to a tty. If + `\--paginate` or `\--no-pager` is specified on the command line, + it takes precedence over this option. To disable pagination for + all commands, set `core.pager` or 'GIT_PAGER' to "`cat`". pull.octopus:: The default merge strategy to use when pulling multiple branches -- cgit v1.2.1 From ab54cd6c4d54b7d9bf6dccfa7ea54f209ea76601 Mon Sep 17 00:00:00 2001 From: Jonathan Nieder Date: Sun, 24 Aug 2008 00:28:32 -0500 Subject: Documentation: clarify pager configuration The unwary user may not know how to disable the -FRSX options. Signed-off-by: Jonathan Nieder Signed-off-by: Junio C Hamano --- Documentation/config.txt | 9 +++++++-- Documentation/git.txt | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 167958282b..81f981509a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -358,8 +358,13 @@ core.editor:: `EDITOR` environment variables and then finally `vi`. core.pager:: - The command that git will use to paginate output. Can be overridden - with the `GIT_PAGER` environment variable. + The command that git will use to paginate output. Can + be overridden with the `GIT_PAGER` environment + variable. Note that git sets the `LESS` environment + variable to `FRSX` if it is unset when it runs the + pager. One can change these settings by setting the + `LESS` variable to some other value or by giving the + `core.pager` option a value such as "`less -+FRSX`". core.whitespace:: A comma separated list of common whitespace problems to diff --git a/Documentation/git.txt b/Documentation/git.txt index 1bc295dd54..363a785452 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -497,7 +497,8 @@ other 'GIT_PAGER':: This environment variable overrides `$PAGER`. If it is set to an empty string or to the value "cat", git will not launch - a pager. + a pager. See also the `core.pager` option in + linkgit:git-config[1]. 'GIT_SSH':: If this environment variable is set then 'git-fetch' -- cgit v1.2.1 From 98fcf840af443a0a93b9a6cd1fada5af826383f3 Mon Sep 17 00:00:00 2001 From: Mark Levedahl Date: Sun, 24 Aug 2008 14:46:10 -0400 Subject: git-submodule - Use "get_default_remote" from git-parse-remote Resolve_relative_url was using its own code for this function, but this is duplication with the best result that this continues to work. Replace with the common function provided by git-parse-remote. Signed-off-by: Mark Levedahl Signed-off-by: Junio C Hamano --- git-submodule.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/git-submodule.sh b/git-submodule.sh index 2a3a197d10..59fe7b335c 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -9,6 +9,7 @@ USAGE="[--quiet] [--cached] \ [--] [...]|[foreach ]" OPTIONS_SPEC= . git-sh-setup +. git-parse-remote require_work_tree command= @@ -30,9 +31,7 @@ say() # Resolve relative url by appending to parent's url resolve_relative_url () { - branch="$(git symbolic-ref HEAD 2>/dev/null)" - remote="$(git config branch.${branch#refs/heads/}.remote)" - remote="${remote:-origin}" + remote=$(get_default_remote) remoteurl=$(git config "remote.$remote.url") || die "remote ($remote) does not have a url defined in .git/config" url="$1" -- cgit v1.2.1 From 5760a6b094736e6f59eb32c7abb4cdbb7fca1627 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 24 Aug 2008 14:47:24 -0700 Subject: GIT 1.6.0.1 Signed-off-by: Junio C Hamano --- Documentation/RelNotes-1.6.0.1.txt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Documentation/RelNotes-1.6.0.1.txt b/Documentation/RelNotes-1.6.0.1.txt index bac117e89d..49d7a1cafa 100644 --- a/Documentation/RelNotes-1.6.0.1.txt +++ b/Documentation/RelNotes-1.6.0.1.txt @@ -4,6 +4,9 @@ GIT v1.6.0.1 Release Notes Fixes since v1.6.0 ------------------ +* "git diff --cc" did not honor content mangling specified by + gitattributes and core.autocrlf when reading from the work tree. + * "git diff --check" incorrectly detected new trailing blank lines when whitespace check was in effect. @@ -13,6 +16,8 @@ Fixes since v1.6.0 * "git format-patch" peeked before the beginning of a string when "format.headers" variable is empty (a misconfiguration). +* "git help help" did not work correctly. + * "git mailinfo" (hence "git am") was unhappy when MIME multipart message contained garbage after the finishing boundary. @@ -22,10 +27,10 @@ Fixes since v1.6.0 * "git merge" did not refresh the index correctly when a merge resulted in a fast-forward. -Contains other various documentation fixes. +* "git merge" did not resolve a truly trivial merges that can be done + without content level merges. --- -exec >/var/tmp/1 -O=v1.6.0-14-g3a634dc -echo O=$(git describe maint) -git shortlog --no-merges $O..maint +* "git svn dcommit" to a repository with URL that has embedded usernames + did not work correctly. + +Contains other various documentation fixes. -- cgit v1.2.1 From 27a6ed492b3962d8214a196e57e8969ff9772249 Mon Sep 17 00:00:00 2001 From: Tommi Virtanen Date: Sun, 24 Aug 2008 23:23:25 +0300 Subject: Install git-shell in bindir, too /etc/passwd shell field must be something execable, you can't enter "/usr/bin/git shell" there. git-shell must be present as a separate executable, or it is useless. Signed-off-by: Tommi Virtanen Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ff71e6acd1..7a6cbb6f03 100644 --- a/Makefile +++ b/Makefile @@ -1359,7 +1359,7 @@ install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' - $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install ifndef NO_TCLTK -- cgit v1.2.1 From e681cb7d6a00b4860c84bf8ca7b8d8dab366c9fc Mon Sep 17 00:00:00 2001 From: Gustaf Hendeby Date: Fri, 22 Aug 2008 22:10:27 +0200 Subject: git-gui: Teach git gui about file type changes Signed-off-by: Gustaf Hendeby Signed-off-by: Shawn O. Pearce --- git-gui.sh | 18 +++++++++++++++++- lib/commit.tcl | 2 ++ lib/index.tcl | 7 +++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/git-gui.sh b/git-gui.sh index ad65aaad5a..8ad6567edd 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -1619,6 +1619,15 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask +image create bitmap file_statechange -background white -foreground green -data { +#define file_merge_width 14 +#define file_merge_height 15 +static unsigned char file_statechange_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10, + 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list @@ -1627,12 +1636,14 @@ set all_icons(A$ui_index) file_fulltick set all_icons(M$ui_index) file_fulltick set all_icons(D$ui_index) file_removed set all_icons(U$ui_index) file_merge +set all_icons(T$ui_index) file_statechange set all_icons(_$ui_workdir) file_plain set all_icons(M$ui_workdir) file_mod set all_icons(D$ui_workdir) file_question set all_icons(U$ui_workdir) file_merge set all_icons(O$ui_workdir) file_plain +set all_icons(T$ui_workdir) file_statechange set max_status_desc 0 foreach i { @@ -1643,6 +1654,9 @@ foreach i { {MM {mc "Portions staged for commit"}} {MD {mc "Staged for commit, missing"}} + {_T {mc "File type changed, not staged"}} + {T_ {mc "File type changed, staged"}} + {_O {mc "Untracked, not staged"}} {A_ {mc "Staged for commit"}} {AM {mc "Portions staged for commit"}} @@ -2757,7 +2771,9 @@ proc popup_diff_menu {ctxm x y X Y} { if {$::is_3way_diff || $current_diff_path eq {} || ![info exists file_states($current_diff_path)] - || {_O} eq [lindex $file_states($current_diff_path) 0]} { + || {_O} eq [lindex $file_states($current_diff_path) 0] + || {_T} eq [lindex $file_states($current_diff_path) 0] + || {T_} eq [lindex $file_states($current_diff_path) 0]} { set s disabled } else { set s normal diff --git a/lib/commit.tcl b/lib/commit.tcl index 40a7103557..f4ab70784c 100644 --- a/lib/commit.tcl +++ b/lib/commit.tcl @@ -149,6 +149,7 @@ The rescan will be automatically started now. _? {continue} A? - D? - + T_ - M? {set files_ready 1} U? { error_popup [mc "Unmerged files cannot be committed. @@ -428,6 +429,7 @@ A rescan will be automatically started now. __ - A_ - M_ - + T_ - D_ { unset file_states($path) catch {unset selected_paths($path)} diff --git a/lib/index.tcl b/lib/index.tcl index 3c1fce7475..7c27f2af6c 100644 --- a/lib/index.tcl +++ b/lib/index.tcl @@ -99,6 +99,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch after} { switch -glob -- [lindex $s 0] { A? {set new _O} M? {set new _M} + T_ {set new _T} D_ {set new _D} D? {set new _?} ?? {continue} @@ -162,6 +163,7 @@ proc write_update_index {fd pathList totalCnt batch after} { ?D {set new D_} _O - AM {set new A_} + _T {set new T_} U? { if {[file exists $path]} { set new M_ @@ -231,6 +233,7 @@ proc write_checkout_index {fd pathList totalCnt batch after} { switch -glob -- [lindex $file_states($path) 0] { U? {continue} ?M - + ?T - ?D { puts -nonewline $fd "[encoding convertto $path]\0" display_file $path ?_ @@ -252,6 +255,7 @@ proc unstage_helper {txt paths} { switch -glob -- [lindex $file_states($path) 0] { A? - M? - + T_ - D? { lappend pathList $path if {$path eq $current_diff_path} { @@ -296,6 +300,7 @@ proc add_helper {txt paths} { _O - ?M - ?D - + ?T - U? { lappend pathList $path if {$path eq $current_diff_path} { @@ -336,6 +341,7 @@ proc do_add_all {} { switch -glob -- [lindex $file_states($path) 0] { U? {continue} ?M - + ?T - ?D {lappend paths $path} } } @@ -353,6 +359,7 @@ proc revert_helper {txt paths} { switch -glob -- [lindex $file_states($path) 0] { U? {continue} ?M - + ?T - ?D { lappend pathList $path if {$path eq $current_diff_path} { -- cgit v1.2.1 From a9c80b83d4bb40e0ff696520de3ef2b0f74ff8c6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:30:00 +0400 Subject: git-gui: Support starting gitk from Gui Blame Add a context menu command to load commits that are within a certain time range from the selected commit into gitk. It can be useful for understanding of the code, especially if the repository is imported from a VCS that does not support atomic commits. Signed-off-by: Alexander Gavrilov Signed-off-by: Shawn O. Pearce --- git-gui.sh | 1 + lib/blame.tcl | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- lib/option.tcl | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index 8ad6567edd..b0207ac36a 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -668,6 +668,7 @@ set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.fastcopyblame) false set default_config(gui.copyblamethreshold) 40 +set default_config(gui.blamehistoryctx) 7 set default_config(gui.diffcontext) 5 set default_config(gui.commitmsgwidth) 75 set default_config(gui.newbranchtemplate) {} diff --git a/lib/blame.tcl b/lib/blame.tcl index b6e42cbc8f..c8975cfecd 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -259,6 +259,9 @@ constructor new {i_commit i_path} { $w.ctxm add command \ -label [mc "Do Full Copy Detection"] \ -command [cb _fullcopyblame] + $w.ctxm add command \ + -label [mc "Show History Context"] \ + -command [cb _gitkcommit] foreach i $w_columns { for {set g 0} {$g < [llength $group_colors]} {incr g} { @@ -905,10 +908,14 @@ method _showcommit {cur_w lno} { } } -method _copycommit {} { +method _get_click_amov_info {} { set pos @$::cursorX,$::cursorY set lno [lindex [split [$::cursorW index $pos] .] 0] - set dat [lindex $amov_data $lno] + return [lindex $amov_data $lno] +} + +method _copycommit {} { + set dat [_get_click_amov_info $this] if {$dat ne {}} { clipboard clear clipboard append \ @@ -918,6 +925,50 @@ method _copycommit {} { } } +method _format_offset_date {base offset} { + set exval [expr {$base + $offset*24*60*60}] + return [clock format $exval -format {%Y-%m-%d}] +} + +method _gitkcommit {} { + set dat [_get_click_amov_info $this] + if {$dat ne {}} { + set cmit [lindex $dat 0] + set radius [get_config gui.blamehistoryctx] + set cmdline [list --select-commit=$cmit] + + if {$radius > 0} { + set author_time {} + set committer_time {} + + catch {set author_time $header($cmit,author-time)} + catch {set committer_time $header($cmit,committer-time)} + + if {$committer_time eq {}} { + set committer_time $author_time + } + + set after_time [_format_offset_date $this $committer_time [expr {-$radius}]] + set before_time [_format_offset_date $this $committer_time $radius] + + lappend cmdline --after=$after_time --before=$before_time + } + + lappend cmdline $cmit + + set base_rev "HEAD" + if {$commit ne {}} { + set base_rev $commit + } + + if {$base_rev ne $cmit} { + lappend cmdline $base_rev + } + + do_gitk $cmdline + } +} + method _show_tooltip {cur_w pos} { if {$tooltip_wm ne {}} { _open_tooltip $this $cur_w diff --git a/lib/option.tcl b/lib/option.tcl index ffb3f00ff0..eb958ffd47 100644 --- a/lib/option.tcl +++ b/lib/option.tcl @@ -125,6 +125,7 @@ proc do_options {} { {b gui.matchtrackingbranch {mc "Match Tracking Branches"}} {b gui.fastcopyblame {mc "Blame Copy Only On Changed Files"}} {i-20..200 gui.copyblamethreshold {mc "Minimum Letters To Blame Copy On"}} + {i-0..300 gui.blamehistoryctx {mc "Blame History Context Radius (days)"}} {i-0..99 gui.diffcontext {mc "Number of Diff Context Lines"}} {i-0..99 gui.commitmsgwidth {mc "Commit Message Text Width"}} {t gui.newbranchtemplate {mc "New Branch Name Template"}} -- cgit v1.2.1 From 80fd76bd58070fe9615baf365abf2ff82276e50c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:30:51 +0400 Subject: git-gui: Support passing blame to a parent commit. Add a context menu item that switches the view to the parent of the commit under cursor. It is useful to see how the file looked before the change, and find older changes in the same lines. Signed-off-by: Alexander Gavrilov Signed-off-by: Shawn O. Pearce --- lib/blame.tcl | 48 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/lib/blame.tcl b/lib/blame.tcl index c8975cfecd..ef42231793 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -262,6 +262,9 @@ constructor new {i_commit i_path} { $w.ctxm add command \ -label [mc "Show History Context"] \ -command [cb _gitkcommit] + $w.ctxm add command \ + -label [mc "Blame Parent Commit"] \ + -command [cb _blameparent] foreach i $w_columns { for {set g 0} {$g < [llength $group_colors]} {incr g} { @@ -790,19 +793,27 @@ method _load_commit {cur_w cur_d pos} { set lno [lindex [split [$cur_w index $pos] .] 0] set dat [lindex $line_data $lno] if {$dat ne {}} { - lappend history [list \ - $commit $path \ - $highlight_column \ - $highlight_line \ - [lindex [$w_file xview] 0] \ - [lindex [$w_file yview] 0] \ - ] - set commit [lindex $dat 0] - set path [lindex $dat 1] - _load $this [list [lindex $dat 2]] + _load_new_commit $this \ + [lindex $dat 0] \ + [lindex $dat 1] \ + [list [lindex $dat 2]] } } +method _load_new_commit {new_commit new_path jump} { + lappend history [list \ + $commit $path \ + $highlight_column \ + $highlight_line \ + [lindex [$w_file xview] 0] \ + [lindex [$w_file yview] 0] \ + ] + + set commit $new_commit + set path $new_path + _load $this $jump +} + method _showcommit {cur_w lno} { global repo_config variable active_color @@ -969,6 +980,23 @@ method _gitkcommit {} { } } +method _blameparent {} { + set dat [_get_click_amov_info $this] + if {$dat ne {}} { + set cmit [lindex $dat 0] + + if {[catch {set cparent [git rev-parse --verify "$cmit^"]}]} { + error_popup [strcat [mc "Cannot find parent commit:"] "\n\n$err"] + return; + } + + _load_new_commit $this \ + $cparent \ + [lindex $dat 1] \ + [list [lindex $dat 2]] + } +} + method _show_tooltip {cur_w pos} { if {$tooltip_wm ne {}} { _open_tooltip $this $cur_w -- cgit v1.2.1 From 823f7cf81dbed741c3a645913d7461e9bc04e0d2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:31:35 +0400 Subject: git-gui: Better positioning in Blame Parent Commit Invoke diff-tree between the commit and its parent, and use the hunks to fix the target line number, accounting for addition and removal of lines. Signed-off-by: Alexander Gavrilov Signed-off-by: Shawn O. Pearce --- lib/blame.tcl | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/lib/blame.tcl b/lib/blame.tcl index ef42231793..1106975775 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -984,19 +984,76 @@ method _blameparent {} { set dat [_get_click_amov_info $this] if {$dat ne {}} { set cmit [lindex $dat 0] + set new_path [lindex $dat 1] if {[catch {set cparent [git rev-parse --verify "$cmit^"]}]} { error_popup [strcat [mc "Cannot find parent commit:"] "\n\n$err"] return; } - _load_new_commit $this \ - $cparent \ - [lindex $dat 1] \ - [list [lindex $dat 2]] + _kill $this + + # Generate a diff between the commit and its parent, + # and use the hunks to update the line number. + # Request zero context to simplify calculations. + if {[catch {set fd [eval git_read diff-tree \ + --unified=0 $cparent $cmit $new_path]} err]} { + $status stop [mc "Unable to display parent"] + error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] + return + } + + set r_orig_line [lindex $dat 2] + + fconfigure $fd \ + -blocking 0 \ + -encoding binary \ + -translation binary + fileevent $fd readable [cb _read_diff_load_commit \ + $fd $cparent $new_path $r_orig_line] + set current_fd $fd } } +method _read_diff_load_commit {fd cparent new_path tline} { + if {$fd ne $current_fd} { + catch {close $fd} + return + } + + while {[gets $fd line] >= 0} { + if {[regexp {^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@} $line line \ + old_line osz old_size new_line nsz new_size]} { + + if {$osz eq {}} { set old_size 1 } + if {$nsz eq {}} { set new_size 1 } + + if {$new_line <= $tline} { + if {[expr {$new_line + $new_size}] > $tline} { + # Target line within the hunk + set line_shift [expr { + ($new_size-$old_size)*($tline-$new_line)/$new_size + }] + } else { + set line_shift [expr {$new_size-$old_size}] + } + + set r_orig_line [expr {$r_orig_line - $line_shift}] + } + } + } + + if {[eof $fd]} { + close $fd; + set current_fd {} + + _load_new_commit $this \ + $cparent \ + $new_path \ + [list $r_orig_line] + } +} ifdeleted { catch {close $fd} } + method _show_tooltip {cur_w pos} { if {$tooltip_wm ne {}} { _open_tooltip $this $cur_w -- cgit v1.2.1 From f7078b40917daa20ea3fa54c7e38766594e4b996 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:32:20 +0400 Subject: git-gui: Allow specifying an initial line for git gui blame. Add a command-line option to make git gui blame automatically scroll to a specific line in the file. Useful for integration with other tools. Signed-off-by: Alexander Gavrilov Signed-off-by: Shawn O. Pearce --- git-gui.sh | 13 +++++++++++-- lib/blame.tcl | 4 ++-- lib/browser.tcl | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/git-gui.sh b/git-gui.sh index b0207ac36a..ec08b5a921 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2296,10 +2296,15 @@ proc usage {} { switch -- $subcommand { browser - blame { - set subcommand_args {rev? path} + if {$subcommand eq "blame"} { + set subcommand_args {[--line=] rev? path} + } else { + set subcommand_args {rev? path} + } if {$argv eq {}} usage set head {} set path {} + set jump_spec {} set is_path 0 foreach a $argv { if {$is_path || [file exists $_prefix$a]} { @@ -2313,6 +2318,9 @@ blame { set path {} } set is_path 1 + } elseif {[regexp {^--line=(\d+)$} $a a lnum]} { + if {$jump_spec ne {} || $head ne {}} usage + set jump_spec [list $lnum] } elseif {$head eq {}} { if {$head ne {}} usage set head $a @@ -2344,6 +2352,7 @@ blame { switch -- $subcommand { browser { + if {$jump_spec ne {}} usage if {$head eq {}} { if {$path ne {} && [file isdirectory $path]} { set head $current_branch @@ -2359,7 +2368,7 @@ blame { puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path] exit 1 } - blame::new $head $path + blame::new $head $path $jump_spec } } return diff --git a/lib/blame.tcl b/lib/blame.tcl index 1106975775..827c85d67f 100644 --- a/lib/blame.tcl +++ b/lib/blame.tcl @@ -58,7 +58,7 @@ field tooltip_t {} ; # Text widget in $tooltip_wm field tooltip_timer {} ; # Current timer event for our tooltip field tooltip_commit {} ; # Commit(s) in tooltip -constructor new {i_commit i_path} { +constructor new {i_commit i_path i_jump} { global cursor_ptr variable active_color variable group_colors @@ -338,7 +338,7 @@ constructor new {i_commit i_path} { wm protocol $top WM_DELETE_WINDOW "destroy $top" bind $top [cb _kill] - _load $this {} + _load $this $i_jump } method _kill {} { diff --git a/lib/browser.tcl b/lib/browser.tcl index ab470d1264..0410cc68df 100644 --- a/lib/browser.tcl +++ b/lib/browser.tcl @@ -151,7 +151,7 @@ method _enter {} { append p [lindex $n 1] } append p $name - blame::new $browser_commit $p + blame::new $browser_commit $p {} } } } -- cgit v1.2.1 From 0843acfd2c32dd8a8594731a0090d0934ccb123b Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 25 Aug 2008 02:15:05 -0400 Subject: Fix "git log -i --grep" This has been broken in v1.6.0 due to the reorganization of the revision option parsing code. The "-i" is completely ignored, but works fine in "git log --grep -i". What happens is that the code for "-i" looks for revs->grep_filter; if it is NULL, we do nothing, since there are no grep filters. But that is obviously not correct, since we want it to influence the later --grep option. Doing it the other way around works, since "-i" just impacts the existing grep_filter option. Instead, we now always initialize the grep_filter member and just fill in options and patterns as we get them. This means that we can no longer check grep_filter for NULL, but instead must check the pattern list to see if we have any actual patterns. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-rev-list.c | 3 ++- revision.c | 34 ++++++++++++---------------------- revision.h | 3 ++- t/t4202-log.sh | 22 ++++++++++++++++++++++ 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 893762c80f..c023003b2b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -645,7 +645,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.diff) usage(rev_list_usage); - save_commit_buffer = revs.verbose_header || revs.grep_filter; + save_commit_buffer = revs.verbose_header || + revs.grep_filter.pattern_list; if (bisect_list) revs.limited = 1; diff --git a/revision.c b/revision.c index e75079a6e1..36291b6b86 100644 --- a/revision.c +++ b/revision.c @@ -782,6 +782,10 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->commit_format = CMIT_FMT_DEFAULT; + revs->grep_filter.status_only = 1; + revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list); + revs->grep_filter.regflags = REG_NEWLINE; + diff_setup(&revs->diffopt); if (prefix && !revs->diffopt.prefix) { revs->diffopt.prefix = prefix; @@ -946,15 +950,7 @@ void read_revisions_from_stdin(struct rev_info *revs) static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what) { - if (!revs->grep_filter) { - struct grep_opt *opt = xcalloc(1, sizeof(*opt)); - opt->status_only = 1; - opt->pattern_tail = &(opt->pattern_list); - opt->regflags = REG_NEWLINE; - revs->grep_filter = opt; - } - append_grep_pattern(revs->grep_filter, ptn, - "command line", 0, what); + append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what); } static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern) @@ -1164,17 +1160,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!prefixcmp(arg, "--grep=")) { add_message_grep(revs, arg+7); } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) { - if (revs->grep_filter) - revs->grep_filter->regflags |= REG_EXTENDED; + revs->grep_filter.regflags |= REG_EXTENDED; } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) { - if (revs->grep_filter) - revs->grep_filter->regflags |= REG_ICASE; + revs->grep_filter.regflags |= REG_ICASE; } else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) { - if (revs->grep_filter) - revs->grep_filter->fixed = 1; + revs->grep_filter.fixed = 1; } else if (!strcmp(arg, "--all-match")) { - if (revs->grep_filter) - revs->grep_filter->all_match = 1; + revs->grep_filter.all_match = 1; } else if (!prefixcmp(arg, "--encoding=")) { arg += 11; if (strcmp(arg, "none")) @@ -1349,9 +1341,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (diff_setup_done(&revs->diffopt) < 0) die("diff_setup_done failed"); - if (revs->grep_filter) { - compile_grep_patterns(revs->grep_filter); - } + compile_grep_patterns(&revs->grep_filter); if (revs->reverse && revs->reflog_info) die("cannot combine --reverse with --walk-reflogs"); @@ -1492,9 +1482,9 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit) static int commit_match(struct commit *commit, struct rev_info *opt) { - if (!opt->grep_filter) + if (!opt->grep_filter.pattern_list) return 1; - return grep_buffer(opt->grep_filter, + return grep_buffer(&opt->grep_filter, NULL, /* we say nothing, not even filename */ commit->buffer, strlen(commit->buffer)); } diff --git a/revision.h b/revision.h index 1b045669ae..91f194478b 100644 --- a/revision.h +++ b/revision.h @@ -2,6 +2,7 @@ #define REVISION_H #include "parse-options.h" +#include "grep.h" #define SEEN (1u<<0) #define UNINTERESTING (1u<<1) @@ -92,7 +93,7 @@ struct rev_info { int show_log_size; /* Filter by commit log message */ - struct grep_opt *grep_filter; + struct grep_opt grep_filter; /* Display history graph */ struct git_graph *graph; diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 4c8af45f83..0ab925c4e4 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -69,7 +69,29 @@ test_expect_success 'diff-filter=D' ' ' +test_expect_success 'setup case sensitivity tests' ' + echo case >one && + test_tick && + git commit -a -m Second +' + +test_expect_success 'log --grep' ' + echo second >expect && + git log -1 --pretty="tformat:%s" --grep=sec >actual && + test_cmp expect actual +' +test_expect_success 'log -i --grep' ' + echo Second >expect && + git log -1 --pretty="tformat:%s" -i --grep=sec >actual && + test_cmp expect actual +' + +test_expect_success 'log --grep -i' ' + echo Second >expect && + git log -1 --pretty="tformat:%s" --grep=sec -i >actual && + test_cmp expect actual +' test_done -- cgit v1.2.1 From 1e7abc593d55bc436ccd98f6815d49b13511baa1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 25 Aug 2008 22:39:17 -0700 Subject: Revert "Build-in "git-shell"" This reverts commit daa0cc9a92c9c2c714aa5f7da6d0ff65b93e0698. It was a stupid idea to do this; when run as a log-in shell, it is spawned with argv[0] set to "-git-shell", so the usual name-based dispatch would not work to begin with. --- Makefile | 2 +- builtin-shell.c | 90 --------------------------------------------------------- builtin.h | 1 - git.c | 1 - shell.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 90 insertions(+), 93 deletions(-) delete mode 100644 builtin-shell.c create mode 100644 shell.c diff --git a/Makefile b/Makefile index 7c585e866c..71339e1098 100644 --- a/Makefile +++ b/Makefile @@ -546,7 +546,6 @@ BUILTIN_OBJS += builtin-rev-parse.o BUILTIN_OBJS += builtin-revert.o BUILTIN_OBJS += builtin-rm.o BUILTIN_OBJS += builtin-send-pack.o -BUILTIN_OBJS += builtin-shell.o BUILTIN_OBJS += builtin-shortlog.o BUILTIN_OBJS += builtin-show-branch.o BUILTIN_OBJS += builtin-show-ref.o @@ -822,6 +821,7 @@ EXTLIBS += -lz ifndef NO_POSIX_ONLY_PROGRAMS PROGRAMS += git-daemon$X PROGRAMS += git-imap-send$X + PROGRAMS += git-shell$X endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl diff --git a/builtin-shell.c b/builtin-shell.c deleted file mode 100644 index 3cf97d4f5d..0000000000 --- a/builtin-shell.c +++ /dev/null @@ -1,90 +0,0 @@ -#include "cache.h" -#include "quote.h" -#include "exec_cmd.h" -#include "strbuf.h" -#include "builtin.h" - -static int do_generic_cmd(const char *me, char *arg) -{ - const char *my_argv[4]; - - setup_path(); - if (!arg || !(arg = sq_dequote(arg))) - die("bad argument"); - if (prefixcmp(me, "git-")) - die("bad command"); - - my_argv[0] = me + 4; - my_argv[1] = arg; - my_argv[2] = NULL; - - return execv_git_cmd(my_argv); -} - -static int do_cvs_cmd(const char *me, char *arg) -{ - const char *cvsserver_argv[3] = { - "cvsserver", "server", NULL - }; - - if (!arg || strcmp(arg, "server")) - die("git-cvsserver only handles server: %s", arg); - - setup_path(); - return execv_git_cmd(cvsserver_argv); -} - - -static struct commands { - const char *name; - int (*exec)(const char *me, char *arg); -} cmd_list[] = { - { "git-receive-pack", do_generic_cmd }, - { "git-upload-pack", do_generic_cmd }, - { "cvs", do_cvs_cmd }, - { NULL }, -}; - -int cmd_shell(int argc, const char **argv, const char *prefix) -{ - char *prog; - struct commands *cmd; - - /* - * Special hack to pretend to be a CVS server - */ - if (argc == 2 && !strcmp(argv[1], "cvs server")) - argv--; - - /* - * We do not accept anything but "-c" followed by "cmd arg", - * where "cmd" is a very limited subset of git commands. - */ - else if (argc != 3 || strcmp(argv[1], "-c")) - die("What do you think I am? A shell?"); - - prog = xstrdup(argv[2]); - if (!strncmp(prog, "git", 3) && isspace(prog[3])) - /* Accept "git foo" as if the caller said "git-foo". */ - prog[3] = '-'; - - for (cmd = cmd_list ; cmd->name ; cmd++) { - int len = strlen(cmd->name); - char *arg; - if (strncmp(cmd->name, prog, len)) - continue; - arg = NULL; - switch (prog[len]) { - case '\0': - arg = NULL; - break; - case ' ': - arg = prog + len + 1; - break; - default: - continue; - } - exit(cmd->exec(cmd->name, arg)); - } - die("unrecognized command '%s'", prog); -} diff --git a/builtin.h b/builtin.h index 2b57a5eb6b..f3502d305e 100644 --- a/builtin.h +++ b/builtin.h @@ -88,7 +88,6 @@ extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); extern int cmd_revert(int argc, const char **argv, const char *prefix); extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_send_pack(int argc, const char **argv, const char *prefix); -extern int cmd_shell(int argc, const char **argv, const char *prefix); extern int cmd_shortlog(int argc, const char **argv, const char *prefix); extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); diff --git a/git.c b/git.c index 89e4645736..37b1d76a08 100644 --- a/git.c +++ b/git.c @@ -338,7 +338,6 @@ static void handle_internal_command(int argc, const char **argv) { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE }, { "rm", cmd_rm, RUN_SETUP }, { "send-pack", cmd_send_pack, RUN_SETUP }, - { "shell", cmd_shell }, { "shortlog", cmd_shortlog, USE_PAGER }, { "show-branch", cmd_show_branch, RUN_SETUP }, { "show", cmd_show, RUN_SETUP | USE_PAGER }, diff --git a/shell.c b/shell.c new file mode 100644 index 0000000000..0f6a727a8c --- /dev/null +++ b/shell.c @@ -0,0 +1,89 @@ +#include "cache.h" +#include "quote.h" +#include "exec_cmd.h" +#include "strbuf.h" + +static int do_generic_cmd(const char *me, char *arg) +{ + const char *my_argv[4]; + + setup_path(); + if (!arg || !(arg = sq_dequote(arg))) + die("bad argument"); + if (prefixcmp(me, "git-")) + die("bad command"); + + my_argv[0] = me + 4; + my_argv[1] = arg; + my_argv[2] = NULL; + + return execv_git_cmd(my_argv); +} + +static int do_cvs_cmd(const char *me, char *arg) +{ + const char *cvsserver_argv[3] = { + "cvsserver", "server", NULL + }; + + if (!arg || strcmp(arg, "server")) + die("git-cvsserver only handles server: %s", arg); + + setup_path(); + return execv_git_cmd(cvsserver_argv); +} + + +static struct commands { + const char *name; + int (*exec)(const char *me, char *arg); +} cmd_list[] = { + { "git-receive-pack", do_generic_cmd }, + { "git-upload-pack", do_generic_cmd }, + { "cvs", do_cvs_cmd }, + { NULL }, +}; + +int main(int argc, char **argv) +{ + char *prog; + struct commands *cmd; + + /* + * Special hack to pretend to be a CVS server + */ + if (argc == 2 && !strcmp(argv[1], "cvs server")) + argv--; + + /* + * We do not accept anything but "-c" followed by "cmd arg", + * where "cmd" is a very limited subset of git commands. + */ + else if (argc != 3 || strcmp(argv[1], "-c")) + die("What do you think I am? A shell?"); + + prog = argv[2]; + if (!strncmp(prog, "git", 3) && isspace(prog[3])) + /* Accept "git foo" as if the caller said "git-foo". */ + prog[3] = '-'; + + for (cmd = cmd_list ; cmd->name ; cmd++) { + int len = strlen(cmd->name); + char *arg; + if (strncmp(cmd->name, prog, len)) + continue; + arg = NULL; + switch (prog[len]) { + case '\0': + arg = NULL; + break; + case ' ': + arg = prog + len + 1; + break; + default: + continue; + } + exit(cmd->exec(cmd->name, arg)); + } + die("unrecognized command '%s'", prog); +} -- cgit v1.2.1 From 2327f61ecc4e9fbb6dd9fffdec0b043aeaca908f Mon Sep 17 00:00:00 2001 From: David Aguilar Date: Sun, 24 Aug 2008 12:43:37 -0700 Subject: git-submodule: add "sync" command When a submodule's URL changes upstream, existing submodules will be out of sync since their remote."$origin".url will still be set to the old value. This adds a "git submodule sync" command that reads submodules' URLs from .gitmodules and updates them accordingly. Signed-off-by: David Aguilar Signed-off-by: Junio C Hamano --- Documentation/git-submodule.txt | 9 ++++++++ git-submodule.sh | 48 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index abbd5b72de..babaa9bc46 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -15,6 +15,7 @@ SYNOPSIS 'git submodule' [--quiet] update [--init] [--] [...] 'git submodule' [--quiet] summary [--summary-limit ] [commit] [--] [...] 'git submodule' [--quiet] foreach +'git submodule' [--quiet] sync [--] [...] DESCRIPTION @@ -139,6 +140,14 @@ foreach:: As an example, "git submodule foreach 'echo $path `git rev-parse HEAD`' will show the path and currently checked out commit for each submodule. +sync:: + Synchronizes submodules' remote URL configuration setting + to the value specified in .gitmodules. This is useful when + submodule URLs change upstream and you need to update your local + repositories accordingly. ++ +"git submodule sync" synchronizes all submodules while +"git submodule sync -- A" synchronizes submodule "A" only. OPTIONS ------- diff --git a/git-submodule.sh b/git-submodule.sh index 59fe7b335c..4a95035d85 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -6,7 +6,7 @@ USAGE="[--quiet] [--cached] \ [add [-b branch] ]|[status|init|update [-i|--init]|summary [-n|--summary-limit ] []] \ -[--] [...]|[foreach ]" +[--] [...]|[foreach ]|[sync [--] [...]]" OPTIONS_SPEC= . git-sh-setup . git-parse-remote @@ -601,6 +601,50 @@ cmd_status() fi done } +# +# Sync remote urls for submodules +# This makes the value for remote.$remote.url match the value +# specified in .gitmodules. +# +cmd_sync() +{ + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + quiet=1 + shift + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + done + cd_to_toplevel + module_list "$@" | + while read mode sha1 stage path + do + name=$(module_name "$path") + url=$(git config -f .gitmodules --get submodule."$name".url) + if test -e "$path"/.git + then + ( + unset GIT_DIR + cd "$path" + remote=$(get_default_remote) + say "Synchronizing submodule url for '$name'" + git config remote."$remote".url "$url" + ) + fi + done +} # This loop parses the command line arguments to find the # subcommand name to dispatch. Parsing of the subcommand specific @@ -611,7 +655,7 @@ cmd_status() while test $# != 0 && test -z "$command" do case "$1" in - add | foreach | init | update | status | summary) + add | foreach | init | update | status | summary | sync) command=$1 ;; -q|--quiet) -- cgit v1.2.1 From 460c201039471d22194ca871290c098bfe6ce6a3 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 24 Aug 2008 13:27:10 -0700 Subject: daemon.c: minor style fixup * "else" on the same line as "}" that closes corresponding "if (...) {"; * multi-line comments begin with "/*\n"; * sizeof, even it is not a function, is written as "sizeof(...)"; * no need to check x?alloc() return value -- it would have died; * "if (...) { ... }" that covers the whole function body can be dedented by returning from the function early with "if (!...) return;"; * SP on each side of an operator, i.e. "a > 0", not "a>0"; Also removes stale comment describing how remove_child() used to do its thing. Signed-off-by: Junio C Hamano : Signed-off-by: Junio C Hamano --- daemon.c | 76 +++++++++++++++++++++++++++++----------------------------------- 1 file changed, 34 insertions(+), 42 deletions(-) diff --git a/daemon.c b/daemon.c index 79f0388204..23278e28dc 100644 --- a/daemon.c +++ b/daemon.c @@ -81,9 +81,9 @@ static void logreport(int priority, const char *err, va_list params) char buf[1024]; vsnprintf(buf, sizeof(buf), err, params); syslog(priority, "%s", buf); - } - else { - /* Since stderr is set to linebuffered mode, the + } else { + /* + * Since stderr is set to linebuffered mode, the * logging of different processes will not overlap */ fprintf(stderr, "[%d] ", (int)getpid()); @@ -596,31 +596,24 @@ static struct child { static void add_child(pid_t pid, struct sockaddr *addr, int addrlen) { - struct child *newborn; - newborn = xcalloc(1, sizeof *newborn); - if (newborn) { - struct child **cradle; - - live_children++; - newborn->pid = pid; - memcpy(&newborn->address, addr, addrlen); - for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next) - if (!memcmp(&(*cradle)->address, &newborn->address, - sizeof newborn->address)) - break; - newborn->next = *cradle; - *cradle = newborn; - } - else - logerror("Out of memory spawning new child"); + struct child *newborn, **cradle; + + /* + * This must be xcalloc() -- we'll compare the whole sockaddr_storage + * but individual address may be shorter. + */ + newborn = xcalloc(1, sizeof(*newborn)); + live_children++; + newborn->pid = pid; + memcpy(&newborn->address, addr, addrlen); + for (cradle = &firstborn; *cradle; cradle = &(*cradle)->next) + if (!memcmp(&(*cradle)->address, &newborn->address, + sizeof(newborn->address))) + break; + newborn->next = *cradle; + *cradle = newborn; } -/* - * Walk from "deleted" to "spawned", and remove child "pid". - * - * We move everything up by one, since the new "deleted" will - * be one higher. - */ static void remove_child(pid_t pid) { struct child **cradle, *blanket; @@ -642,18 +635,17 @@ static void remove_child(pid_t pid) */ static void kill_some_child(void) { - const struct child *blanket; + const struct child *blanket, *next; - if ((blanket = firstborn)) { - const struct child *next; + if (!(blanket = firstborn)) + return; - for (; (next = blanket->next); blanket = next) - if (!memcmp(&blanket->address, &next->address, - sizeof next->address)) { - kill(blanket->pid, SIGTERM); - break; - } - } + for (; (next = blanket->next); blanket = next) + if (!memcmp(&blanket->address, &next->address, + sizeof(next->address))) { + kill(blanket->pid, SIGTERM); + break; + } } static void check_dead_children(void) @@ -661,10 +653,10 @@ static void check_dead_children(void) int status; pid_t pid; - while ((pid = waitpid(-1, &status, WNOHANG))>0) { + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { const char *dead = ""; remove_child(pid); - if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) + if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0)) dead = " (with error)"; loginfo("[%d] Disconnected%s", (int)pid, dead); } @@ -676,7 +668,7 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) if (max_connections && live_children >= max_connections) { kill_some_child(); - sleep(1); /* give it some time to die */ + sleep(1); /* give it some time to die */ check_dead_children(); if (live_children >= max_connections) { close(incoming); @@ -705,7 +697,8 @@ static void handle(int incoming, struct sockaddr *addr, int addrlen) static void child_handler(int signo) { - /* Otherwise empty handler because systemcalls will get interrupted + /* + * Otherwise empty handler because systemcalls will get interrupted * upon signal receipt * SysV needs the handler to be rearmed */ @@ -1089,8 +1082,7 @@ int main(int argc, char **argv) if (log_syslog) { openlog("git-daemon", LOG_PID, LOG_DAEMON); set_die_routine(daemon_die); - } - else + } else setlinebuf(stderr); /* avoid splitting a message in the middle */ if (inetd_mode && (group_name || user_name)) -- cgit v1.2.1 From 3e073dc561185d5ad2325cb012943020e068801e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20F=C3=A4rber?= Date: Mon, 25 Aug 2008 17:33:03 +0200 Subject: Makefile: always provide a fallback when hardlinks fail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We make hardlinks from "git" to "git-" built-ins and have been careful to avoid cross-device links when linking "git-" to gitexecdir. However, we were not prepared to deal with a build directory that is incapable of making hard links within itself. This patch corrects it. Instead of temporarily linking "git" to gitexecdir, directly link "git- add", falling back to "cp". Try hardlinking that as "git-", falling back to symlinks or "cp" on error. While at it, avoid 100+ error messages from hardlink failures when we are going to fall back to symlinks or "cp" by redirecting the standard error to /dev/null. Signed-off-by: Andreas Färber Signed-off-by: Junio C Hamano --- Makefile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 2cef0187d1..588bf5aee5 100644 --- a/Makefile +++ b/Makefile @@ -1099,7 +1099,10 @@ help.o: help.c common-cmds.h GIT-CFLAGS '-DGIT_INFO_PATH="$(infodir_SQ)"' $< $(BUILT_INS): git$X - $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@ + $(QUIET_BUILT_IN)$(RM) $@ && \ + ln git$X $@ 2>/dev/null || \ + ln -s git$X $@ 2>/dev/null || \ + cp git$X $@ common-cmds.h: ./generate-cmdlist.sh command-list.txt @@ -1364,16 +1367,13 @@ ifneq (,$X) endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ - if test "z$$bindir" != "z$$execdir"; \ - then \ - ln -f "$$bindir/git$X" "$$execdir/git$X" || \ - cp "$$bindir/git$X" "$$execdir/git$X"; \ - fi && \ - { $(foreach p,$(BUILT_INS), $(RM) "$$execdir/$p" && ln "$$execdir/git$X" "$$execdir/$p" ;) } && \ - if test "z$$bindir" != "z$$execdir"; \ - then \ - $(RM) "$$execdir/git$X"; \ - fi && \ + { $(RM) "$$execdir/git-add$X" && \ + ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \ + cp git-add$X "$$execdir/git-add$X"; } && \ + { $(foreach p,$(filter-out git-add,$(BUILT_INS)), $(RM) "$$execdir/$p" && \ + ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \ + ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \ + cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" install-doc: -- cgit v1.2.1 From 39816d60e14b4d3be6ab9cf55caf79d7596bdb29 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:27:44 +0400 Subject: gitk: Add option to specify the default commit on command line Other GUI tools may need to start gitk and make it automatically select a certain commit. This adds a new command-line option --select-commit=id to make that possible. Signed-off-by: Alexander Gavrilov Signed-off-by: Paul Mackerras --- gitk | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/gitk b/gitk index 087c4ac733..7698b70817 100755 --- a/gitk +++ b/gitk @@ -418,10 +418,12 @@ proc stop_rev_list {view} { } proc reset_pending_select {selid} { - global pending_select mainheadid + global pending_select mainheadid selectheadid if {$selid ne {}} { set pending_select $selid + } elseif {$selectheadid ne {}} { + set pending_select $selectheadid } else { set pending_select $mainheadid } @@ -1609,6 +1611,7 @@ proc getcommit {id} { proc readrefs {} { global tagids idtags headids idheads tagobjid global otherrefids idotherrefs mainhead mainheadid + global selecthead selectheadid foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -1655,6 +1658,12 @@ proc readrefs {} { set mainhead [string range $thehead 11 end] } } + set selectheadid {} + if {$selecthead ne {}} { + catch { + set selectheadid [exec git rev-parse --verify $selecthead] + } + } } # skip over fake commits @@ -9865,6 +9874,9 @@ if {![file isdirectory $gitdir]} { exit 1 } +set selecthead {} +set selectheadid {} + set revtreeargs {} set cmdline_files {} set i 0 @@ -9876,6 +9888,9 @@ foreach arg $argv { set cmdline_files [lrange $argv [expr {$i + 1}] end] break } + "--select-commit=*" { + set selecthead [string range $arg 16 end] + } "--argscmd=*" { set revtreeargscmd [string range $arg 10 end] } @@ -9886,6 +9901,10 @@ foreach arg $argv { incr i } +if {$selecthead eq "HEAD"} { + set selecthead {} +} + if {$i >= [llength $argv] && $revtreeargs ne {}} { # no -- on command line, but some arguments (other than --argscmd) if {[catch { -- cgit v1.2.1 From 77aa0ae8d3599b905ceb25db8cc50d6820efb793 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 23 Aug 2008 12:29:08 +0400 Subject: gitk: Add menu item for calling git gui blame This adds a new item to the file list popup menu, that calls git gui blame for the selected file, starting with the first parent of the current commit. Signed-off-by: Alexander Gavrilov Signed-off-by: Paul Mackerras --- gitk | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gitk b/gitk index 7698b70817..2eaa2ae7d6 100755 --- a/gitk +++ b/gitk @@ -2214,6 +2214,8 @@ proc makewindow {} { -command {flist_hl 1} $flist_menu add command -label [mc "External diff"] \ -command {external_diff} + $flist_menu add command -label [mc "Blame parent commit"] \ + -command {external_blame 1} } # Windows sends all mouse wheel events to the current focused window, not @@ -3021,6 +3023,27 @@ proc external_diff {} { } } +proc external_blame {parent_idx} { + global flist_menu_file + global nullid nullid2 + global parentlist selectedline currentid + + if {$parent_idx > 0} { + set base_commit [lindex $parentlist $selectedline [expr {$parent_idx-1}]] + } else { + set base_commit $currentid + } + + if {$base_commit eq {} || $base_commit eq $nullid || $base_commit eq $nullid2} { + error_popup [mc "No such commit"] + return + } + + if {[catch {exec git gui blame $base_commit $flist_menu_file &} err]} { + error_popup [mc "git gui blame: command failed: $err"] + } +} + # delete $dir when we see eof on $f (presumably because the child has exited) proc delete_at_eof {f dir} { while {[gets $f line] >= 0} {} -- cgit v1.2.1 From d47fb8b099d38cde7d3b27b44cc86cd720284d39 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Tue, 26 Aug 2008 18:50:37 +0100 Subject: Fix a warning (on cygwin) to allow -Werror Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- builtin-fast-export.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin-fast-export.c b/builtin-fast-export.c index 070971616d..7c93eb878d 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -417,7 +417,8 @@ static void export_marks(char *file) for (i = 0; i < idnums.size; i++) { if (deco->base && deco->base->type == 1) { mark = ptr_to_mark(deco->decoration); - fprintf(f, ":%u %s\n", mark, sha1_to_hex(deco->base->sha1)); + fprintf(f, ":%"PRIu32" %s\n", mark, + sha1_to_hex(deco->base->sha1)); } deco++; } -- cgit v1.2.1 From 2b8437321901cf1559527090f5a475f4924031e5 Mon Sep 17 00:00:00 2001 From: Ramsay Jones Date: Tue, 26 Aug 2008 18:52:57 +0100 Subject: Suppress some bash redirection error messages In particular, when testing if the filesystem allows tabs in filenames, bash issues an error something like: ./t4016-diff-quote.sh: pathname with HT: No such file or directory which is caused by the failure of the (stdout) redirection, since the file cannot be created. In order to suppress the error message, you must redirect stderr to /dev/null, *before* the stdout redirection on the command-line. Also, remove a redundant filesystem check from the begining of the t3902-quoted.sh test and standardise the "test skipped" message to 'say' on exit. Signed-off-by: Ramsay Jones Signed-off-by: Junio C Hamano --- t/t3300-funny-names.sh | 2 +- t/t3902-quoted.sh | 8 +------- t/t4016-diff-quote.sh | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/t/t3300-funny-names.sh b/t/t3300-funny-names.sh index 0574ef1f10..db46d53e82 100755 --- a/t/t3300-funny-names.sh +++ b/t/t3300-funny-names.sh @@ -21,7 +21,7 @@ cat >"$p0" <<\EOF 3. A quick brown fox jumps over the lazy cat, oops dog. EOF -cat >"$p1" "$p0" +cat 2>/dev/null >"$p1" "$p0" echo 'Foo Bar Baz' >"$p2" test -f "$p1" && cmp "$p0" "$p1" || { diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index fe4fb5116a..5868052425 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -7,12 +7,6 @@ test_description='quoted output' . ./test-lib.sh -P1='pathname with HT' -: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || { - echo >&2 'Filesystem does not support HT in names' - test_done -} - FN='濱野' GN='純' HT=' ' @@ -20,7 +14,7 @@ LF=' ' DQ='"' -echo foo > "Name and an${HT}HT" +echo foo 2>/dev/null > "Name and an${HT}HT" test -f "Name and an${HT}HT" || { # since FAT/NTFS does not allow tabs in filenames, skip this test say 'Your filesystem does not allow tabs in filenames, test skipped.' diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh index f07035ab7e..55eb5f83f1 100755 --- a/t/t4016-diff-quote.sh +++ b/t/t4016-diff-quote.sh @@ -13,8 +13,8 @@ P1='pathname with HT' P2='pathname with SP' P3='pathname with LF' -: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || { - echo >&2 'Filesystem does not support tabs in names' +: 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || { + say 'Your filesystem does not allow tabs in filenames, test skipped.' test_done } -- cgit v1.2.1 From d0b92a3f6e4d98a38a86cbd86f0e39eea9005958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Tue, 26 Aug 2008 21:32:42 +0700 Subject: index-pack: setup git repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "git index-pack" is an independent command and does not setup git repository while still need pack.indexversion. It may miss the info if it is in a subdirectory of the repository. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- index-pack.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index-pack.c b/index-pack.c index 52064befdb..728af7da9c 100644 --- a/index-pack.c +++ b/index-pack.c @@ -876,7 +876,9 @@ int main(int argc, char **argv) char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; unsigned char sha1[20]; + int nongit = 0; + setup_git_directory_gently(&nongit); git_config(git_index_pack_config, NULL); for (i = 1; i < argc; i++) { -- cgit v1.2.1 From 68daa64df2363f848d818dda9fc414511d9330da Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sun, 24 Aug 2008 22:10:29 -0400 Subject: format-patch: use default diff format even with patch options Previously, running "git format-patch -U5" would cause the low-level diff machinery to change the diff output format from "not specified" to "patch". This meant that format-patch thought we explicitly specified a diff output format, and would not use the default format. The resulting message lacked both the diffstat and the summary, as well as the separating "---". Now format-patch explicitly checks for this condition and uses the default. That means that "git format-patch -p" will now have the "-p" ignored. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- builtin-log.c | 3 ++- t/t4014-format-patch.sh | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/builtin-log.c b/builtin-log.c index 9204ffd760..1d3c5cbf58 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -932,7 +932,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (argc > 1) die ("unrecognized argument: %s", argv[1]); - if (!rev.diffopt.output_format) + if (!rev.diffopt.output_format + || rev.diffopt.output_format == DIFF_FORMAT_PATCH) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH; if (!DIFF_OPT_TST(&rev.diffopt, TEXT) && !no_binary_diff) diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 7fe853c20d..9d99dc2887 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -230,4 +230,29 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' ' ' +cat > expect << EOF +--- + file | 16 ++++++++++++++++ + 1 files changed, 16 insertions(+), 0 deletions(-) + +diff --git a/file b/file +index 40f36c6..2dc5c23 100644 +--- a/file ++++ b/file +@@ -13,4 +13,20 @@ C + 10 + D + E + F ++5 +EOF + +test_expect_success 'format-patch respects -U' ' + + git format-patch -U4 -2 && + sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output && + test_cmp expect output + +' + test_done -- cgit v1.2.1 From cdc7e388da47b66fe11c92ed7698ec233ce70635 Mon Sep 17 00:00:00 2001 From: Simon Hausmann Date: Wed, 27 Aug 2008 09:30:29 +0200 Subject: Make it possible to abort the submission of a change to Perforce Currently it is not possible to skip the submission of a change to Perforce when running git-p4 submit. This patch compares the modification time before and after the submit editor invokation and offers a prompt for skipping if the submit template file was not saved. Signed-off-by: Simon Hausmann Signed-off-by: Junio C Hamano --- contrib/fast-import/git-p4 | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 46136d49bf..c1d24b38f3 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -708,6 +708,7 @@ class P4Submit(Command): newdiff = newdiff.replace("\n", "\r\n") tmpFile.write(submitTemplate + separatorLine + diff + newdiff) tmpFile.close() + mtime = os.stat(fileName).st_mtime defaultEditor = "vi" if platform.system() == "Windows": defaultEditor = "notepad" @@ -716,15 +717,29 @@ class P4Submit(Command): else: editor = os.environ.get("EDITOR", defaultEditor); system(editor + " " + fileName) - tmpFile = open(fileName, "rb") - message = tmpFile.read() - tmpFile.close() - os.remove(fileName) - submitTemplate = message[:message.index(separatorLine)] - if self.isWindows: - submitTemplate = submitTemplate.replace("\r\n", "\n") - p4_write_pipe("submit -i", submitTemplate) + response = "y" + if os.stat(fileName).st_mtime <= mtime: + response = "x" + while response != "y" and response != "n": + response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ") + + if response == "y": + tmpFile = open(fileName, "rb") + message = tmpFile.read() + tmpFile.close() + submitTemplate = message[:message.index(separatorLine)] + if self.isWindows: + submitTemplate = submitTemplate.replace("\r\n", "\n") + p4_write_pipe("submit -i", submitTemplate) + else: + for f in editedFiles: + p4_system("revert \"%s\"" % f); + for f in filesToAdd: + p4_system("revert \"%s\"" % f); + system("rm %s" %f) + + os.remove(fileName) else: fileName = "submit.txt" file = open(fileName, "w+") -- cgit v1.2.1 From 0c68d386da710940a22712b8f3539f7e73ba1b8f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 27 Aug 2008 12:48:00 -0700 Subject: index-pack: be careful after fixing up the header/footer The index-pack command, when processing a thin pack, fixed up the pack after-the-fact. It forgets to fsync the result, because it only did that in one path rather in all cases of fixup. This moves the fsync_or_die() to the fix-up routine itself, rather than doing it in one of the callers, so that all cases are covered. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 1 - pack-write.c | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 2dadec1630..d394c494a5 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -499,7 +499,6 @@ static void write_pack_file(void) } else { int fd = sha1close(f, NULL, 0); fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written); - fsync_or_die(fd, pack_tmp_name); close(fd); } diff --git a/pack-write.c b/pack-write.c index a8f0269936..ddcfd37af2 100644 --- a/pack-write.c +++ b/pack-write.c @@ -179,6 +179,7 @@ void fixup_pack_header_footer(int pack_fd, SHA1_Final(pack_file_sha1, &c); write_or_die(pack_fd, pack_file_sha1, 20); + fsync_or_die(pack_fd, pack_name); } char *index_pack_lockfile(int ip_out) -- cgit v1.2.1 From c67b1fa349cc7b4341b32b9ef1c58a3821ce0830 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Aug 2008 16:14:22 -0700 Subject: ctype.c: protect tiny C preprocessor constants Some platforms contaminate the preprocessor token namespace with their own definition of SS without being asked. Avoid getting hit by redefinition warning messages by explicitly undef SS, AA and DD shorthand we use in this table definition. Signed-off-by: Junio C Hamano --- ctype.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ctype.c b/ctype.c index ee06eb7f48..d2bd38e901 100644 --- a/ctype.c +++ b/ctype.c @@ -5,6 +5,11 @@ */ #include "cache.h" +/* Just so that no insane platform contaminate namespace with these symbols */ +#undef SS +#undef AA +#undef DD + #define SS GIT_SPACE #define AA GIT_ALPHA #define DD GIT_DIGIT -- cgit v1.2.1 From f821d0892173e4e46a71fef4d06995f7a81c9296 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 22 Aug 2008 05:52:22 +0200 Subject: bisect: test merge base if good rev is not an ancestor of bad rev Before this patch, "git bisect", when it was given some good revs that are not ancestor of the bad rev, didn't check if the merge bases were good. "git bisect" just supposed that the user knew what he was doing, and that, when he said the revs were good, he knew that it meant that all the revs in the history leading to the good revs were also considered good. But in pratice, the user may not know that a good rev is not an ancestor of the bad rev, or he may not know/remember that all revs leading to the good rev will be considered good. So he may give a good rev that is a sibling, instead of an ancestor, of the bad rev, when in fact there can be one rev becoming good in the branch of the good rev (because the bug was already fixed there, for example) instead of one rev becoming bad in the branch of the bad rev. For example, if there is the following history: A--B--C--D \ E--F and we launch "git bisect start D F" then only C and D would have been considered as possible first bad commit before this patch. This could invite user errors; F could be the commit that fixes the bug that exists everywhere else. The purpose of this patch is to detect when "git bisect" is passed some good revs that are not ancestors of the bad rev, and then to first ask the user to test the merge bases between the good and bad revs. If the merge bases are good then all is fine, we can continue bisecting. Otherwise, if one merge base is bad, it means that the assumption that all revs leading to the good one are good too is wrong and we error out. In the case where one merge base is skipped we issue a warning and then continue bisecting anyway. These checks will also catch the case where good and bad have been mistaken. This means that we can remove the check that was done latter on the output of "git rev-list --bisect-vars". Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- git-bisect.sh | 127 +++++++++++++++++++++++++++++++++++--------- t/t6030-bisect-porcelain.sh | 90 +++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 25 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index 97ac600873..b314d47704 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -243,33 +243,18 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } -eval_rev_list() { - _eval="$1" - - eval $_eval - res=$? - - if [ $res -ne 0 ]; then - echo >&2 "'git rev-list --bisect-vars' failed:" - echo >&2 "maybe you mistake good and bad revs?" - exit $res - fi - - return $res -} - filter_skipped() { _eval="$1" _skip="$2" if [ -z "$_skip" ]; then - eval_rev_list "$_eval" + eval "$_eval" return fi # Let's parse the output of: # "git rev-list --bisect-vars --bisect-all ..." - eval_rev_list "$_eval" | while read hash line + eval "$_eval" | while read hash line do case "$VARS,$FOUND,$TRIED,$hash" in # We display some vars. @@ -332,20 +317,113 @@ exit_if_skipped_commits () { fi } +bisect_checkout() { + _rev="$1" + _msg="$2" + echo "Bisecting: $_msg" + git checkout -q "$_rev" || exit + git show-branch "$_rev" +} + +is_among() { + _rev="$1" + _list="$2" + case "$_list" in *$_rev*) return 0 ;; esac + return 1 +} + +is_testing_merge_base() { + grep "^testing $1$" "$GIT_DIR/BISECT_MERGE_BASES" >/dev/null 2>&1 +} + +mark_testing_merge_base() { + echo "testing $1" >> "$GIT_DIR/BISECT_MERGE_BASES" +} + +handle_bad_merge_base() { + _badmb="$1" + _good="$2" + if is_testing_merge_base "$_badmb"; then + cat >&2 <&2 <&2 < my_bisect_log.txt && + grep "merge base must be tested" my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect good > my_bisect_log.txt && + test_must_fail grep "merge base must be tested" my_bisect_log.txt && + grep $HASH6 my_bisect_log.txt && + git bisect reset +' +test_expect_success 'skipped merge base when good and bad are siblings' ' + git bisect start "$SIDE_HASH7" "$HASH7" > my_bisect_log.txt && + grep "merge base must be tested" my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + git bisect skip > my_bisect_log.txt 2>&1 && + grep "Warning" my_bisect_log.txt && + grep $SIDE_HASH6 my_bisect_log.txt && + git bisect reset +' + +test_expect_success 'bad merge base when good and bad are siblings' ' + git bisect start "$HASH7" HEAD > my_bisect_log.txt && + grep "merge base must be tested" my_bisect_log.txt && + grep $HASH4 my_bisect_log.txt && + test_must_fail git bisect bad > my_bisect_log.txt 2>&1 && + grep "merge base $HASH4 is bad" my_bisect_log.txt && + grep "fixed between $HASH4 and \[$SIDE_HASH7\]" my_bisect_log.txt && + git bisect reset +' + +# This creates a few more commits (A and B) to test "siblings" cases +# when a good and a bad rev have many merge bases. +# +# We should have the following: +# +# H1-H2-H3-H4-H5-H6-H7 +# \ \ \ +# S5-A \ +# \ \ +# S6-S7----B +# +# And there A and B have 2 merge bases (S5 and H5) that should be +# reported by "git merge-base --all A B". +# +test_expect_success 'many merge bases creation' ' + git checkout "$SIDE_HASH5" && + git merge -m "merge HASH5 and SIDE_HASH5" "$HASH5" && + A_HASH=$(git rev-parse --verify HEAD) && + git checkout side && + git merge -m "merge HASH7 and SIDE_HASH7" "$HASH7" && + B_HASH=$(git rev-parse --verify HEAD) && + git merge-base --all "$A_HASH" "$B_HASH" > merge_bases.txt && + test $(wc -l < merge_bases.txt) = "2" && + grep "$HASH5" merge_bases.txt && + grep "$SIDE_HASH5" merge_bases.txt +' + +test_expect_success 'good merge bases when good and bad are siblings' ' + git bisect start "$B_HASH" "$A_HASH" > my_bisect_log.txt && + grep "merge base must be tested" my_bisect_log.txt && + git bisect good > my_bisect_log2.txt && + grep "merge base must be tested" my_bisect_log2.txt && + { + { + grep "$SIDE_HASH5" my_bisect_log.txt && + grep "$HASH5" my_bisect_log2.txt + } || { + grep "$SIDE_HASH5" my_bisect_log2.txt && + grep "$HASH5" my_bisect_log.txt + } + } && + git bisect reset +' + # # test_done -- cgit v1.2.1 From c9c4e2d5a25bbf637780f9b83e74c2a26fb957f5 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Fri, 22 Aug 2008 05:52:29 +0200 Subject: bisect: only check merge bases when needed When one good revision is not an ancestor of the bad revision, the merge bases between the good and the bad revision should be checked to make sure that they are also good revisions. A previous patch takes care of that, but it may check the merge bases more often than really needed. In fact the previous patch did not try to optimize this as much as possible because it is not so simple. So this is the purpose of this patch. One may think that when all the merge bases have been checked then we can save a flag, so that we don't need to check the merge bases again during the bisect process. The problem is that the user may choose to checkout and test something completely different from what the bisect process suggested. In this case we have to check the merge bases again, because there may be new merge bases relevant to the bisect process. That's why, in this patch, when we detect that the user tested something else than what the bisect process suggested, we remove the flag that says that we don't need to check the merge bases again. Signed-off-by: Christian Couder Signed-off-by: Junio C Hamano --- git-bisect.sh | 48 ++++++++++++++++++++++++++++++++------------- t/t6030-bisect-porcelain.sh | 24 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 14 deletions(-) diff --git a/git-bisect.sh b/git-bisect.sh index b314d47704..69a9a565e0 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -172,6 +172,25 @@ bisect_write() { test -n "$nolog" || echo "git bisect $state $rev" >>"$GIT_DIR/BISECT_LOG" } +is_expected_rev() { + test -f "$GIT_DIR/BISECT_EXPECTED_REV" && + test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV") +} + +mark_expected_rev() { + echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV" +} + +check_expected_revs() { + for _rev in "$@"; do + if ! is_expected_rev "$_rev"; then + rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" + rm -f "$GIT_DIR/BISECT_EXPECTED_REV" + return + fi + done +} + bisect_state() { bisect_autostart state=$1 @@ -181,7 +200,8 @@ bisect_state() { 1,bad|1,good|1,skip) rev=$(git rev-parse --verify HEAD) || die "Bad rev input: HEAD" - bisect_write "$state" "$rev" ;; + bisect_write "$state" "$rev" + check_expected_revs "$rev" ;; 2,bad|*,good|*,skip) shift eval='' @@ -191,7 +211,8 @@ bisect_state() { die "Bad rev input: $rev" eval="$eval bisect_write '$state' '$sha'; " done - eval "$eval" ;; + eval "$eval" + check_expected_revs "$@" ;; *,bad) die "'git bisect bad' can take only one argument." ;; *) @@ -321,6 +342,7 @@ bisect_checkout() { _rev="$1" _msg="$2" echo "Bisecting: $_msg" + mark_expected_rev "$_rev" git checkout -q "$_rev" || exit git show-branch "$_rev" } @@ -332,18 +354,10 @@ is_among() { return 1 } -is_testing_merge_base() { - grep "^testing $1$" "$GIT_DIR/BISECT_MERGE_BASES" >/dev/null 2>&1 -} - -mark_testing_merge_base() { - echo "testing $1" >> "$GIT_DIR/BISECT_MERGE_BASES" -} - handle_bad_merge_base() { _badmb="$1" _good="$2" - if is_testing_merge_base "$_badmb"; then + if is_expected_rev "$_badmb"; then cat >&2 < "$GIT_DIR/BISECT_ANCESTORS_OK" } bisect_next() { @@ -491,7 +510,8 @@ bisect_clean_state() { do git update-ref -d $ref $hash || exit done - rm -f "$GIT_DIR/BISECT_MERGE_BASES" && + rm -f "$GIT_DIR/BISECT_EXPECTED_REV" && + rm -f "$GIT_DIR/BISECT_ANCESTORS_OK" && rm -f "$GIT_DIR/BISECT_LOG" && rm -f "$GIT_DIR/BISECT_NAMES" && rm -f "$GIT_DIR/BISECT_RUN" && diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index a1ce95c5a6..c163114693 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -440,6 +440,30 @@ test_expect_success 'good merge bases when good and bad are siblings' ' git bisect reset ' +check_trace() { + grep "$1" "$GIT_TRACE" | grep "\^$2" | grep "$3" >/dev/null +} + +test_expect_success 'optimized merge base checks' ' + GIT_TRACE="$(pwd)/trace.log" && + export GIT_TRACE && + git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt && + grep "merge base must be tested" my_bisect_log.txt && + grep "$HASH4" my_bisect_log.txt && + check_trace "rev-list" "$HASH7" "$SIDE_HASH7" && + git bisect good > my_bisect_log2.txt && + test -f ".git/BISECT_ANCESTORS_OK" && + test "$HASH6" = $(git rev-parse --verify HEAD) && + : > "$GIT_TRACE" && + git bisect bad > my_bisect_log3.txt && + test_must_fail check_trace "rev-list" "$HASH6" "$SIDE_HASH7" && + git bisect good "$A_HASH" > my_bisect_log4.txt && + grep "merge base must be tested" my_bisect_log4.txt && + test_must_fail test -f ".git/BISECT_ANCESTORS_OK" && + check_trace "rev-list" "$HASH6" "$A_HASH" && + unset GIT_TRACE +' + # # test_done -- cgit v1.2.1 From 1b0f7978ddb9e2ed4437ce68a4b82ab831288a41 Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Thu, 28 Aug 2008 10:57:55 +0200 Subject: bash-completion: Add all submodule subcommands to the completion list Signed-off-by: Matthias Kestenholz Signed-off-by: Junio C Hamano --- contrib/completion/git-completion.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 89858c237e..4f64f8ab7d 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1483,7 +1483,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init update" + local subcommands="add status init update summary foreach sync" if [ -z "$(__git_find_subcommand "$subcommands")" ]; then local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in -- cgit v1.2.1 From 4f38f6b5bafb1f7f85c7b54d0bb0a0e977cd947c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 28 Aug 2008 20:02:12 +0700 Subject: diff*: fix worktree setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes "git diff", "git diff-files" and "git diff-index" to work correctly under worktree setup. Because diff* family works in many modes and not all of them require worktree, Junio made a nice summary (with a little modification from me): * diff-files is about comparing with work tree, so it obviously needs a work tree; * diff-index also does, except "diff-index --cached" or "diff --cached TREE" * no-index is about random files outside git context, so it obviously doesn't need any work tree; * comparing two (or more) trees doesn't; * comparing two blobs doesn't; * comparing a blob with a random file doesn't; Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-diff-index.c | 2 ++ builtin-diff.c | 3 +++ git.c | 2 +- t/t1501-worktree.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/builtin-diff-index.c b/builtin-diff-index.c index 17d851b29e..04837494fe 100644 --- a/builtin-diff-index.c +++ b/builtin-diff-index.c @@ -39,6 +39,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); + if (!cached) + setup_work_tree(); if (read_cache() < 0) { perror("read_cache"); return -1; diff --git a/builtin-diff.c b/builtin-diff.c index 7ffea97505..037c3039a4 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -122,6 +122,8 @@ static int builtin_diff_index(struct rev_info *revs, usage(builtin_diff_usage); argv++; argc--; } + if (!cached) + setup_work_tree(); /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. @@ -225,6 +227,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; + setup_work_tree(); if (read_cache() < 0) { perror("read_cache"); return -1; diff --git a/git.c b/git.c index 37b1d76a08..fdb0f71019 100644 --- a/git.c +++ b/git.c @@ -286,7 +286,7 @@ static void handle_internal_command(int argc, const char **argv) { "count-objects", cmd_count_objects, RUN_SETUP }, { "describe", cmd_describe, RUN_SETUP }, { "diff", cmd_diff }, - { "diff-files", cmd_diff_files, RUN_SETUP }, + { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE }, { "diff-index", cmd_diff_index, RUN_SETUP }, { "diff-tree", cmd_diff_tree, RUN_SETUP }, { "fast-export", cmd_fast_export, RUN_SETUP }, diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 2ee88d8a06..8244b3a86f 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -28,6 +28,7 @@ test_rev_parse() { [ $# -eq 0 ] && return } +EMPTY_TREE=$(git write-tree) mkdir -p work/sub/dir || exit 1 mv .git repo.git || exit 1 @@ -106,12 +107,66 @@ test_expect_success 'repo finds its work tree from work tree, too' ' ' test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' - cd repo.git/work/sub/dir && + (cd repo.git/work/sub/dir && GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ git diff --exit-code tracked && echo changed > tracked && ! GIT_DIR=../../.. GIT_WORK_TREE=../.. GIT_PAGER= \ - git diff --exit-code tracked + git diff --exit-code tracked) +' +cat > diff-index-cached.expected <<\EOF +:000000 100644 0000000000000000000000000000000000000000 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 A sub/dir/tracked +EOF +cat > diff-index.expected <<\EOF +:000000 100644 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 A sub/dir/tracked +EOF + + +test_expect_success 'git diff-index' ' + GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-index $EMPTY_TREE > result && + test_cmp diff-index.expected result && + GIT_DIR=repo.git git diff-index --cached $EMPTY_TREE > result && + test_cmp diff-index-cached.expected result +' +cat >diff-files.expected <<\EOF +:100644 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0000000000000000000000000000000000000000 M sub/dir/tracked +EOF + +test_expect_success 'git diff-files' ' + GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff-files > result && + test_cmp diff-files.expected result +' + +cat >diff-TREE.expected <<\EOF +diff --git a/sub/dir/tracked b/sub/dir/tracked +new file mode 100644 +index 0000000..5ea2ed4 +--- /dev/null ++++ b/sub/dir/tracked +@@ -0,0 +1 @@ ++changed +EOF +cat >diff-TREE-cached.expected <<\EOF +diff --git a/sub/dir/tracked b/sub/dir/tracked +new file mode 100644 +index 0000000..e69de29 +EOF +cat >diff-FILES.expected <<\EOF +diff --git a/sub/dir/tracked b/sub/dir/tracked +index e69de29..5ea2ed4 100644 +--- a/sub/dir/tracked ++++ b/sub/dir/tracked +@@ -0,0 +1 @@ ++changed +EOF + +test_expect_success 'git diff' ' + GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff $EMPTY_TREE > result && + test_cmp diff-TREE.expected result && + GIT_DIR=repo.git git diff --cached $EMPTY_TREE > result && + test_cmp diff-TREE-cached.expected result && + GIT_DIR=repo.git GIT_WORK_TREE=repo.git/work git diff > result && + test_cmp diff-FILES.expected result ' test_done -- cgit v1.2.1 From 63e8aea74e992bd8667e35a0e646110acc1f0d7c Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Thu, 28 Aug 2008 17:47:22 -0500 Subject: dir.c: Avoid c99 array initialization The following syntax: char foo[] = { [0] = 1, [7] = 2, [15] = 3 }; is a c99 construct which some compilers do not support even though they support other c99 constructs. This construct can be avoided by folding these 'special' test cases into the sane_ctype array and making use of the related infrastructure. Signed-off-by: Brandon Casey Signed-off-by: Junio C Hamano --- ctype.c | 10 ++++++---- dir.c | 14 ++------------ git-compat-util.h | 2 ++ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/ctype.c b/ctype.c index d2bd38e901..9208d674db 100644 --- a/ctype.c +++ b/ctype.c @@ -9,18 +9,20 @@ #undef SS #undef AA #undef DD +#undef GS #define SS GIT_SPACE #define AA GIT_ALPHA #define DD GIT_DIGIT +#define GS GIT_SPECIAL /* \0, *, ?, [, \\ */ unsigned char sane_ctype[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */ + GS, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */ - SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 32-15 */ - DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, 0, /* 48-15 */ + SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, GS, 0, 0, 0, 0, 0, /* 32-15 */ + DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, GS, /* 48-15 */ 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */ - AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 80-15 */ + AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS, 0, 0, 0, /* 80-15 */ 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */ AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */ /* Nothing in the 128.. range */ diff --git a/dir.c b/dir.c index 92452eb5ef..acf1001c4f 100644 --- a/dir.c +++ b/dir.c @@ -52,11 +52,6 @@ int common_prefix(const char **pathspec) return prefix; } -static inline int special_char(unsigned char c1) -{ - return !c1 || c1 == '*' || c1 == '[' || c1 == '?' || c1 == '\\'; -} - /* * Does 'match' matches the given name? * A match is found if @@ -80,7 +75,7 @@ static int match_one(const char *match, const char *name, int namelen) for (;;) { unsigned char c1 = *match; unsigned char c2 = *name; - if (special_char(c1)) + if (isspecial(c1)) break; if (c1 != c2) return 0; @@ -680,17 +675,12 @@ static int cmp_name(const void *p1, const void *p2) */ static int simple_length(const char *match) { - const char special[256] = { - [0] = 1, ['?'] = 1, - ['\\'] = 1, ['*'] = 1, - ['['] = 1 - }; int len = -1; for (;;) { unsigned char c = *match++; len++; - if (special[c]) + if (isspecial(c)) return len; } } diff --git a/git-compat-util.h b/git-compat-util.h index 6ee325546e..db2836fbde 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -329,11 +329,13 @@ extern unsigned char sane_ctype[256]; #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 #define GIT_ALPHA 0x04 +#define GIT_SPECIAL 0x08 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) #define isspace(x) sane_istest(x,GIT_SPACE) #define isdigit(x) sane_istest(x,GIT_DIGIT) #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) +#define isspecial(x) sane_istest(x,GIT_SPECIAL) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) -- cgit v1.2.1 From e321180ed3e22120e30bb350a5adecbe959e1241 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 19:15:33 +0200 Subject: Remove calculation of the longest command name from where it is not used Just calculate it where it is needed - it is cheap and trivial, as all the lengths are already there (stored when creating the command lists). Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- builtin-help.c | 4 ++-- builtin-merge.c | 8 ++++---- help.c | 34 +++++++++++++++------------------- help.h | 6 +++--- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/builtin-help.c b/builtin-help.c index 391f749376..9225102f6c 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -418,7 +418,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; const char *alias; - unsigned int longest = load_command_list("git-", &main_cmds, &other_cmds); + load_command_list("git-", &main_cmds, &other_cmds); setup_git_directory_gently(&nongit); git_config(git_help_config, NULL); @@ -428,7 +428,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) if (show_all) { printf("usage: %s\n\n", git_usage_string); - list_commands("git commands", longest, &main_cmds, &other_cmds); + list_commands("git commands", &main_cmds, &other_cmds); printf("%s\n", git_more_info_string); return 0; } diff --git a/builtin-merge.c b/builtin-merge.c index d6bcbec705..dcd08f76b1 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -80,7 +80,7 @@ static struct strategy *get_strategy(const char *name) int i; struct strategy *ret; static struct cmdnames main_cmds, other_cmds; - static int longest; + static int loaded; if (!name) return NULL; @@ -89,14 +89,14 @@ static struct strategy *get_strategy(const char *name) if (!strcmp(name, all_strategy[i].name)) return &all_strategy[i]; - if (!longest) { + if (!loaded) { struct cmdnames not_strategies; + loaded = 1; memset(&main_cmds, 0, sizeof(struct cmdnames)); memset(&other_cmds, 0, sizeof(struct cmdnames)); memset(¬_strategies, 0, sizeof(struct cmdnames)); - longest = load_command_list("git-merge-", &main_cmds, - &other_cmds); + load_command_list("git-merge-", &main_cmds, &other_cmds); for (i = 0; i < main_cmds.cnt; i++) { int j, found = 0; struct cmdname *ent = main_cmds.names[i]; diff --git a/help.c b/help.c index 1afbac0927..a17a74631e 100644 --- a/help.c +++ b/help.c @@ -133,11 +133,10 @@ static int is_executable(const char *name) return st.st_mode & S_IXUSR; } -static unsigned int list_commands_in_dir(struct cmdnames *cmds, +static void list_commands_in_dir(struct cmdnames *cmds, const char *path, const char *prefix) { - unsigned int longest = 0; int prefix_len; DIR *dir = opendir(path); struct dirent *de; @@ -145,7 +144,7 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds, int len; if (!dir) - return 0; + return; if (!prefix) prefix = "git-"; prefix_len = strlen(prefix); @@ -168,29 +167,22 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds, if (has_extension(de->d_name, ".exe")) entlen -= 4; - if (longest < entlen) - longest = entlen; - add_cmdname(cmds, de->d_name + prefix_len, entlen); } closedir(dir); strbuf_release(&buf); - - return longest; } -unsigned int load_command_list(const char *prefix, +void load_command_list(const char *prefix, struct cmdnames *main_cmds, struct cmdnames *other_cmds) { - unsigned int longest = 0; - unsigned int len; const char *env_path = getenv("PATH"); char *paths, *path, *colon; const char *exec_path = git_exec_path(); if (exec_path) - longest = list_commands_in_dir(main_cmds, exec_path, prefix); + list_commands_in_dir(main_cmds, exec_path, prefix); if (!env_path) { fprintf(stderr, "PATH not set\n"); @@ -202,9 +194,7 @@ unsigned int load_command_list(const char *prefix, if ((colon = strchr(path, PATH_SEP))) *colon = 0; - len = list_commands_in_dir(other_cmds, path, prefix); - if (len > longest) - longest = len; + list_commands_in_dir(other_cmds, path, prefix); if (!colon) break; @@ -220,14 +210,20 @@ unsigned int load_command_list(const char *prefix, sizeof(*other_cmds->names), cmdname_compare); uniq(other_cmds); exclude_cmds(other_cmds, main_cmds); - - return longest; } -void list_commands(const char *title, unsigned int longest, - struct cmdnames *main_cmds, struct cmdnames *other_cmds) +void list_commands(const char *title, struct cmdnames *main_cmds, + struct cmdnames *other_cmds) { const char *exec_path = git_exec_path(); + int i, longest = 0; + + for (i = 0; i < main_cmds->cnt; i++) + if (longest < main_cmds->names[i]->len) + longest = main_cmds->names[i]->len; + for (i = 0; i < other_cmds->cnt; i++) + if (longest < other_cmds->names[i]->len) + longest = other_cmds->names[i]->len; if (main_cmds->cnt) { printf("available %s in '%s'\n", title, exec_path); diff --git a/help.h b/help.h index 3f1ae89dd6..2733433bf0 100644 --- a/help.h +++ b/help.h @@ -16,14 +16,14 @@ static inline void mput_char(char c, unsigned int num) putchar(c); } -unsigned int load_command_list(const char *prefix, +void load_command_list(const char *prefix, struct cmdnames *main_cmds, struct cmdnames *other_cmds); void add_cmdname(struct cmdnames *cmds, const char *name, int len); /* Here we require that excludes is a sorted list. */ void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes); int is_in_cmdlist(struct cmdnames *c, const char *s); -void list_commands(const char *title, unsigned int longest, - struct cmdnames *main_cmds, struct cmdnames *other_cmds); +void list_commands(const char *title, struct cmdnames *main_cmds, + struct cmdnames *other_cmds); #endif /* HELP_H */ -- cgit v1.2.1 From b9f62c0e7d60905d20de552280f91441300197c0 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 19:17:13 +0200 Subject: Remove useless memset of static command name lists in builtin-merge.c The statics are always initialized with 0 Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- builtin-merge.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index dcd08f76b1..9ad9791068 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -93,8 +93,6 @@ static struct strategy *get_strategy(const char *name) struct cmdnames not_strategies; loaded = 1; - memset(&main_cmds, 0, sizeof(struct cmdnames)); - memset(&other_cmds, 0, sizeof(struct cmdnames)); memset(¬_strategies, 0, sizeof(struct cmdnames)); load_command_list("git-merge-", &main_cmds, &other_cmds); for (i = 0; i < main_cmds.cnt; i++) { -- cgit v1.2.1 From c7371e992b1aa147b640759a7fa89e722e6db8dc Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 19:17:46 +0200 Subject: Make main_cmds and other_cmds local to builtin-help.c These are not used anywhere else. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- builtin-help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin-help.c b/builtin-help.c index 9225102f6c..721038e4f5 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -273,7 +273,7 @@ static int git_help_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } -struct cmdnames main_cmds, other_cmds; +static struct cmdnames main_cmds, other_cmds; void list_common_cmds_help(void) { -- cgit v1.2.1 From 1f08e5ce2435f1d5c78a31c0a783d5bb177cd657 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 19:19:07 +0200 Subject: Allow git help work without PATH set Just because we can Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- help.c | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/help.c b/help.c index a17a74631e..ab2c2ba260 100644 --- a/help.c +++ b/help.c @@ -178,37 +178,34 @@ void load_command_list(const char *prefix, struct cmdnames *other_cmds) { const char *env_path = getenv("PATH"); - char *paths, *path, *colon; const char *exec_path = git_exec_path(); - if (exec_path) + if (exec_path) { list_commands_in_dir(main_cmds, exec_path, prefix); - - if (!env_path) { - fprintf(stderr, "PATH not set\n"); - exit(1); + qsort(main_cmds->names, main_cmds->cnt, + sizeof(*main_cmds->names), cmdname_compare); + uniq(main_cmds); } - path = paths = xstrdup(env_path); - while (1) { - if ((colon = strchr(path, PATH_SEP))) - *colon = 0; - - list_commands_in_dir(other_cmds, path, prefix); + if (env_path) { + char *paths, *path, *colon; + path = paths = xstrdup(env_path); + while (1) { + if ((colon = strchr(path, PATH_SEP))) + *colon = 0; - if (!colon) - break; - path = colon + 1; - } - free(paths); + list_commands_in_dir(other_cmds, path, prefix); - qsort(main_cmds->names, main_cmds->cnt, - sizeof(*main_cmds->names), cmdname_compare); - uniq(main_cmds); + if (!colon) + break; + path = colon + 1; + } + free(paths); - qsort(other_cmds->names, other_cmds->cnt, - sizeof(*other_cmds->names), cmdname_compare); - uniq(other_cmds); + qsort(other_cmds->names, other_cmds->cnt, + sizeof(*other_cmds->names), cmdname_compare); + uniq(other_cmds); + } exclude_cmds(other_cmds, main_cmds); } -- cgit v1.2.1 From 61c5d431deb0437b35c18d268c1957eefd1f4b91 Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 19:19:42 +0200 Subject: list_commands: only call git_exec_path if it is needed Even if it always needed Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- help.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help.c b/help.c index ab2c2ba260..b278257aab 100644 --- a/help.c +++ b/help.c @@ -212,7 +212,6 @@ void load_command_list(const char *prefix, void list_commands(const char *title, struct cmdnames *main_cmds, struct cmdnames *other_cmds) { - const char *exec_path = git_exec_path(); int i, longest = 0; for (i = 0; i < main_cmds->cnt; i++) @@ -223,6 +222,7 @@ void list_commands(const char *title, struct cmdnames *main_cmds, longest = other_cmds->names[i]->len; if (main_cmds->cnt) { + const char *exec_path = git_exec_path(); printf("available %s in '%s'\n", title, exec_path); printf("----------------"); mput_char('-', strlen(title) + strlen(exec_path)); -- cgit v1.2.1 From 6577f542b3ab64594c7d7a7db752e96be7234fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 28 Aug 2008 20:04:30 +0700 Subject: grep: fix worktree setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unless used with --cached or grepping on a tree, "git grep" will search on working directory, so set up worktree properly Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-grep.c | 5 ++++- t/t1501-worktree.sh | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/builtin-grep.c b/builtin-grep.c index 631129ddfd..3ded1ba9da 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -783,8 +783,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) paths[1] = NULL; } - if (!list.nr) + if (!list.nr) { + if (!cached) + setup_work_tree(); return !grep_cache(&opt, paths, cached); + } if (cached) die("both --cached and trees are given."); diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 8244b3a86f..c039ee3fd8 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -169,4 +169,9 @@ test_expect_success 'git diff' ' test_cmp diff-FILES.expected result ' +test_expect_success 'git grep' ' + (cd repo.git/work/sub && + GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep -q dir/tracked) +' + test_done -- cgit v1.2.1 From b6469a81d228bd4b3e3fa62bbaf5fdd25915cd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 28 Aug 2008 20:03:22 +0700 Subject: read-tree: setup worktree if merge is required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-read-tree.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 72a6de302f..dddc3044b8 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -194,6 +194,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) usage(read_tree_usage); if ((opts.dir && !opts.update)) die("--exclude-per-directory is meaningless unless -u"); + if (opts.merge && !opts.index_only) + setup_work_tree(); if (opts.merge) { if (stage < 2) -- cgit v1.2.1 From 114ef90854c395145594974c222a004060b2acd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinbrink?= Date: Thu, 28 Aug 2008 04:14:02 +0200 Subject: for-each-ref: Allow a trailing slash in the patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More often than not, I end up using something like refs/remotes/ as the pattern for for-each-ref, but that doesn't work, because it expects to see the slash in the ref name right after the matched pattern. So teach it to accept the slash as the final character in the pattern as well. Signed-off-by: Björn Steinbrink Signed-off-by: Junio C Hamano --- builtin-for-each-ref.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index 4d25ec51d0..21e92bbcb5 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -652,7 +652,8 @@ static int grab_single_ref(const char *refname, const unsigned char *sha1, int f if ((plen <= namelen) && !strncmp(refname, p, plen) && (refname[plen] == '\0' || - refname[plen] == '/')) + refname[plen] == '/' || + p[plen-1] == '/')) break; if (!fnmatch(p, refname, FNM_PATHNAME)) break; -- cgit v1.2.1 From 441bca0bbc6d80a78ce332ef4fa6225155dfbce6 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Thu, 28 Aug 2008 16:19:08 -0700 Subject: Fix '--dirstat' with cross-directory renaming The dirstat code depends on the fact that we always generate diffs with the names sorted, since it then just does a single-pass walk-over of the sorted list of names and how many changes there were. The sorting means that all files are nicely grouped by directory. That all works fine. Except when we have rename detection, and suddenly the nicely sorted list of pathnames isn't all that sorted at all. And now the single-pass dirstat walk gets all confused, and you can get results like this: [torvalds@nehalem linux]$ git diff --dirstat=2 -M v2.6.27-rc4..v2.6.27-rc5 3.0% arch/powerpc/configs/ 6.8% arch/arm/configs/ 2.7% arch/powerpc/configs/ 4.2% arch/arm/configs/ 5.6% arch/powerpc/configs/ 8.4% arch/arm/configs/ 5.5% arch/powerpc/configs/ 23.3% arch/arm/configs/ 8.6% arch/powerpc/configs/ 4.0% arch/ 4.4% drivers/usb/musb/ 4.0% drivers/watchdog/ 7.6% drivers/ 3.5% fs/ The trivial fix is to add a sorting pass, fixing it to: [torvalds@nehalem linux]$ git diff --dirstat=2 -M v2.6.27-rc4..v2.6.27-rc5 43.0% arch/arm/configs/ 25.5% arch/powerpc/configs/ 5.3% arch/ 4.4% drivers/usb/musb/ 4.0% drivers/watchdog/ 7.6% drivers/ 3.5% fs/ Spot the difference. In case anybody wonders: it's because of a ton of renames from {include/asm-blackfin => arch/blackfin/include/asm} that just totally messed up the file ordering in between arch/arm and arch/powerpc. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- diff.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/diff.c b/diff.c index f70e6b4912..7b4300a74a 100644 --- a/diff.c +++ b/diff.c @@ -1054,6 +1054,13 @@ static long gather_dirstat(FILE *file, struct dirstat_dir *dir, unsigned long ch return this_dir; } +static int dirstat_compare(const void *_a, const void *_b) +{ + const struct dirstat_file *a = _a; + const struct dirstat_file *b = _b; + return strcmp(a->name, b->name); +} + static void show_dirstat(struct diff_options *options) { int i; @@ -1113,6 +1120,7 @@ static void show_dirstat(struct diff_options *options) return; /* Show all directories with more than x% of the changes */ + qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare); gather_dirstat(options->file, &dir, changed, "", 0); } -- cgit v1.2.1 From 29f28151c5ac0ed842e9a00438f1930ee4052757 Mon Sep 17 00:00:00 2001 From: Yann Dirson Date: Fri, 29 Aug 2008 00:00:28 +0200 Subject: Document gitk --argscmd flag. This was part of my original patch, but appears to have been lost. Signed-off-by: Yann Dirson Signed-off-by: Junio C Hamano --- Documentation/gitk.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 6e827cd11c..ae29a00d59 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -49,6 +49,13 @@ frequently used options. the history between two branches (i.e. the HEAD and the MERGE_HEAD) that modify the conflicted files. +--argscmd=:: + Command to be run each time gitk has to determine the list of + to show. The command is expected to print on its standard + output a list of additional revs to be shown, one per line. + Use this instead of explicitly specifying if the set of + commits to show may vary between refreshes. + :: Limit the revisions to show. This can be either a single revision -- cgit v1.2.1 From 0cfeed2e1d320cc76c434e0bfc26d90065754e46 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Wed, 27 Aug 2008 17:20:35 +0200 Subject: make git-shell paranoid about closed stdin/stdout/stderr It is in general unsafe to start a program with one or more of file descriptors 0/1/2 closed. Karl Chen for example noticed that stat_command does this in order to rename a pipe file descriptor to 0: dup2(from, 0); close(from); ... but if stdin was closed (for example) from == 0, so that dup2(0, 0); close(0); just ends up closing the pipe. Another extremely rare but nasty problem would occur if an "important" file ends up in file descriptor 2, and is corrupted by a call to die(). Fixing this in git was considered to be overkill, so this patch works around it only for git-shell. The fix is simply to open all the "low" descriptors to /dev/null in main. Signed-off-by: Paolo Bonzini Acked-by: Stephen R. van den Berg Signed-off-by: Junio C Hamano --- shell.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shell.c b/shell.c index 6a48de05ff..ad60200d28 100644 --- a/shell.c +++ b/shell.c @@ -56,6 +56,19 @@ int main(int argc, char **argv) { char *prog; struct commands *cmd; + int devnull_fd; + + /* + * Always open file descriptors 0/1/2 to avoid clobbering files + * in die(). It also avoids not messing up when the pipes are + * dup'ed onto stdin/stdout/stderr in the child processes we spawn. + */ + devnull_fd = open("/dev/null", O_RDWR); + while (devnull_fd >= 0 && devnull_fd <= 2) + devnull_fd = dup(devnull_fd); + if (devnull_fd == -1) + die("opening /dev/null failed (%s)", strerror(errno)); + close (devnull_fd); /* * Special hack to pretend to be a CVS server -- cgit v1.2.1 From d36f8679e94c2a0d4d15d6adcea434634af6d627 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Thu, 28 Aug 2008 20:54:59 -0400 Subject: pretty=format: respect date format options When running a command like: git log --pretty=format:%ad --date=short the date option was ignored. This patch causes it to use whatever format was specified by --date (or by --relative-date, etc), just as the non-user formats would do. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano --- Documentation/pretty-formats.txt | 2 +- archive.c | 2 +- builtin-commit.c | 2 +- commit.h | 3 ++- pretty.c | 17 +++++++++++------ t/t6006-rev-list-format.sh | 6 ++++++ 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index c11d495771..388d4925e6 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -103,7 +103,7 @@ The placeholders are: - '%an': author name - '%aN': author name (respecting .mailmap) - '%ae': author email -- '%ad': author date +- '%ad': author date (format respects --date= option) - '%aD': author date, RFC2822 style - '%ar': author date, relative - '%at': author date, UNIX timestamp diff --git a/archive.c b/archive.c index 5b40e261f1..e2280df567 100644 --- a/archive.c +++ b/archive.c @@ -48,7 +48,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf); + format_commit_message(commit, fmt.buf, buf, DATE_NORMAL); len -= c + 1 - src; src = c + 1; } diff --git a/builtin-commit.c b/builtin-commit.c index 649c8beb3e..c870037b07 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -882,7 +882,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) if (!log_tree_commit(&rev, commit)) { struct strbuf buf = STRBUF_INIT; - format_commit_message(commit, "%h: %s", &buf); + format_commit_message(commit, "%h: %s", &buf, DATE_NORMAL); printf("%s\n", buf.buf); strbuf_release(&buf); } diff --git a/commit.h b/commit.h index 77de9621d9..ecdd5733f9 100644 --- a/commit.h +++ b/commit.h @@ -67,7 +67,8 @@ extern int non_ascii(int); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern void get_commit_format(const char *arg, struct rev_info *); extern void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb); + const void *format, struct strbuf *sb, + enum date_mode dmode); extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*, struct strbuf *, int abbrev, const char *subject, diff --git a/pretty.c b/pretty.c index 33ef34a411..a29c290009 100644 --- a/pretty.c +++ b/pretty.c @@ -310,7 +310,7 @@ static int mailmap_name(struct strbuf *sb, const char *email) } static size_t format_person_part(struct strbuf *sb, char part, - const char *msg, int len) + const char *msg, int len, enum date_mode dmode) { /* currently all placeholders have same length */ const int placeholder_len = 2; @@ -377,7 +377,7 @@ static size_t format_person_part(struct strbuf *sb, char part, switch (part) { case 'd': /* date */ - strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL)); + strbuf_addstr(sb, show_date(date, tz, dmode)); return placeholder_len; case 'D': /* date, RFC2822 style */ strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822)); @@ -409,6 +409,7 @@ struct chunk { struct format_commit_context { const struct commit *commit; + enum date_mode dmode; /* These offsets are relative to the start of the commit message. */ int commit_header_parsed; @@ -584,10 +585,12 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return 1; case 'a': /* author ... */ return format_person_part(sb, placeholder[1], - msg + c->author.off, c->author.len); + msg + c->author.off, c->author.len, + c->dmode); case 'c': /* committer ... */ return format_person_part(sb, placeholder[1], - msg + c->committer.off, c->committer.len); + msg + c->committer.off, c->committer.len, + c->dmode); case 'e': /* encoding */ strbuf_add(sb, msg + c->encoding.off, c->encoding.len); return 1; @@ -599,12 +602,14 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, } void format_commit_message(const struct commit *commit, - const void *format, struct strbuf *sb) + const void *format, struct strbuf *sb, + enum date_mode dmode) { struct format_commit_context context; memset(&context, 0, sizeof(context)); context.commit = commit; + context.dmode = dmode; strbuf_expand(sb, format, format_commit_item, &context); } @@ -770,7 +775,7 @@ void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, const char *encoding; if (fmt == CMIT_FMT_USERFORMAT) { - format_commit_message(commit, user_format, sb); + format_commit_message(commit, user_format, sb, dmode); return; } diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 9176484db2..485ad4d44a 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -139,6 +139,12 @@ commit 131a310eb913d107dd3c09a65d1651175898735d commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 EOF +test_expect_success '%ad respects --date=' ' + echo 2005-04-07 >expect.ad-short && + git log -1 --date=short --pretty=tformat:%ad >output.ad-short master && + test_cmp expect.ad-short output.ad-short +' + test_expect_success 'empty email' ' test_tick && C=$(GIT_AUTHOR_EMAIL= git commit-tree HEAD^{tree} Date: Thu, 28 Aug 2008 14:23:52 +0200 Subject: tutorial: gentler illustration of Alice/Bob workflow using gitk Update to gitutorial as discussedin the git mailing list: http://marc.info/?t=121969390900002&r=1&w=2 Signed-off-by: Paolo Ciarrocchi Signed-off-by: Junio C Hamano --- Documentation/gittutorial.txt | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 48d1454a90..384972cb9b 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -321,10 +321,37 @@ pulling, like this: ------------------------------------------------ alice$ git fetch /home/bob/myrepo master -alice$ git log -p ..FETCH_HEAD +alice$ git log -p HEAD..FETCH_HEAD ------------------------------------------------ This operation is safe even if Alice has uncommitted local changes. +The range notation HEAD..FETCH_HEAD" means "show everything that is reachable +from the FETCH_HEAD but exclude anything that is reachable from HEAD. +Alice already knows everything that leads to her current state (HEAD), +and reviewing what Bob has in his state (FETCH_HEAD) that she has not +seen with this command + +If Alice wants to visualize what Bob did since their histories forked +she can issue the following command: + +------------------------------------------------ +$ gitk HEAD..FETCH_HEAD +------------------------------------------------ + +This uses the same two-dot range notation we saw earlier with 'git log'. + +Alice may want to view what both of them did since they forked. +She can use three-dot form instead of the two-dot form: + +------------------------------------------------ +$ gitk HEAD...FETCH_HEAD +------------------------------------------------ + +This means "show everything that is reachable from either one, but +exclude anything that is reachable from both of them". + +Please note that these range notation can be used with both gitk +and "git log". After inspecting what Bob did, if there is nothing urgent, Alice may decide to continue working without pulling from Bob. If Bob's history -- cgit v1.2.1 From e990501312e22cfa910d88dc7143bc4eb3632ae1 Mon Sep 17 00:00:00 2001 From: Tor Arvid Lund Date: Thu, 28 Aug 2008 00:36:12 +0200 Subject: git-p4: Fix checkout bug when using --import-local. When this option is passed to git p4 clone, the checkout at the end would previously fail. This patch fixes it by optionally creating the master branch from refs/heads/p4/master, which is the correct one for this option. Signed-off-by: Tor Arvid Lund Acked-By: Simon Hausmann Signed-off-by: Junio C Hamano --- contrib/fast-import/git-p4 | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index c1d24b38f3..2216cacba7 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -1748,8 +1748,12 @@ class P4Clone(P4Sync): if not P4Sync.run(self, depotPaths): return False if self.branch != "master": - if gitBranchExists("refs/remotes/p4/master"): - system("git branch master refs/remotes/p4/master") + if self.importIntoRemotes: + masterbranch = "refs/remotes/p4/master" + else: + masterbranch = "refs/heads/p4/master" + if gitBranchExists(masterbranch): + system("git branch master %s" % masterbranch) system("git checkout -f") else: print "Could not detect main branch. No checkout/master branch created." -- cgit v1.2.1 From 5059a427804f9fefaf75dd1aa92cb620ce9219c7 Mon Sep 17 00:00:00 2001 From: Romain Francoise Date: Fri, 29 Aug 2008 17:00:43 +0200 Subject: builtin-help: fallback to GIT_MAN_VIEWER before man In some situations it is useful to be able to switch viewers via the environment, e.g. in Emacs shell buffers. So check the GIT_MAN_VIEWER environment variable and try it before falling back to "man". Signed-off-by: Romain Francoise Signed-off-by: Junio C Hamano --- Documentation/git-help.txt | 4 +++- builtin-help.c | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index f414583fc4..d9b9c34b3a 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -112,7 +112,9 @@ For example, this configuration: will try to use konqueror first. But this may fail (for example if DISPLAY is not set) and in that case emacs' woman mode will be tried. -If everything fails the 'man' program will be tried anyway. +If everything fails, or if no viewer is configured, the viewer specified +in the GIT_MAN_VIEWER environment variable will be tried. If that +fails too, the 'man' program will be tried anyway. man..path ~~~~~~~~~~~~~~~ diff --git a/builtin-help.c b/builtin-help.c index 721038e4f5..64207cbfe9 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -361,12 +361,15 @@ static void show_man_page(const char *git_cmd) { struct man_viewer_list *viewer; const char *page = cmd_to_page(git_cmd); + const char *fallback = getenv("GIT_MAN_VIEWER"); setup_man_path(); for (viewer = man_viewer_list; viewer; viewer = viewer->next) { exec_viewer(viewer->name, page); /* will return when unable */ } + if (fallback) + exec_viewer(fallback, page); exec_viewer("man", page); die("no man viewer handled the request"); } -- cgit v1.2.1 From 6ed7f25e95069a900b10f838b15639af3fac05d3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 29 Aug 2008 16:07:58 -0400 Subject: pack-objects: improve returned information from write_one() This function returns 0 when the current object couldn't be written due to the pack size limit, otherwise the current offset in the pack. There is a problem with this approach however, since current object could be a delta and its delta base might just have been written in the same write_one() call, but those successfully written objects are not accounted in the offset variable tracked by the caller. Currently this is not an issue but a subsequent patch will need this. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index d394c494a5..640e05a12e 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -410,25 +410,22 @@ static unsigned long write_object(struct sha1file *f, return hdrlen + datalen; } -static off_t write_one(struct sha1file *f, +static int write_one(struct sha1file *f, struct object_entry *e, - off_t offset) + off_t *offset) { unsigned long size; /* offset is non zero if object is written already. */ if (e->idx.offset || e->preferred_base) - return offset; + return 1; /* if we are deltified, write out base object first. */ - if (e->delta) { - offset = write_one(f, e->delta, offset); - if (!offset) - return 0; - } + if (e->delta && !write_one(f, e->delta, offset)) + return 0; - e->idx.offset = offset; - size = write_object(f, e, offset); + e->idx.offset = *offset; + size = write_object(f, e, *offset); if (!size) { e->idx.offset = 0; return 0; @@ -436,9 +433,10 @@ static off_t write_one(struct sha1file *f, written_list[nr_written++] = &e->idx; /* make sure off_t is sufficiently large not to wrap */ - if (offset > offset + size) + if (*offset > *offset + size) die("pack too large for current definition of off_t"); - return offset + size; + *offset += size; + return 1; } /* forward declaration for write_pack_file */ @@ -448,7 +446,7 @@ static void write_pack_file(void) { uint32_t i = 0, j; struct sha1file *f; - off_t offset, offset_one, last_obj_offset = 0; + off_t offset; struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; @@ -480,11 +478,8 @@ static void write_pack_file(void) offset = sizeof(hdr); nr_written = 0; for (; i < nr_objects; i++) { - last_obj_offset = offset; - offset_one = write_one(f, objects + i, offset); - if (!offset_one) + if (!write_one(f, objects + i, &offset)) break; - offset = offset_one; display_progress(progress_state, written); } -- cgit v1.2.1 From abeb40e5aa5b4a39799ae1caad241c8c7708053a Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 29 Aug 2008 16:07:59 -0400 Subject: improve reliability of fixup_pack_header_footer() Currently, this function has the potential to read corrupted pack data from disk and give it a valid SHA1 checksum. Let's add the ability to validate SHA1 checksum of existing data along the way, including before and after any arbitrary point in the pack. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 3 ++- fast-import.c | 3 ++- index-pack.c | 3 ++- pack-write.c | 71 +++++++++++++++++++++++++++++++++++++++++--------- pack.h | 2 +- 5 files changed, 66 insertions(+), 16 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 640e05a12e..501f9367cb 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -493,7 +493,8 @@ static void write_pack_file(void) sha1close(f, sha1, CSUM_FSYNC); } else { int fd = sha1close(f, NULL, 0); - fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written); + fixup_pack_header_footer(fd, sha1, pack_tmp_name, + nr_written, NULL, 0); close(fd); } diff --git a/fast-import.c b/fast-import.c index 7089e6f9e6..d85b3a561f 100644 --- a/fast-import.c +++ b/fast-import.c @@ -951,7 +951,8 @@ static void end_packfile(void) close_pack_windows(pack_data); fixup_pack_header_footer(pack_data->pack_fd, pack_data->sha1, - pack_data->pack_name, object_count); + pack_data->pack_name, object_count, + NULL, 0); close(pack_data->pack_fd); idx_name = keep_pack(create_index()); diff --git a/index-pack.c b/index-pack.c index 728af7da9c..411b80d815 100644 --- a/index-pack.c +++ b/index-pack.c @@ -982,7 +982,8 @@ int main(int argc, char **argv) nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg); fixup_pack_header_footer(output_fd, sha1, - curr_pack, nr_objects); + curr_pack, nr_objects, + NULL, 0); } if (nr_deltas != nr_resolved_deltas) die("pack has %d unresolved deltas", diff --git a/pack-write.c b/pack-write.c index ddcfd37af2..f9776c51fa 100644 --- a/pack-write.c +++ b/pack-write.c @@ -144,41 +144,88 @@ char *write_idx_file(char *index_name, struct pack_idx_entry **objects, return index_name; } +/* + * Update pack header with object_count and compute new SHA1 for pack data + * associated to pack_fd, and write that SHA1 at the end. That new SHA1 + * is also returned in new_pack_sha1. + * + * If partial_pack_sha1 is non null, then the SHA1 of the existing pack + * (without the header update) is computed and validated against the + * one provided in partial_pack_sha1. The validation is performed at + * partial_pack_offset bytes in the pack file. The SHA1 of the remaining + * data (i.e. from partial_pack_offset to the end) is then computed and + * returned in partial_pack_sha1. + * + * Note that new_pack_sha1 is updated last, so both new_pack_sha1 and + * partial_pack_sha1 can refer to the same buffer if the caller is not + * interested in the resulting SHA1 of pack data above partial_pack_offset. + */ void fixup_pack_header_footer(int pack_fd, - unsigned char *pack_file_sha1, + unsigned char *new_pack_sha1, const char *pack_name, - uint32_t object_count) + uint32_t object_count, + unsigned char *partial_pack_sha1, + off_t partial_pack_offset) { static const int buf_sz = 128 * 1024; - SHA_CTX c; + SHA_CTX old_sha1_ctx, new_sha1_ctx; struct pack_header hdr; char *buf; + SHA1_Init(&old_sha1_ctx); + SHA1_Init(&new_sha1_ctx); + if (lseek(pack_fd, 0, SEEK_SET) != 0) - die("Failed seeking to start: %s", strerror(errno)); + die("Failed seeking to start of %s: %s", pack_name, strerror(errno)); if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) die("Unable to reread header of %s: %s", pack_name, strerror(errno)); if (lseek(pack_fd, 0, SEEK_SET) != 0) - die("Failed seeking to start: %s", strerror(errno)); + die("Failed seeking to start of %s: %s", pack_name, strerror(errno)); + SHA1_Update(&old_sha1_ctx, &hdr, sizeof(hdr)); hdr.hdr_entries = htonl(object_count); + SHA1_Update(&new_sha1_ctx, &hdr, sizeof(hdr)); write_or_die(pack_fd, &hdr, sizeof(hdr)); - - SHA1_Init(&c); - SHA1_Update(&c, &hdr, sizeof(hdr)); + partial_pack_offset -= sizeof(hdr); buf = xmalloc(buf_sz); for (;;) { - ssize_t n = xread(pack_fd, buf, buf_sz); + ssize_t m, n; + m = (partial_pack_sha1 && partial_pack_offset < buf_sz) ? + partial_pack_offset : buf_sz; + n = xread(pack_fd, buf, m); if (!n) break; if (n < 0) die("Failed to checksum %s: %s", pack_name, strerror(errno)); - SHA1_Update(&c, buf, n); + SHA1_Update(&new_sha1_ctx, buf, n); + + if (!partial_pack_sha1) + continue; + + SHA1_Update(&old_sha1_ctx, buf, n); + partial_pack_offset -= n; + if (partial_pack_offset == 0) { + unsigned char sha1[20]; + SHA1_Final(sha1, &old_sha1_ctx); + if (hashcmp(sha1, partial_pack_sha1) != 0) + die("Unexpected checksum for %s " + "(disk corruption?)", pack_name); + /* + * Now let's compute the SHA1 of the remainder of the + * pack, which also means making partial_pack_offset + * big enough not to matter anymore. + */ + SHA1_Init(&old_sha1_ctx); + partial_pack_offset = ~partial_pack_offset; + partial_pack_offset -= MSB(partial_pack_offset, 1); + } } free(buf); - SHA1_Final(pack_file_sha1, &c); - write_or_die(pack_fd, pack_file_sha1, 20); + if (partial_pack_sha1) + SHA1_Final(partial_pack_sha1, &old_sha1_ctx); + SHA1_Final(new_pack_sha1, &new_sha1_ctx); + write_or_die(pack_fd, new_pack_sha1, 20); fsync_or_die(pack_fd, pack_name); } diff --git a/pack.h b/pack.h index 76e6aa2aad..a883334b26 100644 --- a/pack.h +++ b/pack.h @@ -58,7 +58,7 @@ struct pack_idx_entry { extern char *write_idx_file(char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); extern int verify_pack(struct packed_git *); -extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t); +extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); extern char *index_pack_lockfile(int fd); #define PH_ERROR_EOF (-1) -- cgit v1.2.1 From ac0463ed44e859c84e354cd0ae47d9b5b124e112 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 29 Aug 2008 16:08:00 -0400 Subject: pack-objects: use fixup_pack_header_footer()'s validation mode When limiting the pack size, a new header has to be written to the pack and a new SHA1 computed. Make sure that the SHA1 of what is being read back matches the SHA1 of what was written. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 4 ++-- csum-file.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 501f9367cb..a02c673fb7 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -492,9 +492,9 @@ static void write_pack_file(void) } else if (nr_written == nr_remaining) { sha1close(f, sha1, CSUM_FSYNC); } else { - int fd = sha1close(f, NULL, 0); + int fd = sha1close(f, sha1, 0); fixup_pack_header_footer(fd, sha1, pack_tmp_name, - nr_written, NULL, 0); + nr_written, sha1, offset); close(fd); } diff --git a/csum-file.c b/csum-file.c index ace64f165e..28389541a3 100644 --- a/csum-file.c +++ b/csum-file.c @@ -42,11 +42,11 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags) sha1flush(f, offset); f->offset = 0; } + SHA1_Final(f->buffer, &f->ctx); + if (result) + hashcpy(result, f->buffer); if (flags & (CSUM_CLOSE | CSUM_FSYNC)) { /* write checksum and close fd */ - SHA1_Final(f->buffer, &f->ctx); - if (result) - hashcpy(result, f->buffer); sha1flush(f, 20); if (flags & CSUM_FSYNC) fsync_or_die(f->fd, f->name); -- cgit v1.2.1 From 8522148f79c29ef8f63688e7186e458a22333224 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 29 Aug 2008 16:08:01 -0400 Subject: index-pack: use fixup_pack_header_footer()'s validation mode When completing a thin pack, a new header has to be written to the pack and a new SHA1 computed. Make sure that the SHA1 of what is being read back matches the SHA1 of what was written for both: the original pack and the appended objects. To do so, a couple write_or_die() calls were converted to sha1write() which has the advantage of doing some buffering as well as handling SHA1 and CRC32 checksum already. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- index-pack.c | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/index-pack.c b/index-pack.c index 411b80d815..a6e91fe3ba 100644 --- a/index-pack.c +++ b/index-pack.c @@ -654,7 +654,7 @@ static void parse_pack_objects(unsigned char *sha1) } } -static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_crc) +static int write_compressed(struct sha1file *f, void *in, unsigned int size) { z_stream stream; unsigned long maxsize; @@ -674,13 +674,12 @@ static int write_compressed(int fd, void *in, unsigned int size, uint32_t *obj_c deflateEnd(&stream); size = stream.total_out; - write_or_die(fd, out, size); - *obj_crc = crc32(*obj_crc, out, size); + sha1write(f, out, size); free(out); return size; } -static struct object_entry *append_obj_to_pack( +static struct object_entry *append_obj_to_pack(struct sha1file *f, const unsigned char *sha1, void *buf, unsigned long size, enum object_type type) { @@ -696,15 +695,15 @@ static struct object_entry *append_obj_to_pack( s >>= 7; } header[n++] = c; - write_or_die(output_fd, header, n); - obj[0].idx.crc32 = crc32(0, Z_NULL, 0); - obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n); + crc32_begin(f); + sha1write(f, header, n); obj[0].size = size; obj[0].hdr_size = n; obj[0].type = type; obj[0].real_type = type; obj[1].idx.offset = obj[0].idx.offset + n; - obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32); + obj[1].idx.offset += write_compressed(f, buf, size); + obj[0].idx.crc32 = crc32_end(f); hashcpy(obj->idx.sha1, sha1); return obj; } @@ -716,7 +715,7 @@ static int delta_pos_compare(const void *_a, const void *_b) return a->obj_no - b->obj_no; } -static void fix_unresolved_deltas(int nr_unresolved) +static void fix_unresolved_deltas(struct sha1file *f, int nr_unresolved) { struct delta_entry **sorted_by_pos; int i, n = 0; @@ -754,8 +753,8 @@ static void fix_unresolved_deltas(int nr_unresolved) if (check_sha1_signature(d->base.sha1, base_obj.data, base_obj.size, typename(type))) die("local object %s is corrupt", sha1_to_hex(d->base.sha1)); - base_obj.obj = append_obj_to_pack(d->base.sha1, base_obj.data, - base_obj.size, type); + base_obj.obj = append_obj_to_pack(f, d->base.sha1, + base_obj.data, base_obj.size, type); link_base_data(NULL, &base_obj); find_delta_children(&d->base, &first, &last); @@ -875,7 +874,7 @@ int main(int argc, char **argv) const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; - unsigned char sha1[20]; + unsigned char pack_sha1[20]; int nongit = 0; setup_git_directory_gently(&nongit); @@ -962,13 +961,15 @@ int main(int argc, char **argv) parse_pack_header(); objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); - parse_pack_objects(sha1); + parse_pack_objects(pack_sha1); if (nr_deltas == nr_resolved_deltas) { stop_progress(&progress); /* Flush remaining pack final 20-byte SHA1. */ flush(); } else { if (fix_thin_pack) { + struct sha1file *f; + unsigned char read_sha1[20], tail_sha1[20]; char msg[48]; int nr_unresolved = nr_deltas - nr_resolved_deltas; int nr_objects_initial = nr_objects; @@ -977,13 +978,19 @@ int main(int argc, char **argv) objects = xrealloc(objects, (nr_objects + nr_unresolved + 1) * sizeof(*objects)); - fix_unresolved_deltas(nr_unresolved); + f = sha1fd(output_fd, curr_pack); + fix_unresolved_deltas(f, nr_unresolved); sprintf(msg, "completed with %d local objects", nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg); - fixup_pack_header_footer(output_fd, sha1, + sha1close(f, tail_sha1, 0); + hashcpy(read_sha1, pack_sha1); + fixup_pack_header_footer(output_fd, pack_sha1, curr_pack, nr_objects, - NULL, 0); + read_sha1, consumed_bytes-20); + if (hashcmp(read_sha1, tail_sha1) != 0) + die("Unexpected tail checksum for %s " + "(disk corruption?)", curr_pack); } if (nr_deltas != nr_resolved_deltas) die("pack has %d unresolved deltas", @@ -996,13 +1003,13 @@ int main(int argc, char **argv) idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, sha1); + curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); free(idx_objects); final(pack_name, curr_pack, index_name, curr_index, keep_name, keep_msg, - sha1); + pack_sha1); free(objects); free(index_name_buf); free(keep_name_buf); -- cgit v1.2.1 From d35825da6d5570524234e1bfe4ff0f57957b6db3 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Fri, 29 Aug 2008 16:08:02 -0400 Subject: fixup_pack_header_footer(): use nicely aligned buffer sizes It should be more efficient to use nicely aligned buffer sizes, either for filesystem operations or SHA1 checksums. Also, using a relatively small nominal size might allow for the data to remain in L1 cache between both SHA1_Update() calls. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- pack-write.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pack-write.c b/pack-write.c index f9776c51fa..939ed56362 100644 --- a/pack-write.c +++ b/pack-write.c @@ -167,7 +167,7 @@ void fixup_pack_header_footer(int pack_fd, unsigned char *partial_pack_sha1, off_t partial_pack_offset) { - static const int buf_sz = 128 * 1024; + int aligned_sz, buf_sz = 8 * 1024; SHA_CTX old_sha1_ctx, new_sha1_ctx; struct pack_header hdr; char *buf; @@ -188,10 +188,11 @@ void fixup_pack_header_footer(int pack_fd, partial_pack_offset -= sizeof(hdr); buf = xmalloc(buf_sz); + aligned_sz = buf_sz - sizeof(hdr); for (;;) { ssize_t m, n; - m = (partial_pack_sha1 && partial_pack_offset < buf_sz) ? - partial_pack_offset : buf_sz; + m = (partial_pack_sha1 && partial_pack_offset < aligned_sz) ? + partial_pack_offset : aligned_sz; n = xread(pack_fd, buf, m); if (!n) break; @@ -199,6 +200,10 @@ void fixup_pack_header_footer(int pack_fd, die("Failed to checksum %s: %s", pack_name, strerror(errno)); SHA1_Update(&new_sha1_ctx, buf, n); + aligned_sz -= n; + if (!aligned_sz) + aligned_sz = buf_sz; + if (!partial_pack_sha1) continue; -- cgit v1.2.1 From 498bcd3159ae3711f2beea2ea497cdc09856ee79 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Fri, 29 Aug 2008 21:18:38 +0200 Subject: rev-list: fix --reverse interaction with --parents --reverse did not interact well with --parents, as the included test case shows: in a history like A--B. \ \ `C--M--D the command git rev-list --reverse --parents --full-history HEAD erroneously lists D as having no parents at all. (Without --reverse, it correctly lists M.) This is caused by the machinery driving --reverse: it first grabs all commits through the normal routines, then runs them through the same routines again, effectively simplifying them twice. Fix this by moving the --reverse one level up, into get_revision(). This way we can cleanly grab all commits via the normal calls, then just pop them off the list one by one without interfering with get_revision_internal(). Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- revision.c | 38 +++++++++++++++------------------ revision.h | 1 + t/t6013-rev-list-reverse-parents.sh | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 21 deletions(-) create mode 100755 t/t6013-rev-list-reverse-parents.sh diff --git a/revision.c b/revision.c index 0aaa4c10b9..d797f05c93 100644 --- a/revision.c +++ b/revision.c @@ -1786,26 +1786,6 @@ static struct commit *get_revision_internal(struct rev_info *revs) return c; } - if (revs->reverse) { - int limit = -1; - - if (0 <= revs->max_count) { - limit = revs->max_count; - if (0 < revs->skip_count) - limit += revs->skip_count; - } - l = NULL; - while ((c = get_revision_1(revs))) { - commit_list_insert(c, &l); - if ((0 < limit) && !--limit) - break; - } - revs->commits = l; - revs->reverse = 0; - revs->max_count = -1; - c = NULL; - } - /* * Now pick up what they want to give us */ @@ -1878,7 +1858,23 @@ static struct commit *get_revision_internal(struct rev_info *revs) struct commit *get_revision(struct rev_info *revs) { - struct commit *c = get_revision_internal(revs); + struct commit *c; + struct commit_list *reversed; + + if (revs->reverse) { + reversed = NULL; + while ((c = get_revision_internal(revs))) { + commit_list_insert(c, &reversed); + } + revs->commits = reversed; + revs->reverse = 0; + revs->reverse_output_stage = 1; + } + + if (revs->reverse_output_stage) + return pop_commit(&revs->commits); + + c = get_revision_internal(revs); if (c && revs->graph) graph_update(revs->graph, c); return c; diff --git a/revision.h b/revision.h index dfa06b5210..b818cea76b 100644 --- a/revision.h +++ b/revision.h @@ -53,6 +53,7 @@ struct rev_info { rewrite_parents:1, print_parents:1, reverse:1, + reverse_output_stage:1, cherry_pick:1, first_parent_only:1; diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh new file mode 100755 index 0000000000..d294466427 --- /dev/null +++ b/t/t6013-rev-list-reverse-parents.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='--reverse combines with --parents' + +. ./test-lib.sh + + +commit () { + test_tick && + echo $1 > foo && + git add foo && + git commit -m "$1" +} + +test_expect_success 'set up --reverse example' ' + commit one && + git tag root && + commit two && + git checkout -b side HEAD^ && + commit three && + git checkout master && + git merge -s ours side && + commit five + ' + +test_expect_success '--reverse --parents --full-history combines correctly' ' + git rev-list --parents --full-history master -- foo | + tac > expected && + git rev-list --reverse --parents --full-history master -- foo \ + > actual && + test_cmp actual expected + ' + +test_expect_success '--boundary does too' ' + git rev-list --boundary --parents --full-history master ^root -- foo | + tac > expected && + git rev-list --boundary --reverse --parents --full-history \ + master ^root -- foo > actual && + test_cmp actual expected + ' + +test_done -- cgit v1.2.1 From 4e3ae59ef63475411f4c073ba3a1478b1ef73b9a Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Thu, 28 Aug 2008 15:57:32 +0200 Subject: Fix use of hardlinks in "make install" The code failed to filter-out git-add properly on platforms were $X is not empty (ATM there is only one such a platform). Than it tried to create a hardlink to the file ($execdir/git-add) it just removed (because git-add is first in the BUILT_INS), so ln failed (but because stderr was redirected into /dev/null the error was never seen), and the whole install ended up using "ln -s" instead. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 588bf5aee5..b6df1e9d8b 100644 --- a/Makefile +++ b/Makefile @@ -1370,7 +1370,7 @@ endif { $(RM) "$$execdir/git-add$X" && \ ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \ cp git-add$X "$$execdir/git-add$X"; } && \ - { $(foreach p,$(filter-out git-add,$(BUILT_INS)), $(RM) "$$execdir/$p" && \ + { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \ ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \ ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \ cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \ -- cgit v1.2.1 From 6ffaecc7d8b2c3c188a2efa5977a6e6605d878d9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 19 Aug 2008 18:05:39 -0700 Subject: shell: do not play duplicated definition games to shrink the executable Playing with linker games to shrink git-shell did not go well with various other platforms and compilers. Signed-off-by: Junio C Hamano --- Makefile | 9 +-------- shell.c | 8 -------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 2cef0187d1..a65679ab15 100644 --- a/Makefile +++ b/Makefile @@ -333,7 +333,6 @@ endif export PERL_PATH LIB_FILE=libgit.a -COMPAT_LIB = compat/lib.a XDIFF_LIB=xdiff/lib.a LIB_H += archive.h @@ -1224,12 +1223,6 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) -$(COMPAT_LIB): $(COMPAT_OBJS) - $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(COMPAT_OBJS) - -git-shell$X: abspath.o ctype.o exec_cmd.o quote.o strbuf.o usage.o wrapper.o shell.o $(COMPAT_LIB) - $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(COMPAT_LIB) - $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h @@ -1442,7 +1435,7 @@ distclean: clean clean: $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \ - $(LIB_FILE) $(XDIFF_LIB) $(COMPAT_LIB) + $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope* diff --git a/shell.c b/shell.c index ad60200d28..e3393690dd 100644 --- a/shell.c +++ b/shell.c @@ -3,14 +3,6 @@ #include "exec_cmd.h" #include "strbuf.h" -/* Stubs for functions that make no sense for git-shell. These stubs - * are provided here to avoid linking in external redundant modules. - */ -void release_pack_memory(size_t need, int fd){} -void trace_argv_printf(const char **argv, const char *fmt, ...){} -void trace_printf(const char *fmt, ...){} - - static int do_generic_cmd(const char *me, char *arg) { const char *my_argv[4]; -- cgit v1.2.1 From ee837244df2e2e4e9171f508f83f353730db9e53 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 13:08:50 -0700 Subject: Fix example in git-name-rev documentation Since 59d3f54 (name-rev: avoid "^0" when unneeded, 2007-02-20), name-rev stopped showing an unnecessary "^0" to dereference a tag down to a commit. The patch should have made a matching update to the documentation, but we forgot. Signed-off-by: Junio C Hamano --- Documentation/git-name-rev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index abd2237e51..7ca8a7b48c 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -59,7 +59,7 @@ Enter 'git-name-rev': ------------ % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a -33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940 +33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99~940 ------------ Now you are wiser, because you know that it happened 940 revisions before v0.99. -- cgit v1.2.1 From ed0f47a8c431f27e0bd131ea1cf9cabbd580745b Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 13:20:31 -0700 Subject: git-apply: Loosen "match_beginning" logic Even after a handfle attempts, match_beginning logic still has corner cases: 1bf1a85 (apply: treat EOF as proper context., 2006-05-23) 65aadb9 (apply: force matching at the beginning., 2006-05-24) 4be6096 (apply --unidiff-zero: loosen sanity checks ..., 2006-09-17) ee5a317 (Fix "git apply" to correctly enforce "match ..., 2008-04-06) This is a tricky piece of code. We still incorrectly enforce "match_beginning" for -U0 matches. I noticed this while trying out an example sequence from Clemens Buchacher: $ echo a >victim $ git add victim $ echo b >>victim $ git diff -U0 >patch $ cat patch diff --git i/victim w/victim index 7898192..422c2b7 100644 --- i/victim +++ w/victim @@ -1,0 +2 @@ a +b $ git apply --cached --unidiff-zero --- builtin-apply.c | 5 ++++- t/t4104-apply-boundary.sh | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 2216a0bf7c..47261e10fb 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -1996,6 +1996,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, /* * A hunk to change lines at the beginning would begin with * @@ -1,L +N,M @@ + * but we need to be careful. -U0 that inserts before the second + * line also has this pattern. * * And a hunk to add to an empty file would begin with * @@ -0,0 +N,M @@ @@ -2003,7 +2005,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag, * In other words, a hunk that is (frag->oldpos <= 1) with or * without leading context must match at the beginning. */ - match_beginning = frag->oldpos <= 1; + match_beginning = (!frag->oldpos || + (frag->oldpos == 1 && !unidiff_zero)); /* * A hunk without trailing lines must match at the end. diff --git a/t/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh index e7e2913de7..0e3ce3611d 100755 --- a/t/t4104-apply-boundary.sh +++ b/t/t4104-apply-boundary.sh @@ -27,6 +27,15 @@ test_expect_success setup ' git diff victim >add-a-patch.with && git diff --unified=0 >add-a-patch.without && + : insert at line two + for i in b a '"$L"' y + do + echo $i + done >victim && + cat victim >insert-a-expect && + git diff victim >insert-a-patch.with && + git diff --unified=0 >insert-a-patch.without && + : modify at the head for i in a '"$L"' y do @@ -55,7 +64,7 @@ test_expect_success setup ' git diff --unified=0 >add-z-patch.without && : modify at the tail - for i in a '"$L"' y + for i in b '"$L"' z do echo $i done >victim && @@ -81,7 +90,7 @@ do with) u= ;; without) u='--unidiff-zero ' ;; esac - for kind in add-a add-z mod-a mod-z del-a del-z + for kind in add-a add-z insert-a mod-a mod-z del-a del-z do test_expect_success "apply $kind-patch $with context" ' cat original >victim && @@ -95,7 +104,7 @@ do done done -for kind in add-a add-z mod-a mod-z del-a del-z +for kind in add-a add-z insert-a mod-a mod-z del-a del-z do rm -f $kind-ng.without sed -e "s/^diff --git /diff /" \ -- cgit v1.2.1 From 34baebcee1d66be4cc096a69ba448cd24dcedf21 Mon Sep 17 00:00:00 2001 From: Heikki Orsila Date: Sat, 30 Aug 2008 14:12:53 +0300 Subject: Start conforming code to "git subcmd" style User notifications are presented as 'git cmd', and code comments are presented as '"cmd"' or 'git's cmd', rather than 'git-cmd'. Signed-off-by: Heikki Orsila Signed-off-by: Junio C Hamano --- builtin-apply.c | 2 +- builtin-archive.c | 8 ++++---- builtin-blame.c | 2 +- builtin-bundle.c | 4 ++-- builtin-cat-file.c | 4 ++-- builtin-check-ref-format.c | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 2216a0bf7c..40eeabb625 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -274,7 +274,7 @@ static void say_patch_name(FILE *output, const char *pre, static void read_patch_file(struct strbuf *sb, int fd) { if (strbuf_read(sb, fd, 0) < 0) - die("git-apply: read returned %s", strerror(errno)); + die("git apply: read returned %s", strerror(errno)); /* * Make sure that we have some slop in the buffer diff --git a/builtin-archive.c b/builtin-archive.c index 22445acbfc..5ceec433fd 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -47,18 +47,18 @@ static int run_remote_archiver(const char *remote, int argc, len = packet_read_line(fd[0], buf, sizeof(buf)); if (!len) - die("git-archive: expected ACK/NAK, got EOF"); + die("git archive: expected ACK/NAK, got EOF"); if (buf[len-1] == '\n') buf[--len] = 0; if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) - die("git-archive: NACK %s", buf + 5); - die("git-archive: protocol error"); + die("git archive: NACK %s", buf + 5); + die("git archive: protocol error"); } len = packet_read_line(fd[0], buf, sizeof(buf)); if (len) - die("git-archive: expected a flush"); + die("git archive: expected a flush"); /* Now, start reading from fd[0] and spit it out to stdout */ rv = recv_sideband("archive", fd[0], 1, 2); diff --git a/builtin-blame.c b/builtin-blame.c index 4ea343189f..d2fc68c68a 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1791,7 +1791,7 @@ static int prepare_lines(struct scoreboard *sb) /* * Add phony grafts for use with -S; this is primarily to - * support git-cvsserver that wants to give a linear history + * support git's cvsserver that wants to give a linear history * to its clients. */ static int read_ancestry(const char *graft_file) diff --git a/builtin-bundle.c b/builtin-bundle.c index ac476e7a4b..9b58152047 100644 --- a/builtin-bundle.c +++ b/builtin-bundle.c @@ -6,10 +6,10 @@ * Basic handler for bundle files to connect repositories via sneakernet. * Invocation must include action. * This function can create a bundle or provide information on an existing - * bundle supporting git-fetch, git-pull, and git-ls-remote + * bundle supporting "fetch", "pull", and "ls-remote". */ -static const char *bundle_usage="git-bundle (create | verify | list-heads [refname]... | unbundle [refname]... )"; +static const char *bundle_usage="git bundle (create | verify | list-heads [refname]... | unbundle [refname]... )"; int cmd_bundle(int argc, const char **argv, const char *prefix) { diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 7441a56acd..3fba6b9e74 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -137,11 +137,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) break; default: - die("git-cat-file: unknown option: %s\n", exp_type); + die("git cat-file: unknown option: %s\n", exp_type); } if (!buf) - die("git-cat-file %s: bad file", obj_name); + die("git cat-file %s: bad file", obj_name); write_or_die(1, buf, size); return 0; diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index fe04be77a9..701de439ae 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -9,6 +9,6 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { if (argc != 2) - usage("git-check-ref-format refname"); + usage("git check-ref-format refname"); return !!check_ref_format(argv[1]); } -- cgit v1.2.1 From 8fdcf3125465f70c0cad5be5ab192d46e46307c7 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 13:40:36 -0700 Subject: checkout: do not check out unmerged higher stages randomly During a conflicted merge when you have unmerged stages for a path F in the index, if you said: $ git checkout F we rewrote F as many times as we have stages for it, and the last one (typically "theirs") was left in the work tree, without resolving the conflict. This fixes it by noticing that a specified pathspec pattern matches an unmerged path, and by erroring out. Signed-off-by: Junio C Hamano --- builtin-checkout.c | 29 ++++++++++++++++++++++++++++- t/t7201-co.sh | 22 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index 411cc513c6..8544010994 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -76,6 +76,15 @@ static int read_tree_some(struct tree *tree, const char **pathspec) return 0; } +static int skip_same_name(struct cache_entry *ce, int pos) +{ + while (++pos < active_nr && + !strcmp(active_cache[pos]->name, ce->name)) + ; /* skip */ + return pos; +} + + static int checkout_paths(struct tree *source_tree, const char **pathspec) { int pos; @@ -107,6 +116,20 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec) if (report_path_error(ps_matched, pathspec, 0)) return 1; + /* Any unmerged paths? */ + for (pos = 0; pos < active_nr; pos++) { + struct cache_entry *ce = active_cache[pos]; + if (pathspec_match(pathspec, NULL, ce->name, 0)) { + if (!ce_stage(ce)) + continue; + errs = 1; + error("path '%s' is unmerged", ce->name); + pos = skip_same_name(ce, pos) - 1; + } + } + if (errs) + return 1; + /* Now we are committed to check them out */ memset(&state, 0, sizeof(state)); state.force = 1; @@ -114,7 +137,11 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec) for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (pathspec_match(pathspec, NULL, ce->name, 0)) { - errs |= checkout_entry(ce, &state, NULL); + if (!ce_stage(ce)) { + errs |= checkout_entry(ce, &state, NULL); + continue; + } + pos = skip_same_name(ce, pos) - 1; } } diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 9ad5d635a2..83a366f1e7 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -337,4 +337,26 @@ test_expect_success \ test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && test_must_fail git checkout --track -b track' +test_expect_success 'checkout an unmerged path should fail' ' + rm -f .git/index && + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + ( + echo "100644 $A 0 fild" && + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" && + echo "100644 $A 0 filf" + ) | git update-index --index-info && + echo "none of the above" >sample && + cat sample >fild && + cat sample >file && + cat sample >filf && + test_must_fail git checkout fild file filf && + test_cmp sample fild && + test_cmp sample filf && + test_cmp sample file +' + test_done -- cgit v1.2.1 From db9410990ee41f2b253763621c0023c782ec86e2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 07:46:55 -0700 Subject: checkout -f: allow ignoring unmerged paths when checking out of the index Earlier we made "git checkout $pathspec" to atomically refuse the operation of $pathspec matched any path with unmerged stages. This patch allows: $ git checkout -f a b c to ignore, instead of error out on, such unmerged paths. The fix to prevent checkout of an unmerged path from random stages is still there. Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 19 +++++++++++++------ builtin-checkout.c | 41 +++++++++++++++++++++++------------------ t/t7201-co.sh | 23 +++++++++++++++++++++++ 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 5aa69c0e12..15fdb08ce0 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [[--track | --no-track] -b [-l]] [-m] [] -'git checkout' [] [--] ... +'git checkout' [-f] [] [--] ... DESCRIPTION ----------- @@ -23,14 +23,17 @@ options, which will be passed to `git branch`. When are given, this command does *not* switch branches. It updates the named paths in the working tree from -the index file (i.e. it runs `git checkout-index -f -u`), or -from a named commit. In -this case, the `-f` and `-b` options are meaningless and giving +the index file, or from a named commit. In +this case, the `-b` options is meaningless and giving either of them results in an error. argument can be used to specify a specific tree-ish (i.e. commit, tag or tree) to update the index for the given paths before updating the working tree. +The index may contain unmerged entries after a failed merge. By +default, if you try to check out such an entry from the index, the +checkout operation will fail and nothing will be checked out. +Using -f will ignore these unmerged entries. OPTIONS ------- @@ -38,8 +41,12 @@ OPTIONS Quiet, suppress feedback messages. -f:: - Proceed even if the index or the working tree differs - from HEAD. This is used to throw away local changes. + When switching branches, proceed even if the index or the + working tree differs from HEAD. This is used to throw away + local changes. ++ +When checking out paths from the index, do not fail upon unmerged +entries; instead, unmerged entries are ignored. -b:: Create a new branch named and start it at diff --git a/builtin-checkout.c b/builtin-checkout.c index 8544010994..1303f3b5b3 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -20,6 +20,17 @@ static const char * const checkout_usage[] = { NULL, }; +struct checkout_opts { + int quiet; + int merge; + int force; + int writeout_error; + + const char *new_branch; + int new_branch_log; + enum branch_track track; +}; + static int post_checkout_hook(struct commit *old, struct commit *new, int changed) { @@ -85,7 +96,8 @@ static int skip_same_name(struct cache_entry *ce, int pos) } -static int checkout_paths(struct tree *source_tree, const char **pathspec) +static int checkout_paths(struct tree *source_tree, const char **pathspec, + struct checkout_opts *opts) { int pos; struct checkout state; @@ -122,8 +134,12 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec) if (pathspec_match(pathspec, NULL, ce->name, 0)) { if (!ce_stage(ce)) continue; - errs = 1; - error("path '%s' is unmerged", ce->name); + if (opts->force) { + warning("path '%s' is unmerged", ce->name); + } else { + errs = 1; + error("path '%s' is unmerged", ce->name); + } pos = skip_same_name(ce, pos) - 1; } } @@ -178,17 +194,6 @@ static void describe_detached_head(char *msg, struct commit *commit) strbuf_release(&sb); } -struct checkout_opts { - int quiet; - int merge; - int force; - int writeout_error; - - char *new_branch; - int new_branch_log; - enum branch_track track; -}; - static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree) { struct unpack_trees_options opts; @@ -554,15 +559,15 @@ no_reference: die("invalid path specification"); /* Checkout paths */ - if (opts.new_branch || opts.force || opts.merge) { + if (opts.new_branch || opts.merge) { if (argc == 1) { - die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); + die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); } else { - die("git checkout: updating paths is incompatible with switching branches/forcing"); + die("git checkout: updating paths is incompatible with switching branches."); } } - return checkout_paths(source_tree, pathspec); + return checkout_paths(source_tree, pathspec, &opts); } if (new.name && !new.commit) { diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 83a366f1e7..88692f9877 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -359,4 +359,27 @@ test_expect_success 'checkout an unmerged path should fail' ' test_cmp sample file ' +test_expect_success 'checkout with an unmerged path can be ignored' ' + rm -f .git/index && + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + ( + echo "100644 $A 0 fild" && + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" && + echo "100644 $A 0 filf" + ) | git update-index --index-info && + echo "none of the above" >sample && + echo ourside >expect && + cat sample >fild && + cat sample >file && + cat sample >filf && + git checkout -f fild file filf && + test_cmp expect fild && + test_cmp expect filf && + test_cmp sample file +' + test_done -- cgit v1.2.1 From 38901a48375952ab6c02f22bddfa19ac2bec2c36 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 07:48:18 -0700 Subject: checkout --ours/--theirs: allow checking out one side of a conflicting merge This lets you to check out 'our' (or 'their') version of an unmerged path out of the index while resolving conflicts. Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 11 +++++++++-- builtin-checkout.c | 39 ++++++++++++++++++++++++++++++++++++++- t/t7201-co.sh | 25 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 15fdb08ce0..a9ca2f5520 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [[--track | --no-track] -b [-l]] [-m] [] -'git checkout' [-f] [] [--] ... +'git checkout' [-f|--ours|--theirs] [] [--] ... DESCRIPTION ----------- @@ -33,7 +33,9 @@ working tree. The index may contain unmerged entries after a failed merge. By default, if you try to check out such an entry from the index, the checkout operation will fail and nothing will be checked out. -Using -f will ignore these unmerged entries. +Using -f will ignore these unmerged entries. The contents from a +specific side of the merge can be checked out of the index by +using --ours or --theirs. OPTIONS ------- @@ -48,6 +50,11 @@ OPTIONS When checking out paths from the index, do not fail upon unmerged entries; instead, unmerged entries are ignored. +--ours:: +--theirs:: + When checking out paths from the index, check out stage #2 + ('ours') or #3 ('theirs') for unmerged paths. + -b:: Create a new branch named and start it at . The new branch name must pass all checks defined diff --git a/builtin-checkout.c b/builtin-checkout.c index 1303f3b5b3..16bfbb6605 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -24,6 +24,7 @@ struct checkout_opts { int quiet; int merge; int force; + int writeout_stage; int writeout_error; const char *new_branch; @@ -95,6 +96,32 @@ static int skip_same_name(struct cache_entry *ce, int pos) return pos; } +static int check_stage(int stage, struct cache_entry *ce, int pos) +{ + while (pos < active_nr && + !strcmp(active_cache[pos]->name, ce->name)) { + if (ce_stage(active_cache[pos]) == stage) + return 0; + pos++; + } + return error("path '%s' does not have %s version", + ce->name, + (stage == 2) ? "our" : "their"); +} + +static int checkout_stage(int stage, struct cache_entry *ce, int pos, + struct checkout *state) +{ + while (pos < active_nr && + !strcmp(active_cache[pos]->name, ce->name)) { + if (ce_stage(active_cache[pos]) == stage) + return checkout_entry(active_cache[pos], state, NULL); + pos++; + } + return error("path '%s' does not have %s version", + ce->name, + (stage == 2) ? "our" : "their"); +} static int checkout_paths(struct tree *source_tree, const char **pathspec, struct checkout_opts *opts) @@ -106,7 +133,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, int flag; struct commit *head; int errs = 0; - + int stage = opts->writeout_stage; int newfd; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -136,6 +163,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, continue; if (opts->force) { warning("path '%s' is unmerged", ce->name); + } else if (stage) { + errs |= check_stage(stage, ce, pos); } else { errs = 1; error("path '%s' is unmerged", ce->name); @@ -157,6 +186,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, errs |= checkout_entry(ce, &state, NULL); continue; } + if (stage) + errs |= checkout_stage(stage, ce, pos, &state); pos = skip_same_name(ce, pos) - 1; } } @@ -458,6 +489,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"), OPT_SET_INT('t', "track", &opts.track, "track", BRANCH_TRACK_EXPLICIT), + OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage", + 2), + OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", + 3), OPT_BOOLEAN('f', NULL, &opts.force, "force"), OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), OPT_END(), @@ -573,6 +608,8 @@ no_reference: if (new.name && !new.commit) { die("Cannot switch branch to a non-commit."); } + if (opts.writeout_stage) + die("--ours/--theirs is incompatible with switching branches."); return switch_branches(&opts, &new); } diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 88692f9877..c7ae14118a 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -382,4 +382,29 @@ test_expect_success 'checkout with an unmerged path can be ignored' ' test_cmp sample file ' +test_expect_success 'checkout unmerged stage' ' + rm -f .git/index && + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + ( + echo "100644 $A 0 fild" && + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" && + echo "100644 $A 0 filf" + ) | git update-index --index-info && + echo "none of the above" >sample && + echo ourside >expect && + cat sample >fild && + cat sample >file && + cat sample >filf && + git checkout --ours . && + test_cmp expect fild && + test_cmp expect filf && + test_cmp expect file && + git checkout --theirs file && + test ztheirside = "z$(cat file)" +' + test_done -- cgit v1.2.1 From f2b25dd81f4ccbf42700bd15feeb2becf10331ab Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 28 Aug 2008 01:04:00 -0700 Subject: xdl_fill_merge_buffer(): separate out a too deeply nested function This simply moves code around to make a separate function that prepares a single conflicted hunk with markers into the buffer. Signed-off-by: Junio C Hamano --- xdiff/xmerge.c | 121 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 82b3573e7a..6ffaa4f9e5 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -113,65 +113,84 @@ static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) return size; } -static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + int size, int i, + xdmerge_t *m, char *dest) { const int marker_size = 7; int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int conflict_marker_size = 3 * (marker_size + 1) - + marker1_size + marker2_size; - int size, i1, j; - - for (size = i1 = 0; m; m = m->next) { - if (m->mode == 0) { - size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '<'; - if (marker1_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name1, - marker1_size - 1); - size += marker1_size; - } - dest[size++] = '\n'; - } else - size += conflict_marker_size; - size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '='; - dest[size++] = '\n'; - } - size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '>'; - if (marker2_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name2, - marker2_size - 1); - size += marker2_size; - } - dest[size++] = '\n'; - } - } else if (m->mode == 1) - size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, - dest ? dest + size : NULL); + int j; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + marker1_size; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + marker2_size; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + size, i, m, dest); + else if (m->mode == 1) + size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0, + dest ? dest + size : NULL); else if (m->mode == 2) - size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, - m->i1 + m->chg2 - i1, 0, - dest ? dest + size : NULL); + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i, + m->i1 + m->chg2 - i, 0, + dest ? dest + size : NULL); else continue; - i1 = m->i1 + m->chg1; + i = m->i1 + m->chg1; } - size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, - dest ? dest + size : NULL); + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL); return size; } -- cgit v1.2.1 From e0af48e49682ea3345f932f6b9e7fa7c5e5e611a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 28 Aug 2008 01:10:04 -0700 Subject: xdiff-merge: optionally show conflicts in "diff3 -m" style When showing conflicting merges, we traditionally followed RCS's merge output format. The output shows: <<<<<<< postimage from one side; ======= postimage of the other side; and >>>>>>> Some poeple find it easier to be able to understand what is going on when they can view the common ancestor's version, which is used by "diff3 -m", which shows: <<<<<<< postimage from one side; ||||||| shared preimage; ======= postimage of the other side; and >>>>>>> This is an initial step to bring that as an optional feature to git. Only "git merge-file" has been converted, with "--diff3" option. Signed-off-by: Junio C Hamano --- builtin-merge-file.c | 10 ++++- t/t6023-merge-file.sh | 37 ++++++++++++++++++ xdiff/xdiff.h | 6 +++ xdiff/xmerge.c | 103 ++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 135 insertions(+), 21 deletions(-) diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 3605960c2d..5b4f020e38 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -4,7 +4,7 @@ #include "xdiff-interface.h" static const char merge_file_usage[] = -"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; +"git merge-file [-p | --stdout] [--diff3] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; int cmd_merge_file(int argc, const char **argv, const char *prefix) { @@ -13,6 +13,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) mmbuffer_t result = {NULL, 0}; xpparam_t xpp = {XDF_NEED_MINIMAL}; int ret = 0, i = 0, to_stdout = 0; + int merge_level = XDL_MERGE_ZEALOUS_ALNUM; + int merge_style = 0; while (argc > 4) { if (!strcmp(argv[1], "-L") && i < 3) { @@ -25,6 +27,10 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-q") || !strcmp(argv[1], "--quiet")) freopen("/dev/null", "w", stderr); + else if (!strcmp(argv[1], "--diff3")) { + merge_style = XDL_MERGE_DIFF3; + merge_level = XDL_MERGE_EAGER; + } else usage(merge_file_usage); argc--; @@ -46,7 +52,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) } ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], - &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result); + &xpp, merge_level | merge_style, &result); for (i = 0; i < 3; i++) free(mmfs[i].ptr); diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index f674c48cab..20786ce79d 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -161,4 +161,41 @@ test_expect_success 'ZEALOUS_ALNUM' ' ' +cat >expect <<\EOF +Dominus regit me, +<<<<<<< new8.txt +et nihil mihi deerit; + + + + +In loco pascuae ibi me collocavit; +super aquam refectionis educavit me. +||||||| +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +======= +et nihil mihi deerit, + + + + +In loco pascuae ibi me collocavit -- +super aquam refectionis educavit me, +>>>>>>> new9.txt +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam TU mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success '"diff3 -m" style output' ' + test_must_fail git merge-file -p --diff3 \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + test_done diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 413082e1fd..deebe02cd3 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -50,10 +50,16 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +/* merge simplification levels */ #define XDL_MERGE_MINIMAL 0 #define XDL_MERGE_EAGER 1 #define XDL_MERGE_ZEALOUS 2 #define XDL_MERGE_ZEALOUS_ALNUM 3 +#define XDL_MERGE_LEVEL_MASK 0x0f + +/* merge output styles */ +#define XDL_MERGE_DIFF3 0x8000 +#define XDL_MERGE_STYLE_MASK 0x8000 typedef struct s_mmfile { char *ptr; diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 6ffaa4f9e5..29cdbea1ee 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -30,17 +30,32 @@ typedef struct s_xdmerge { * 2 = no conflict, take second. */ int mode; + /* + * These point at the respective postimages. E.g. is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ long i1, i2; long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; } xdmerge_t; static int xdl_append_merge(xdmerge_t **merge, int mode, - long i1, long chg1, long i2, long chg2) + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) { xdmerge_t *m = *merge; if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { if (mode != m->mode) m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; m->chg1 = i1 + chg1 - m->i1; m->chg2 = i2 + chg2 - m->i2; } else { @@ -49,6 +64,8 @@ static int xdl_append_merge(xdmerge_t **merge, int mode, return -1; m->next = NULL; m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; m->i1 = i1; m->chg1 = chg1; m->i2 = i2; @@ -91,11 +108,13 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, return 0; } -static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) { - xrecord_t **recs = xe->xdf2.recs + i; + xrecord_t **recs; int size = 0; + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + if (count < 1) return 0; @@ -113,9 +132,19 @@ static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) return size; } +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); +} + static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, xdfenv_t *xe2, const char *name2, - int size, int i, + int size, int i, int style, xdmerge_t *m, char *dest) { const int marker_size = 7; @@ -143,6 +172,20 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, /* Postimage from side #1 */ size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '|'; + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL); + } + if (!dest) { size += marker_size + 1; } else { @@ -170,14 +213,15 @@ static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, } static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) + xdfenv_t *xe2, const char *name2, + xdmerge_t *m, char *dest, int style) { int size, i; for (size = i = 0; m; m = m->next) { if (m->mode == 0) size = fill_conflict_hunk(xe1, name1, xe2, name2, - size, i, m, dest); + size, i, style, m, dest); else if (m->mode == 1) size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0, dest ? dest + size : NULL); @@ -342,9 +386,11 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, */ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, - int level, xpparam_t const *xpp, mmbuffer_t *result) { + int flags, xpparam_t const *xpp, mmbuffer_t *result) { xdmerge_t *changes, *c; - int i1, i2, chg1, chg2; + int i0, i1, i2, chg0, chg1, chg2; + int level = flags & XDL_MERGE_LEVEL_MASK; + int style = flags & XDL_MERGE_STYLE_MASK; c = changes = NULL; @@ -352,11 +398,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (!changes) changes = c; if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; chg1 = xscr1->chg2; chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -364,11 +413,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, continue; } if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; i2 = xscr2->i2; + chg0 = xscr2->chg1; chg1 = xscr2->chg1; chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -385,19 +437,26 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, int off = xscr1->i1 - xscr2->i1; int ffo = off + xscr1->chg1 - xscr2->chg1; + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr2->i2; - if (off > 0) + if (off > 0) { + i0 -= off; i1 -= off; + } else i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; chg1 = xscr1->i2 + xscr1->chg2 - i1; chg2 = xscr2->i2 + xscr2->chg2 - i2; if (ffo > 0) chg2 += ffo; - else + else { + chg0 -= ffo; chg1 -= ffo; - if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + } + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -414,11 +473,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, while (xscr1) { if (!changes) changes = c; + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; chg1 = xscr1->chg2; chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -427,11 +489,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, while (xscr2) { if (!changes) changes = c; + i0 = xscr2->i1; i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; i2 = xscr2->i2; + chg0 = xscr2->chg1; chg1 = xscr2->chg1; chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -449,7 +514,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, /* output */ if (result) { int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - changes, NULL); + changes, NULL, style); result->ptr = xdl_malloc(size); if (!result->ptr) { xdl_cleanup_merge(changes); @@ -457,14 +522,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, } result->size = size; xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, - result->ptr); + result->ptr, style); } return xdl_cleanup_merge(changes); } int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, mmfile_t *mf2, const char *name2, - xpparam_t const *xpp, int level, mmbuffer_t *result) { + xpparam_t const *xpp, int flags, mmbuffer_t *result) { xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; int status; @@ -501,7 +566,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, } else { status = xdl_do_merge(&xe1, xscr1, name1, &xe2, xscr2, name2, - level, xpp, result); + flags, xpp, result); } xdl_free_script(xscr1); xdl_free_script(xscr2); -- cgit v1.2.1 From 838338cd22c1df53c1460d9239780371a234b242 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 08:16:30 -0700 Subject: xmerge.c: minimum readability fixups This replaces hardcoded magic constants with symbolic ones for readability, and swaps one if/else blocks to better match the order in which 0/1/2 variables are handled to nearby codepath. Signed-off-by: Junio C Hamano --- xdiff/xmerge.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 29cdbea1ee..7dcd4055ae 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -427,7 +427,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, xscr2 = xscr2->next; continue; } - if (level < 1 || xscr1->i1 != xscr2->i1 || + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || xscr1->chg1 != xscr2->chg1 || xscr1->chg2 != xscr2->chg2 || xdl_merge_cmp_lines(xe1, xscr1->i2, @@ -449,12 +449,11 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, chg0 = xscr1->i1 + xscr1->chg1 - i0; chg1 = xscr1->i2 + xscr1->chg2 - i1; chg2 = xscr2->i2 + xscr2->chg2 - i2; - if (ffo > 0) - chg2 += ffo; - else { + if (ffo < 0) { chg0 -= ffo; chg1 -= ffo; - } + } else + chg2 += ffo; if (xdl_append_merge(&c, 0, i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); @@ -505,9 +504,10 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (!changes) changes = c; /* refine conflicts */ - if (level > 1 && + if (XDL_MERGE_ZEALOUS <= level && (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || - xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) { + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { xdl_cleanup_merge(changes); return -1; } -- cgit v1.2.1 From 83133740d9c81ce4c98cb53b85c8d5b190944f81 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 08:22:55 -0700 Subject: xmerge.c: "diff3 -m" style clips merge reduction level to EAGER or less When showing a conflicting merge result, and "--diff3 -m" style is asked for, this patch makes sure that the merge reduction level does not exceed XDL_MERGE_EAGER. This is because "diff3 -m" style output would not make sense for anything more aggressive than XDL_MERGE_EAGER, because of the way how the merge reduction works. "git merge-file" no longer has to force MERGE_EAGER when "--diff3" is asked for because of this change. Suppose a common ancestor (shared preimage) is modified to postimage #1 and #2 (each letter represents one line): ##### postimage#1: 1234ABCDE789 | / | / preimage: 123456789 | \ postimage#2: 1234AXYE789 #### XDL_MERGE_MINIMAL and XDL_MERGE_EAGER would: (1) find the s/56/ABCDE/ done on one side and s/56/AXYE/ done on the other side, (2) notice that they touch an overlapping area, and (3) mark it as a conflict, "ABCDE vs AXYE". The difference between the two algorithms is that EAGER drops the hunk altogether if the postimages match (i.e. both sides modified the same way), while MINIMAL keeps it. There is no other operation performed to the hunk. As the result, lines marked with "#" in the above picure will be in the RCS merge style output like this (letters <, = and > represent conflict marker lines): output: 1234789 ; with MINIMAL/EAGER The part from the preimage that corresponds to these conflicting changes is "56", which is what "diff3 -m" style output adds to it: output: 1234789 ; in "diff3 -m" style Now, XDL_MERGE_ZEALOUS looks at the differences between the changes two postimages made in order to reduce the number of lines in the conflicting regions. It notices that both sides start their new contents with "A", and excludes it from the output (it also excludes "E" for the same reason). The conflict that used to be "ABCDE vs AXYE" is now "BCD vs XY": output: 1234AE789 ; with ZEALOUS There could even be matching parts between two postimages in the middle. Instead of one side rewriting the shared "56" to "ABCDE" and the other side to "AXYE", imagine the case where the postimages are "ABCDE" and "AXCYE", in which case instead of having one conflicted hunk "BCD vs XY", you would have two conflicting hunks "B vs X" and "D vs Y". In either case, once you reduce "ABCDE vs AXYE" to "BCD vs XY" (or "ABCDE vs AXCYE" to "B vs X" and "D vs Y"), there is no part from the preimage that corresponds to the conflicting change made in both postimages anymore. In other words, conflict reduced by ZEALOUS algorithm cannot be expressed in "diff3 -m" style. Representing the last illustration like this is misleading to say the least: output: 1234AE789 ; broken "diff3 -m" style because the preimage was not ...4A56E... to begin with. "A" and "E" are common only between the postimages. Even worse, once a single conflicting hunk is split into multiple ones (recall the example of breaking "ABCDE vs AXCYE" to "B vs X" and "D vs Y"), there is no sane way to distribute the preimage text across split conflicting hunks. Signed-off-by: Junio C Hamano --- builtin-merge-file.c | 4 +--- xdiff/xmerge.c | 9 +++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 5b4f020e38..1e92510026 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -27,10 +27,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[1], "-q") || !strcmp(argv[1], "--quiet")) freopen("/dev/null", "w", stderr); - else if (!strcmp(argv[1], "--diff3")) { + else if (!strcmp(argv[1], "--diff3")) merge_style = XDL_MERGE_DIFF3; - merge_level = XDL_MERGE_EAGER; - } else usage(merge_file_usage); argc--; diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 7dcd4055ae..d9737f04c2 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -392,6 +392,15 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, int level = flags & XDL_MERGE_LEVEL_MASK; int style = flags & XDL_MERGE_STYLE_MASK; + if (style == XDL_MERGE_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } + c = changes = NULL; while (xscr1 && xscr2) { -- cgit v1.2.1 From cc58d7dfddc842360f6aade1507e1063d8cd0f40 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 10:12:23 -0700 Subject: rerere.c: use symbolic constants to keep track of parsing states These hardcoded integers make the code harder to follow than necessary; replace them with enums to make it easier to read, before adding support for optionally parsing "diff3 -m" style conflict markers. Signed-off-by: Junio C Hamano --- rerere.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rerere.c b/rerere.c index 323e493daf..bf74b262f0 100644 --- a/rerere.c +++ b/rerere.c @@ -75,7 +75,10 @@ static int handle_file(const char *path, { SHA_CTX ctx; char buf[1024]; - int hunk = 0, hunk_no = 0; + int hunk_no = 0; + enum { + RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, + } hunk = RR_CONTEXT; struct strbuf one, two; FILE *f = fopen(path, "r"); FILE *out = NULL; @@ -98,20 +101,20 @@ static int handle_file(const char *path, strbuf_init(&two, 0); while (fgets(buf, sizeof(buf), f)) { if (!prefixcmp(buf, "<<<<<<< ")) { - if (hunk) + if (hunk != RR_CONTEXT) goto bad; - hunk = 1; + hunk = RR_SIDE_1; } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { - if (hunk != 1) + if (hunk != RR_SIDE_1) goto bad; - hunk = 2; + hunk = RR_SIDE_2; } else if (!prefixcmp(buf, ">>>>>>> ")) { - if (hunk != 2) + if (hunk != RR_SIDE_2) goto bad; if (strbuf_cmp(&one, &two) > 0) strbuf_swap(&one, &two); hunk_no++; - hunk = 0; + hunk = RR_CONTEXT; if (out) { fputs("<<<<<<<\n", out); fwrite(one.buf, one.len, 1, out); @@ -127,9 +130,9 @@ static int handle_file(const char *path, } strbuf_reset(&one); strbuf_reset(&two); - } else if (hunk == 1) + } else if (hunk == RR_SIDE_1) strbuf_addstr(&one, buf); - else if (hunk == 2) + else if (hunk == RR_SIDE_2) strbuf_addstr(&two, buf); else if (out) fputs(buf, out); @@ -146,7 +149,7 @@ static int handle_file(const char *path, fclose(out); if (sha1) SHA1_Final(sha1, &ctx); - if (hunk) { + if (hunk != RR_CONTEXT) { if (output) unlink(output); return error("Could not parse conflict hunks in %s", path); -- cgit v1.2.1 From 387c9d49815ef4b1cefda71cf27f199d9fb24083 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 10:24:45 -0700 Subject: rerere: understand "diff3 -m" style conflicts with the original This teaches rerere to grok conflicts expressed in "diff3 -m" style output, where the version from the common ancestor is output after the first side, preceded by a "|||||||" line. The rerere database needs to keep only the versions from two sides, so the code parses the original copy and discards it. Signed-off-by: Junio C Hamano --- rerere.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rerere.c b/rerere.c index bf74b262f0..4e2c9dd5b7 100644 --- a/rerere.c +++ b/rerere.c @@ -77,7 +77,7 @@ static int handle_file(const char *path, char buf[1024]; int hunk_no = 0; enum { - RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, + RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL, } hunk = RR_CONTEXT; struct strbuf one, two; FILE *f = fopen(path, "r"); @@ -104,9 +104,13 @@ static int handle_file(const char *path, if (hunk != RR_CONTEXT) goto bad; hunk = RR_SIDE_1; - } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + } else if (!prefixcmp(buf, "|||||||") && isspace(buf[7])) { if (hunk != RR_SIDE_1) goto bad; + hunk = RR_ORIGINAL; + } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) { + if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL) + goto bad; hunk = RR_SIDE_2; } else if (!prefixcmp(buf, ">>>>>>> ")) { if (hunk != RR_SIDE_2) @@ -132,6 +136,8 @@ static int handle_file(const char *path, strbuf_reset(&two); } else if (hunk == RR_SIDE_1) strbuf_addstr(&one, buf); + else if (hunk == RR_ORIGINAL) + ; /* discard */ else if (hunk == RR_SIDE_2) strbuf_addstr(&two, buf); else if (out) -- cgit v1.2.1 From b541248467fa47979a34e3f1c5bbe3308fbdc4d1 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 10:49:56 -0700 Subject: merge.conflictstyle: choose between "merge" and "diff3 -m" styles This teaches "git merge-file" to honor merge.conflictstyle configuration variable, whose value can be "merge" (default) or "diff3". Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++++++++ builtin-merge-file.c | 9 +++++++++ t/t6023-merge-file.sh | 9 ++++++++- xdiff-interface.c | 20 ++++++++++++++++++++ xdiff-interface.h | 2 ++ 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 81f981509a..8c62ba4e7b 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -889,6 +889,14 @@ man..path:: Override the path for the given tool that may be used to display help in the 'man' format. See linkgit:git-help[1]. +merge.conflictstyle:: + Specify the style in which conflicted hunks are written out to + working tree files upon merge. The default is "merge", which + shows `<<<<<<<` conflict marker, change made by one side, + `=======` marker, change made by the other side, and then + `>>>>>>>` marker. An alternate style, "diff3", adds `|||||||` + marker and the original text before `=======` marker. + mergetool..path:: Override the path for the given tool. This is useful in case your tool is not in the PATH. diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 1e92510026..45c98538cd 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -15,6 +15,15 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) int ret = 0, i = 0, to_stdout = 0; int merge_level = XDL_MERGE_ZEALOUS_ALNUM; int merge_style = 0; + int nongit; + + prefix = setup_git_directory_gently(&nongit); + if (!nongit) { + /* Read the configuration file */ + git_config(git_xmerge_config, NULL); + if (0 <= git_xmerge_style) + merge_style = git_xmerge_style; + } while (argc > 4) { if (!strcmp(argv[1], "-L") && i < 3) { diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 20786ce79d..b76bca8880 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -192,10 +192,17 @@ non timebo mala, quoniam TU mecum es: virga tua et baculus tuus ipsa me consolata sunt. EOF -test_expect_success '"diff3 -m" style output' ' +test_expect_success '"diff3 -m" style output (1)' ' test_must_fail git merge-file -p --diff3 \ new8.txt new5.txt new9.txt >actual && test_cmp expect actual ' +test_expect_success '"diff3 -m" style output (2)' ' + git config merge.conflictstyle diff3 && + test_must_fail git merge-file -p \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + test_done diff --git a/xdiff-interface.c b/xdiff-interface.c index 61dc5c5470..295198333d 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -237,3 +237,23 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value) value = ep + 1; } } + +int git_xmerge_style = -1; + +int git_xmerge_config(const char *var, const char *value, void *cb) +{ + if (!strcasecmp(var, "merge.conflictstyle")) { + if (!value) + die("'%s' is not a boolean", var); + if (!strcmp(value, "diff3")) + git_xmerge_style = XDL_MERGE_DIFF3; + else if (!strcmp(value, "merge")) + git_xmerge_style = 0; + else + die("unknown style '%s' given for '%s'", + value, var); + return 0; + } + return git_default_config(var, value, cb); +} + diff --git a/xdiff-interface.h b/xdiff-interface.h index f7f791d96b..cfe3215cb2 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -22,5 +22,7 @@ int read_mmfile(mmfile_t *ptr, const char *filename); int buffer_is_binary(const char *ptr, unsigned long size); extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line); +extern int git_xmerge_config(const char *var, const char *value, void *cb); +extern int git_xmerge_style; #endif -- cgit v1.2.1 From c236bcd06138bcbc929b86ad1a513635bf4847b2 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 29 Aug 2008 10:59:16 -0700 Subject: git-merge-recursive: learn to honor merge.conflictstyle This teaches the low-level ll_xdl_merge() routine to honor merge.conflictstyle configuration variable, so that merge-recursive strategy can show the conflicts in the style of user's choice. Signed-off-by: Junio C Hamano --- builtin-merge-recursive.c | 2 +- ll-merge.c | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 43e55bf901..c4349d4697 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -1348,7 +1348,7 @@ static int merge_config(const char *var, const char *value, void *cb) merge_rename_limit = git_config_int(var, value); return 0; } - return git_default_config(var, value, cb); + return git_xmerge_config(var, value, cb); } int cmd_merge_recursive(int argc, const char **argv, const char *prefix) diff --git a/ll-merge.c b/ll-merge.c index 9837c842a3..4a716146f6 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -63,6 +63,7 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, int virtual_ancestor) { xpparam_t xpp; + int style = 0; if (buffer_is_binary(orig->ptr, orig->size) || buffer_is_binary(src1->ptr, src1->size) || @@ -77,10 +78,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, } memset(&xpp, 0, sizeof(xpp)); + if (git_xmerge_style >= 0) + style = git_xmerge_style; return xdl_merge(orig, src1, name1, src2, name2, - &xpp, XDL_MERGE_ZEALOUS, + &xpp, XDL_MERGE_ZEALOUS | style, result); } @@ -95,10 +98,15 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused, char *src, *dst; long size; const int marker_size = 7; - - int status = ll_xdl_merge(drv_unused, result, path_unused, - orig, src1, NULL, src2, NULL, - virtual_ancestor); + int status, saved_style; + + /* We have to force the RCS "merge" style */ + saved_style = git_xmerge_style; + git_xmerge_style = 0; + status = ll_xdl_merge(drv_unused, result, path_unused, + orig, src1, NULL, src2, NULL, + virtual_ancestor); + git_xmerge_style = saved_style; if (status <= 0) return status; size = result->size; -- cgit v1.2.1 From 0cf8581e330e7140c9f5c94a53d441187c0f8ff9 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 07:52:24 -0700 Subject: checkout -m: recreate merge when checking out of unmerged index This teaches git-checkout to recreate a merge out of unmerged index entries while resolving conflicts. With this patch, checking out an unmerged path from the index now have the following possibilities: * Without any option, an attempt to checkout an unmerged path will atomically fail (i.e. no other cleanly-merged paths are checked out either); * With "-f", other cleanly-merged paths are checked out, and unmerged paths are ignored; * With "--ours" or "--theirs, the contents from the specified stage is checked out; * With "-m" (we should add "--merge" as synonym), the 3-way merge is recreated from the staged object names and checked out. Signed-off-by: Junio C Hamano --- Documentation/git-checkout.txt | 11 +++-- builtin-checkout.c | 104 ++++++++++++++++++++++++++++++++++++++++- t/t7201-co.sh | 63 +++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 5 deletions(-) diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index a9ca2f5520..c884862e2f 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git checkout' [-q] [-f] [[--track | --no-track] -b [-l]] [-m] [] -'git checkout' [-f|--ours|--theirs] [] [--] ... +'git checkout' [-f|--ours|--theirs|-m] [] [--] ... DESCRIPTION ----------- @@ -35,7 +35,8 @@ default, if you try to check out such an entry from the index, the checkout operation will fail and nothing will be checked out. Using -f will ignore these unmerged entries. The contents from a specific side of the merge can be checked out of the index by -using --ours or --theirs. +using --ours or --theirs. With -m, changes made to the working tree +file can be discarded to recreate the original conflicted merge result. OPTIONS ------- @@ -83,7 +84,8 @@ entries; instead, unmerged entries are ignored. based sha1 expressions such as "@\{yesterday}". -m:: - If you have local modifications to one or more files that + When switching branches, + if you have local modifications to one or more files that are different between the current branch and the branch to which you are switching, the command refuses to switch branches in order to preserve your modifications in context. @@ -95,6 +97,9 @@ When a merge conflict happens, the index entries for conflicting paths are left unmerged, and you need to resolve the conflicts and mark the resolved paths with `git add` (or `git rm` if the merge should result in deletion of the path). ++ +When checking out paths from the index, this option lets you recreate +the conflicted merge in the specified paths. :: Name for the new branch. diff --git a/builtin-checkout.c b/builtin-checkout.c index 16bfbb6605..b957193155 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -13,6 +13,9 @@ #include "diff.h" #include "revision.h" #include "remote.h" +#include "blob.h" +#include "xdiff-interface.h" +#include "ll-merge.h" static const char * const checkout_usage[] = { "git checkout [options] ", @@ -109,6 +112,19 @@ static int check_stage(int stage, struct cache_entry *ce, int pos) (stage == 2) ? "our" : "their"); } +static int check_all_stages(struct cache_entry *ce, int pos) +{ + if (ce_stage(ce) != 1 || + active_nr <= pos + 2 || + strcmp(active_cache[pos+1]->name, ce->name) || + ce_stage(active_cache[pos+1]) != 2 || + strcmp(active_cache[pos+2]->name, ce->name) || + ce_stage(active_cache[pos+2]) != 3) + return error("path '%s' does not have all three versions", + ce->name); + return 0; +} + static int checkout_stage(int stage, struct cache_entry *ce, int pos, struct checkout *state) { @@ -123,6 +139,77 @@ static int checkout_stage(int stage, struct cache_entry *ce, int pos, (stage == 2) ? "our" : "their"); } +/* NEEDSWORK: share with merge-recursive */ +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } + + mm->ptr = read_sha1_file(sha1, &type, &size); + if (!mm->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; +} + +static int checkout_merged(int pos, struct checkout *state) +{ + struct cache_entry *ce = active_cache[pos]; + const char *path = ce->name; + mmfile_t ancestor, ours, theirs; + int status; + unsigned char sha1[20]; + mmbuffer_t result_buf; + + if (ce_stage(ce) != 1 || + active_nr <= pos + 2 || + strcmp(active_cache[pos+1]->name, path) || + ce_stage(active_cache[pos+1]) != 2 || + strcmp(active_cache[pos+2]->name, path) || + ce_stage(active_cache[pos+2]) != 3) + return error("path '%s' does not have all 3 versions", path); + + fill_mm(active_cache[pos]->sha1, &ancestor); + fill_mm(active_cache[pos+1]->sha1, &ours); + fill_mm(active_cache[pos+2]->sha1, &theirs); + + status = ll_merge(&result_buf, path, &ancestor, + &ours, "ours", &theirs, "theirs", 1); + free(ancestor.ptr); + free(ours.ptr); + free(theirs.ptr); + if (status < 0 || !result_buf.ptr) { + free(result_buf.ptr); + return error("path '%s': cannot merge", path); + } + + /* + * NEEDSWORK: + * There is absolutely no reason to write this as a blob object + * and create a phoney cache entry just to leak. This hack is + * primarily to get to the write_entry() machinery that massages + * the contents to work-tree format and writes out which only + * allows it for a cache entry. The code in write_entry() needs + * to be refactored to allow us to feed a + * instead of a cache entry. Such a refactoring would help + * merge_recursive as well (it also writes the merge result to the + * object database even when it may contain conflicts). + */ + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, sha1)) + die("Unable to add merge result for '%s'", path); + ce = make_cache_entry(create_ce_mode(active_cache[pos+1]->ce_mode), + sha1, + path, 2, 0); + status = checkout_entry(ce, state, NULL); + return status; +} + static int checkout_paths(struct tree *source_tree, const char **pathspec, struct checkout_opts *opts) { @@ -134,6 +221,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, struct commit *head; int errs = 0; int stage = opts->writeout_stage; + int merge = opts->merge; int newfd; struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -165,6 +253,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, warning("path '%s' is unmerged", ce->name); } else if (stage) { errs |= check_stage(stage, ce, pos); + } else if (opts->merge) { + errs |= check_all_stages(ce, pos); } else { errs = 1; error("path '%s' is unmerged", ce->name); @@ -188,6 +278,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, } if (stage) errs |= checkout_stage(stage, ce, pos, &state); + else if (merge) + errs |= checkout_merged(pos, &state); pos = skip_same_name(ce, pos) - 1; } } @@ -476,6 +568,11 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) return ret || opts->writeout_error; } +static int git_checkout_config(const char *var, const char *value, void *cb) +{ + return git_xmerge_config(var, value, cb); +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -502,7 +599,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); - git_config(git_default_config, NULL); + git_config(git_checkout_config, NULL); opts.track = git_branch_track; @@ -594,7 +691,7 @@ no_reference: die("invalid path specification"); /* Checkout paths */ - if (opts.new_branch || opts.merge) { + if (opts.new_branch) { if (argc == 1) { die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]); } else { @@ -602,6 +699,9 @@ no_reference: } } + if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge) + die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index."); + return checkout_paths(source_tree, pathspec, &opts); } diff --git a/t/t7201-co.sh b/t/t7201-co.sh index c7ae14118a..1d4ff6e8d3 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -407,4 +407,67 @@ test_expect_success 'checkout unmerged stage' ' test ztheirside = "z$(cat file)" ' +test_expect_success 'checkout with --merge' ' + rm -f .git/index && + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + ( + echo "100644 $A 0 fild" && + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" && + echo "100644 $A 0 filf" + ) | git update-index --index-info && + echo "none of the above" >sample && + echo ourside >expect && + cat sample >fild && + cat sample >file && + cat sample >filf && + git checkout -m -- fild file filf && + ( + echo "<<<<<<< ours" + echo ourside + echo "=======" + echo theirside + echo ">>>>>>> theirs" + ) >merged && + test_cmp expect fild && + test_cmp expect filf && + test_cmp merged file +' + +test_expect_success 'checkout with --merge, in diff3 -m style' ' + git config merge.conflictstyle diff3 && + rm -f .git/index && + O=$(echo original | git hash-object -w --stdin) && + A=$(echo ourside | git hash-object -w --stdin) && + B=$(echo theirside | git hash-object -w --stdin) && + ( + echo "100644 $A 0 fild" && + echo "100644 $O 1 file" && + echo "100644 $A 2 file" && + echo "100644 $B 3 file" && + echo "100644 $A 0 filf" + ) | git update-index --index-info && + echo "none of the above" >sample && + echo ourside >expect && + cat sample >fild && + cat sample >file && + cat sample >filf && + git checkout -m -- fild file filf && + ( + echo "<<<<<<< ours" + echo ourside + echo "|||||||" + echo original + echo "=======" + echo theirside + echo ">>>>>>> theirs" + ) >merged && + test_cmp expect fild && + test_cmp expect filf && + test_cmp merged file +' + test_done -- cgit v1.2.1 From bbb896d8e10f736bfda8f587c0009c358c9a8599 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 30 Aug 2008 14:35:15 -0700 Subject: gitattributes: -crlf is not binary The description of crlf attribute incorrectly said that "-crlf" means binary. It is true that for binary files you would want "-crlf", but that is not the same thing. We also have supported attribute macros and via that mechanism a handy "binary" to specify "-crlf -diff" at the same time. It was not documented anywhere as far as I can tell, even though the support was there from the very beginning. Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index db16b0ca5b..1993887937 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -105,9 +105,8 @@ Set:: Unset:: - Unsetting the `crlf` attribute on a path is meant to - mark the path as a "binary" file. The path never goes - through line endings conversion upon checkin/checkout. + Unsetting the `crlf` attribute on a path tells git not to + attempt any end-of-line conversion upon checkin or checkout. Unspecified:: @@ -482,6 +481,41 @@ in the file. E.g. the string `$Format:%H$` will be replaced by the commit hash. +USING ATTRIBUTE MACROS +---------------------- + +You do not want any end-of-line conversions applied to, nor textual diffs +produced for, any binary file you track. You would need to specify e.g. + +------------ +*.jpg -crlf -diff +------------ + +but that may become cumbersome, when you have many attributes. Using +attribute macros, you can specify groups of attributes set or unset at +the same time. The system knows a built-in attribute macro, `binary`: + +------------ +*.jpg binary +------------ + +which is equivalent to the above. Note that the attribute macros can only +be "Set" (see the above example that sets "binary" macro as if it were an +ordinary attribute --- setting it in turn unsets "crlf" and "diff"). + + +DEFINING ATTRIBUTE MACROS +------------------------- + +Custom attribute macros can be defined only in the `.gitattributes` file +at the toplevel (i.e. not in any subdirectory). The built-in attribute +macro "binary" is equivalent to: + +------------ +[attr]binary -diff -crlf +------------ + + EXAMPLE ------- -- cgit v1.2.1 From 392809702016cde59d50a7b07e8c27f6d0ec3c3f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 27 Aug 2008 19:48:01 -0700 Subject: diff: Help "less" hide ^M from the output When the tracked contents have CRLF line endings, colored diff output shows "^M" at the end of output lines, which is distracting, even though the pager we use by default ("less") knows to hide them. The problem is that "less" hides a carriage-return only at the end of the line, immediately before a line feed. The colored diff output does not take this into account, and emits four element sequence for each line: - force this color; - the line up to but not including the terminating line feed; - reset color - line feed. By including the carriage return at the end of the line in the second item, we are breaking the smart our pager has in order not to show "^M". This can be fixed by changing the sequence to: - force this color; - the line up to but not including the terminating end-of-line; - reset color - end-of-line. where end-of-line is either a single linefeed or a CRLF pair. When the output is not colored, "force this color" and "reset color" sequences are both empty, so we won't have this problem with or without this patch. Signed-off-by: Junio C Hamano --- combine-diff.c | 16 ++++++++++++++-- diff.c | 9 ++++++++- t/t4019-diff-wserror.sh | 12 ++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/combine-diff.c b/combine-diff.c index 4dfc330867..aa9d79ea0b 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -500,6 +500,18 @@ static int hunk_comment_line(const char *bol) return (isalpha(ch) || ch == '_' || ch == '$'); } +static void show_line_to_eol(const char *line, int len, const char *reset) +{ + int saw_cr_at_eol = 0; + if (len < 0) + len = strlen(line); + saw_cr_at_eol = (len && line[len-1] == '\r'); + + printf("%.*s%s%s\n", len - saw_cr_at_eol, line, + reset, + saw_cr_at_eol ? "\r" : ""); +} + static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, int use_color) { @@ -593,7 +605,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, else putchar(' '); } - printf("%s%s\n", ll->line, c_reset); + show_line_to_eol(ll->line, -1, c_reset); ll = ll->next; } if (cnt < lno) @@ -617,7 +629,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, putchar(' '); p_mask <<= 1; } - printf("%.*s%s\n", sl->len, sl->bol, c_reset); + show_line_to_eol(sl->bol, sl->len, c_reset); } } } diff --git a/diff.c b/diff.c index 7b4300a74a..6d56c69810 100644 --- a/diff.c +++ b/diff.c @@ -511,13 +511,20 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) { - int has_trailing_newline = (len > 0 && line[len-1] == '\n'); + int has_trailing_newline, has_trailing_carriage_return; + + has_trailing_newline = (len > 0 && line[len-1] == '\n'); if (has_trailing_newline) len--; + has_trailing_carriage_return = (len > 0 && line[len-1] == '\r'); + if (has_trailing_carriage_return) + len--; fputs(set, file); fwrite(line, len, 1, file); fputs(reset, file); + if (has_trailing_carriage_return) + fputc('\r', file); if (has_trailing_newline) fputc('\n', file); } diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index 7eae1f4500..84a1fe3115 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -178,4 +178,16 @@ test_expect_success 'trailing empty lines (2)' ' ' +test_expect_success 'do not color trailing cr in context' ' + git config --unset core.whitespace + rm -f .gitattributes && + echo AAAQ | tr Q "\015" >G && + git add G && + echo BBBQ | tr Q "\015" >>G + git diff --color G | tr "\015" Q >output && + grep "BBB.*${blue_grep}Q" output && + grep "AAA.*\[mQ" output + +' + test_done -- cgit v1.2.1 From 9047ebbc229bf5b99d6c7522293b8cbd1100b747 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Tue, 12 Aug 2008 18:45:14 +0200 Subject: Split out merge_recursive() to merge-recursive.c Move most of the of code from builtin-merge-recursive.c to a new file merge-recursive.c and introduce merge_recursive_setup() in there so that builtin-merge-recursive and other builtins call it. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin-merge-recursive.c | 1327 +------------------------------------------- merge-recursive.c | 1331 +++++++++++++++++++++++++++++++++++++++++++++ merge-recursive.h | 6 +- 4 files changed, 1341 insertions(+), 1324 deletions(-) create mode 100644 merge-recursive.c diff --git a/Makefile b/Makefile index 20f028ffff..f697618384 100644 --- a/Makefile +++ b/Makefile @@ -440,6 +440,7 @@ LIB_OBJS += log-tree.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o LIB_OBJS += merge-file.o +LIB_OBJS += merge-recursive.o LIB_OBJS += name-hash.o LIB_OBJS += object.o LIB_OBJS += pack-check.o diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index dfb363ee2f..8bf2fa5df3 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -1,1307 +1,8 @@ -/* - * Recursive Merge algorithm stolen from git-merge-recursive.py by - * Fredrik Kuivinen. - * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 - */ #include "cache.h" -#include "cache-tree.h" #include "commit.h" -#include "blob.h" -#include "builtin.h" -#include "tree-walk.h" -#include "diff.h" -#include "diffcore.h" #include "tag.h" -#include "unpack-trees.h" -#include "string-list.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "interpolate.h" -#include "attr.h" #include "merge-recursive.h" -static int subtree_merge; - -static struct tree *shift_tree_object(struct tree *one, struct tree *two) -{ - unsigned char shifted[20]; - - /* - * NEEDSWORK: this limits the recursion depth to hardcoded - * value '2' to avoid excessive overhead. - */ - shift_tree(one->object.sha1, two->object.sha1, shifted, 2); - if (!hashcmp(two->object.sha1, shifted)) - return two; - return lookup_tree(shifted); -} - -/* - * A virtual commit has - * - (const char *)commit->util set to the name, and - * - *(int *)commit->object.sha1 set to the virtual id. - */ - -static struct commit *make_virtual_commit(struct tree *tree, const char *comment) -{ - struct commit *commit = xcalloc(1, sizeof(struct commit)); - static unsigned virtual_id = 1; - commit->tree = tree; - commit->util = (void*)comment; - *(int*)commit->object.sha1 = virtual_id++; - /* avoid warnings */ - commit->object.parsed = 1; - return commit; -} - -/* - * Since we use get_tree_entry(), which does not put the read object into - * the object pool, we cannot rely on a == b. - */ -static int sha_eq(const unsigned char *a, const unsigned char *b) -{ - if (!a && !b) - return 2; - return a && b && hashcmp(a, b) == 0; -} - -/* - * Since we want to write the index eventually, we cannot reuse the index - * for these (temporary) data. - */ -struct stage_data -{ - struct - { - unsigned mode; - unsigned char sha[20]; - } stages[4]; - unsigned processed:1; -}; - -static struct string_list current_file_set = {NULL, 0, 0, 1}; -static struct string_list current_directory_set = {NULL, 0, 0, 1}; - -static int call_depth = 0; -static int verbosity = 2; -static int diff_rename_limit = -1; -static int merge_rename_limit = -1; -static int buffer_output = 1; -static struct strbuf obuf = STRBUF_INIT; - -static int show(int v) -{ - return (!call_depth && verbosity >= v) || verbosity >= 5; -} - -static void flush_output(void) -{ - if (obuf.len) { - fputs(obuf.buf, stdout); - strbuf_reset(&obuf); - } -} - -static void output(int v, const char *fmt, ...) -{ - int len; - va_list ap; - - if (!show(v)) - return; - - strbuf_grow(&obuf, call_depth * 2 + 2); - memset(obuf.buf + obuf.len, ' ', call_depth * 2); - strbuf_setlen(&obuf, obuf.len + call_depth * 2); - - va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); - va_end(ap); - - if (len < 0) - len = 0; - if (len >= strbuf_avail(&obuf)) { - strbuf_grow(&obuf, len + 2); - va_start(ap, fmt); - len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); - va_end(ap); - if (len >= strbuf_avail(&obuf)) { - die("this should not happen, your snprintf is broken"); - } - } - strbuf_setlen(&obuf, obuf.len + len); - strbuf_add(&obuf, "\n", 1); - if (!buffer_output) - flush_output(); -} - -static void output_commit_title(struct commit *commit) -{ - int i; - flush_output(); - for (i = call_depth; i--;) - fputs(" ", stdout); - if (commit->util) - printf("virtual %s\n", (char *)commit->util); - else { - printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); - if (parse_commit(commit) != 0) - printf("(bad commit)\n"); - else { - const char *s; - int len; - for (s = commit->buffer; *s; s++) - if (*s == '\n' && s[1] == '\n') { - s += 2; - break; - } - for (len = 0; s[len] && '\n' != s[len]; len++) - ; /* do nothing */ - printf("%.*s\n", len, s); - } - } -} - -static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, - const char *path, int stage, int refresh, int options) -{ - struct cache_entry *ce; - ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); - if (!ce) - return error("addinfo_cache failed for path '%s'", path); - return add_cache_entry(ce, options); -} - -/* - * This is a global variable which is used in a number of places but - * only written to in the 'merge' function. - * - * index_only == 1 => Don't leave any non-stage 0 entries in the cache and - * don't update the working directory. - * 0 => Leave unmerged entries in the cache and update - * the working directory. - */ -static int index_only = 0; - -static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) -{ - parse_tree(tree); - init_tree_desc(desc, tree->buffer, tree->size); -} - -static int git_merge_trees(int index_only, - struct tree *common, - struct tree *head, - struct tree *merge) -{ - int rc; - struct tree_desc t[3]; - struct unpack_trees_options opts; - - memset(&opts, 0, sizeof(opts)); - if (index_only) - opts.index_only = 1; - else - opts.update = 1; - opts.merge = 1; - opts.head_idx = 2; - opts.fn = threeway_merge; - opts.src_index = &the_index; - opts.dst_index = &the_index; - - init_tree_desc_from_tree(t+0, common); - init_tree_desc_from_tree(t+1, head); - init_tree_desc_from_tree(t+2, merge); - - rc = unpack_trees(3, t, &opts); - cache_tree_free(&active_cache_tree); - return rc; -} - -struct tree *write_tree_from_memory(void) -{ - struct tree *result = NULL; - - if (unmerged_cache()) { - int i; - output(0, "There are unmerged index entries:"); - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) - output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); - } - return NULL; - } - - if (!active_cache_tree) - active_cache_tree = cache_tree(); - - if (!cache_tree_fully_valid(active_cache_tree) && - cache_tree_update(active_cache_tree, - active_cache, active_nr, 0, 0) < 0) - die("error building trees"); - - result = lookup_tree(active_cache_tree->sha1); - - return result; -} - -static int save_files_dirs(const unsigned char *sha1, - const char *base, int baselen, const char *path, - unsigned int mode, int stage, void *context) -{ - int len = strlen(path); - char *newpath = xmalloc(baselen + len + 1); - memcpy(newpath, base, baselen); - memcpy(newpath + baselen, path, len); - newpath[baselen + len] = '\0'; - - if (S_ISDIR(mode)) - string_list_insert(newpath, ¤t_directory_set); - else - string_list_insert(newpath, ¤t_file_set); - free(newpath); - - return READ_TREE_RECURSIVE; -} - -static int get_files_dirs(struct tree *tree) -{ - int n; - if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL)) - return 0; - n = current_file_set.nr + current_directory_set.nr; - return n; -} - -/* - * Returns an index_entry instance which doesn't have to correspond to - * a real cache entry in Git's index. - */ -static struct stage_data *insert_stage_data(const char *path, - struct tree *o, struct tree *a, struct tree *b, - struct string_list *entries) -{ - struct string_list_item *item; - struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry(o->object.sha1, path, - e->stages[1].sha, &e->stages[1].mode); - get_tree_entry(a->object.sha1, path, - e->stages[2].sha, &e->stages[2].mode); - get_tree_entry(b->object.sha1, path, - e->stages[3].sha, &e->stages[3].mode); - item = string_list_insert(path, entries); - item->util = e; - return e; -} - -/* - * Create a dictionary mapping file names to stage_data objects. The - * dictionary contains one entry for every path with a non-zero stage entry. - */ -static struct string_list *get_unmerged(void) -{ - struct string_list *unmerged = xcalloc(1, sizeof(struct string_list)); - int i; - - unmerged->strdup_strings = 1; - - for (i = 0; i < active_nr; i++) { - struct string_list_item *item; - struct stage_data *e; - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - - item = string_list_lookup(ce->name, unmerged); - if (!item) { - item = string_list_insert(ce->name, unmerged); - item->util = xcalloc(1, sizeof(struct stage_data)); - } - e = item->util; - e->stages[ce_stage(ce)].mode = ce->ce_mode; - hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); - } - - return unmerged; -} - -struct rename -{ - struct diff_filepair *pair; - struct stage_data *src_entry; - struct stage_data *dst_entry; - unsigned processed:1; -}; - -/* - * Get information of all renames which occurred between 'o_tree' and - * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and - * 'b_tree') to be able to associate the correct cache entries with - * the rename information. 'tree' is always equal to either a_tree or b_tree. - */ -static struct string_list *get_renames(struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) -{ - int i; - struct string_list *renames; - struct diff_options opts; - - renames = xcalloc(1, sizeof(struct string_list)); - diff_setup(&opts); - DIFF_OPT_SET(&opts, RECURSIVE); - opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : - diff_rename_limit >= 0 ? diff_rename_limit : - 500; - opts.warn_on_too_large_rename = 1; - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - if (diff_setup_done(&opts) < 0) - die("diff setup failed"); - diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); - diffcore_std(&opts); - for (i = 0; i < diff_queued_diff.nr; ++i) { - struct string_list_item *item; - struct rename *re; - struct diff_filepair *pair = diff_queued_diff.queue[i]; - if (pair->status != 'R') { - diff_free_filepair(pair); - continue; - } - re = xmalloc(sizeof(*re)); - re->processed = 0; - re->pair = pair; - item = string_list_lookup(re->pair->one->path, entries); - if (!item) - re->src_entry = insert_stage_data(re->pair->one->path, - o_tree, a_tree, b_tree, entries); - else - re->src_entry = item->util; - - item = string_list_lookup(re->pair->two->path, entries); - if (!item) - re->dst_entry = insert_stage_data(re->pair->two->path, - o_tree, a_tree, b_tree, entries); - else - re->dst_entry = item->util; - item = string_list_insert(pair->one->path, renames); - item->util = re; - } - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_queued_diff.nr = 0; - diff_flush(&opts); - return renames; -} - -static int update_stages(const char *path, struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - int clear) -{ - int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; - if (clear) - if (remove_file_from_cache(path)) - return -1; - if (o) - if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) - return -1; - if (a) - if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) - return -1; - if (b) - if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) - return -1; - return 0; -} - -static int remove_path(const char *name) -{ - int ret; - char *slash, *dirs; - - ret = unlink(name); - if (ret) - return ret; - dirs = xstrdup(name); - while ((slash = strrchr(name, '/'))) { - *slash = '\0'; - if (rmdir(name) != 0) - break; - } - free(dirs); - return ret; -} - -static int remove_file(int clean, const char *path, int no_wd) -{ - int update_cache = index_only || clean; - int update_working_directory = !index_only && !no_wd; - - if (update_cache) { - if (remove_file_from_cache(path)) - return -1; - } - if (update_working_directory) { - unlink(path); - if (errno != ENOENT || errno != EISDIR) - return -1; - remove_path(path); - } - return 0; -} - -static char *unique_path(const char *path, const char *branch) -{ - char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); - int suffix = 0; - struct stat st; - char *p = newpath + strlen(path); - strcpy(newpath, path); - *(p++) = '~'; - strcpy(p, branch); - for (; *p; ++p) - if ('/' == *p) - *p = '_'; - while (string_list_has_string(¤t_file_set, newpath) || - string_list_has_string(¤t_directory_set, newpath) || - lstat(newpath, &st) == 0) - sprintf(p, "_%d", suffix++); - - string_list_insert(newpath, ¤t_file_set); - return newpath; -} - -static void flush_buffer(int fd, const char *buf, unsigned long size) -{ - while (size > 0) { - long ret = write_in_full(fd, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("merge-recursive: %s", strerror(errno)); - } else if (!ret) { - die("merge-recursive: disk full?"); - } - size -= ret; - buf += ret; - } -} - -static int make_room_for_path(const char *path) -{ - int status; - const char *msg = "failed to create path '%s'%s"; - - status = safe_create_leading_directories_const(path); - if (status) { - if (status == -3) { - /* something else exists */ - error(msg, path, ": perhaps a D/F conflict?"); - return -1; - } - die(msg, path, ""); - } - - /* Successful unlink is good.. */ - if (!unlink(path)) - return 0; - /* .. and so is no existing file */ - if (errno == ENOENT) - return 0; - /* .. but not some other error (who really cares what?) */ - return error(msg, path, ": perhaps a D/F conflict?"); -} - -static void update_file_flags(const unsigned char *sha, - unsigned mode, - const char *path, - int update_cache, - int update_wd) -{ - if (index_only) - update_wd = 0; - - if (update_wd) { - enum object_type type; - void *buf; - unsigned long size; - - if (S_ISGITLINK(mode)) - die("cannot read object %s '%s': It is a submodule!", - sha1_to_hex(sha), path); - - buf = read_sha1_file(sha, &type, &size); - if (!buf) - die("cannot read object %s '%s'", sha1_to_hex(sha), path); - if (type != OBJ_BLOB) - die("blob expected for %s '%s'", sha1_to_hex(sha), path); - if (S_ISREG(mode)) { - struct strbuf strbuf; - strbuf_init(&strbuf, 0); - if (convert_to_working_tree(path, buf, size, &strbuf)) { - free(buf); - size = strbuf.len; - buf = strbuf_detach(&strbuf, NULL); - } - } - - if (make_room_for_path(path) < 0) { - update_wd = 0; - free(buf); - goto update_index; - } - if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) { - int fd; - if (mode & 0100) - mode = 0777; - else - mode = 0666; - fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); - if (fd < 0) - die("failed to open %s: %s", path, strerror(errno)); - flush_buffer(fd, buf, size); - close(fd); - } else if (S_ISLNK(mode)) { - char *lnk = xmemdupz(buf, size); - safe_create_leading_directories_const(path); - unlink(path); - symlink(lnk, path); - free(lnk); - } else - die("do not know what to do with %06o %s '%s'", - mode, sha1_to_hex(sha), path); - free(buf); - } - update_index: - if (update_cache) - add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); -} - -static void update_file(int clean, - const unsigned char *sha, - unsigned mode, - const char *path) -{ - update_file_flags(sha, mode, path, index_only || clean, !index_only); -} - -/* Low level file merging, update and removal */ - -struct merge_file_info -{ - unsigned char sha[20]; - unsigned mode; - unsigned clean:1, - merge:1; -}; - -static void fill_mm(const unsigned char *sha1, mmfile_t *mm) -{ - unsigned long size; - enum object_type type; - - if (!hashcmp(sha1, null_sha1)) { - mm->ptr = xstrdup(""); - mm->size = 0; - return; - } - - mm->ptr = read_sha1_file(sha1, &type, &size); - if (!mm->ptr || type != OBJ_BLOB) - die("unable to read blob object %s", sha1_to_hex(sha1)); - mm->size = size; -} - -static int merge_3way(mmbuffer_t *result_buf, - struct diff_filespec *o, - struct diff_filespec *a, - struct diff_filespec *b, - const char *branch1, - const char *branch2) -{ - mmfile_t orig, src1, src2; - char *name1, *name2; - int merge_status; - - name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); - name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); - - fill_mm(o->sha1, &orig); - fill_mm(a->sha1, &src1); - fill_mm(b->sha1, &src2); - - merge_status = ll_merge(result_buf, a->path, &orig, - &src1, name1, &src2, name2, - index_only); - - free(name1); - free(name2); - free(orig.ptr); - free(src1.ptr); - free(src2.ptr); - return merge_status; -} - -static struct merge_file_info merge_file(struct diff_filespec *o, - struct diff_filespec *a, struct diff_filespec *b, - const char *branch1, const char *branch2) -{ - struct merge_file_info result; - result.merge = 0; - result.clean = 1; - - if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { - result.clean = 0; - if (S_ISREG(a->mode)) { - result.mode = a->mode; - hashcpy(result.sha, a->sha1); - } else { - result.mode = b->mode; - hashcpy(result.sha, b->sha1); - } - } else { - if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) - result.merge = 1; - - /* - * Merge modes - */ - if (a->mode == b->mode || a->mode == o->mode) - result.mode = b->mode; - else { - result.mode = a->mode; - if (b->mode != o->mode) { - result.clean = 0; - result.merge = 1; - } - } - - if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) - hashcpy(result.sha, b->sha1); - else if (sha_eq(b->sha1, o->sha1)) - hashcpy(result.sha, a->sha1); - else if (S_ISREG(a->mode)) { - mmbuffer_t result_buf; - int merge_status; - - merge_status = merge_3way(&result_buf, o, a, b, - branch1, branch2); - - if ((merge_status < 0) || !result_buf.ptr) - die("Failed to execute internal merge"); - - if (write_sha1_file(result_buf.ptr, result_buf.size, - blob_type, result.sha)) - die("Unable to add %s to database", - a->path); - - free(result_buf.ptr); - result.clean = (merge_status == 0); - } else if (S_ISGITLINK(a->mode)) { - result.clean = 0; - hashcpy(result.sha, a->sha1); - } else if (S_ISLNK(a->mode)) { - hashcpy(result.sha, a->sha1); - - if (!sha_eq(a->sha1, b->sha1)) - result.clean = 0; - } else { - die("unsupported object type in the tree"); - } - } - - return result; -} - -static void conflict_rename_rename(struct rename *ren1, - const char *branch1, - struct rename *ren2, - const char *branch2) -{ - char *del[2]; - int delp = 0; - const char *ren1_dst = ren1->pair->two->path; - const char *ren2_dst = ren2->pair->two->path; - const char *dst_name1 = ren1_dst; - const char *dst_name2 = ren2_dst; - if (string_list_has_string(¤t_directory_set, ren1_dst)) { - dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s adding as %s instead", - ren1_dst, branch2, dst_name1); - remove_file(0, ren1_dst, 0); - } - if (string_list_has_string(¤t_directory_set, ren2_dst)) { - dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s adding as %s instead", - ren2_dst, branch1, dst_name2); - remove_file(0, ren2_dst, 0); - } - if (index_only) { - remove_file_from_cache(dst_name1); - remove_file_from_cache(dst_name2); - /* - * Uncomment to leave the conflicting names in the resulting tree - * - * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); - * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); - */ - } else { - update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); - update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); - } - while (delp--) - free(del[delp]); -} - -static void conflict_rename_dir(struct rename *ren1, - const char *branch1) -{ - char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); - free(new_path); -} - -static void conflict_rename_rename_2(struct rename *ren1, - const char *branch1, - struct rename *ren2, - const char *branch2) -{ - char *new_path1 = unique_path(ren1->pair->two->path, branch1); - char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renaming %s to %s and %s to %s instead", - ren1->pair->one->path, new_path1, - ren2->pair->one->path, new_path2); - remove_file(0, ren1->pair->two->path, 0); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); - update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); - free(new_path2); - free(new_path1); -} - -static int process_renames(struct string_list *a_renames, - struct string_list *b_renames, - const char *a_branch, - const char *b_branch) -{ - int clean_merge = 1, i, j; - struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; - const struct rename *sre; - - for (i = 0; i < a_renames->nr; i++) { - sre = a_renames->items[i].util; - string_list_insert(sre->pair->two->path, &a_by_dst)->util - = sre->dst_entry; - } - for (i = 0; i < b_renames->nr; i++) { - sre = b_renames->items[i].util; - string_list_insert(sre->pair->two->path, &b_by_dst)->util - = sre->dst_entry; - } - - for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { - int compare; - char *src; - struct string_list *renames1, *renames2, *renames2Dst; - struct rename *ren1 = NULL, *ren2 = NULL; - const char *branch1, *branch2; - const char *ren1_src, *ren1_dst; - - if (i >= a_renames->nr) { - compare = 1; - ren2 = b_renames->items[j++].util; - } else if (j >= b_renames->nr) { - compare = -1; - ren1 = a_renames->items[i++].util; - } else { - compare = strcmp(a_renames->items[i].string, - b_renames->items[j].string); - if (compare <= 0) - ren1 = a_renames->items[i++].util; - if (compare >= 0) - ren2 = b_renames->items[j++].util; - } - - /* TODO: refactor, so that 1/2 are not needed */ - if (ren1) { - renames1 = a_renames; - renames2 = b_renames; - renames2Dst = &b_by_dst; - branch1 = a_branch; - branch2 = b_branch; - } else { - struct rename *tmp; - renames1 = b_renames; - renames2 = a_renames; - renames2Dst = &a_by_dst; - branch1 = b_branch; - branch2 = a_branch; - tmp = ren2; - ren2 = ren1; - ren1 = tmp; - } - src = ren1->pair->one->path; - - ren1->dst_entry->processed = 1; - ren1->src_entry->processed = 1; - - if (ren1->processed) - continue; - ren1->processed = 1; - - ren1_src = ren1->pair->one->path; - ren1_dst = ren1->pair->two->path; - - if (ren2) { - const char *ren2_src = ren2->pair->one->path; - const char *ren2_dst = ren2->pair->two->path; - /* Renamed in 1 and renamed in 2 */ - if (strcmp(ren1_src, ren2_src) != 0) - die("ren1.src != ren2.src"); - ren2->dst_entry->processed = 1; - ren2->processed = 1; - if (strcmp(ren1_dst, ren2_dst) != 0) { - clean_merge = 0; - output(1, "CONFLICT (rename/rename): " - "Rename \"%s\"->\"%s\" in branch \"%s\" " - "rename \"%s\"->\"%s\" in \"%s\"%s", - src, ren1_dst, branch1, - src, ren2_dst, branch2, - index_only ? " (left unresolved)": ""); - if (index_only) { - remove_file_from_cache(src); - update_file(0, ren1->pair->one->sha1, - ren1->pair->one->mode, src); - } - conflict_rename_rename(ren1, branch1, ren2, branch2); - } else { - struct merge_file_info mfi; - remove_file(1, ren1_src, 1); - mfi = merge_file(ren1->pair->one, - ren1->pair->two, - ren2->pair->two, - branch1, - branch2); - if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); - - if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); - - if (!mfi.clean) { - output(1, "CONFLICT (content): merge conflict in %s", - ren1_dst); - clean_merge = 0; - - if (!index_only) - update_stages(ren1_dst, - ren1->pair->one, - ren1->pair->two, - ren2->pair->two, - 1 /* clear */); - } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); - } - } else { - /* Renamed in 1, maybe changed in 2 */ - struct string_list_item *item; - /* we only use sha1 and mode of these */ - struct diff_filespec src_other, dst_other; - int try_merge, stage = a_renames == renames1 ? 3: 2; - - remove_file(1, ren1_src, index_only || stage == 3); - - hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); - src_other.mode = ren1->src_entry->stages[stage].mode; - hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); - dst_other.mode = ren1->dst_entry->stages[stage].mode; - - try_merge = 0; - - if (string_list_has_string(¤t_directory_set, ren1_dst)) { - clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " - " directory %s added in %s", - ren1_src, ren1_dst, branch1, - ren1_dst, branch2); - conflict_rename_dir(ren1, branch1); - } else if (sha_eq(src_other.sha1, null_sha1)) { - clean_merge = 0; - output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " - "and deleted in %s", - ren1_src, ren1_dst, branch1, - branch2); - update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); - } else if (!sha_eq(dst_other.sha1, null_sha1)) { - const char *new_path; - clean_merge = 0; - try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " - "%s added in %s", - ren1_src, ren1_dst, branch1, - ren1_dst, branch2); - new_path = unique_path(ren1_dst, branch2); - output(1, "Adding as %s instead", new_path); - update_file(0, dst_other.sha1, dst_other.mode, new_path); - } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { - ren2 = item->util; - clean_merge = 0; - ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " - "Rename %s->%s in %s", - ren1_src, ren1_dst, branch1, - ren2->pair->one->path, ren2->pair->two->path, branch2); - conflict_rename_rename_2(ren1, branch1, ren2, branch2); - } else - try_merge = 1; - - if (try_merge) { - struct diff_filespec *o, *a, *b; - struct merge_file_info mfi; - src_other.path = (char *)ren1_src; - - o = ren1->pair->one; - if (a_renames == renames1) { - a = ren1->pair->two; - b = &src_other; - } else { - b = ren1->pair->two; - a = &src_other; - } - mfi = merge_file(o, a, b, - a_branch, b_branch); - - if (mfi.clean && - sha_eq(mfi.sha, ren1->pair->two->sha1) && - mfi.mode == ren1->pair->two->mode) - /* - * This messaged is part of - * t6022 test. If you change - * it update the test too. - */ - output(3, "Skipped %s (merged same as existing)", ren1_dst); - else { - if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); - if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); - if (!mfi.clean) { - output(1, "CONFLICT (rename/modify): Merge conflict in %s", - ren1_dst); - clean_merge = 0; - - if (!index_only) - update_stages(ren1_dst, - o, a, b, 1); - } - update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); - } - } - } - } - string_list_clear(&a_by_dst, 0); - string_list_clear(&b_by_dst, 0); - - return clean_merge; -} - -static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) -{ - return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; -} - -/* Per entry merge function */ -static int process_entry(const char *path, struct stage_data *entry, - const char *branch1, - const char *branch2) -{ - /* - printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); - print_index_entry("\tpath: ", entry); - */ - int clean_merge = 1; - unsigned o_mode = entry->stages[1].mode; - unsigned a_mode = entry->stages[2].mode; - unsigned b_mode = entry->stages[3].mode; - unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); - unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); - unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); - - if (o_sha && (!a_sha || !b_sha)) { - /* Case A: Deleted in one */ - if ((!a_sha && !b_sha) || - (sha_eq(a_sha, o_sha) && !b_sha) || - (!a_sha && sha_eq(b_sha, o_sha))) { - /* Deleted in both or deleted in one and - * unchanged in the other */ - if (a_sha) - output(2, "Removing %s", path); - /* do not touch working file if it did not exist */ - remove_file(1, path, !a_sha); - } else { - /* Deleted in one and changed in the other */ - clean_merge = 0; - if (!a_sha) { - output(1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree.", - path, branch1, - branch2, branch2, path); - update_file(0, b_sha, b_mode, path); - } else { - output(1, "CONFLICT (delete/modify): %s deleted in %s " - "and modified in %s. Version %s of %s left in tree.", - path, branch2, - branch1, branch1, path); - update_file(0, a_sha, a_mode, path); - } - } - - } else if ((!o_sha && a_sha && !b_sha) || - (!o_sha && !a_sha && b_sha)) { - /* Case B: Added in one. */ - const char *add_branch; - const char *other_branch; - unsigned mode; - const unsigned char *sha; - const char *conf; - - if (a_sha) { - add_branch = branch1; - other_branch = branch2; - mode = a_mode; - sha = a_sha; - conf = "file/directory"; - } else { - add_branch = branch2; - other_branch = branch1; - mode = b_mode; - sha = b_sha; - conf = "directory/file"; - } - if (string_list_has_string(¤t_directory_set, path)) { - const char *new_path = unique_path(path, add_branch); - clean_merge = 0; - output(1, "CONFLICT (%s): There is a directory with name %s in %s. " - "Adding %s as %s", - conf, path, other_branch, path, new_path); - remove_file(0, path, 0); - update_file(0, sha, mode, new_path); - } else { - output(2, "Adding %s", path); - update_file(1, sha, mode, path); - } - } else if (a_sha && b_sha) { - /* Case C: Added in both (check for same permissions) and */ - /* case D: Modified in both, but differently. */ - const char *reason = "content"; - struct merge_file_info mfi; - struct diff_filespec o, a, b; - - if (!o_sha) { - reason = "add/add"; - o_sha = (unsigned char *)null_sha1; - } - output(2, "Auto-merging %s", path); - o.path = a.path = b.path = (char *)path; - hashcpy(o.sha1, o_sha); - o.mode = o_mode; - hashcpy(a.sha1, a_sha); - a.mode = a_mode; - hashcpy(b.sha1, b_sha); - b.mode = b_mode; - - mfi = merge_file(&o, &a, &b, - branch1, branch2); - - clean_merge = mfi.clean; - if (mfi.clean) - update_file(1, mfi.sha, mfi.mode, path); - else if (S_ISGITLINK(mfi.mode)) - output(1, "CONFLICT (submodule): Merge conflict in %s " - "- needs %s", path, sha1_to_hex(b.sha1)); - else { - output(1, "CONFLICT (%s): Merge conflict in %s", - reason, path); - - if (index_only) - update_file(0, mfi.sha, mfi.mode, path); - else - update_file_flags(mfi.sha, mfi.mode, path, - 0 /* update_cache */, 1 /* update_working_directory */); - } - } else if (!o_sha && !a_sha && !b_sha) { - /* - * this entry was deleted altogether. a_mode == 0 means - * we had that path and want to actively remove it. - */ - remove_file(1, path, !a_mode); - } else - die("Fatal merge failure, shouldn't happen."); - - return clean_merge; -} - -int merge_trees(struct tree *head, - struct tree *merge, - struct tree *common, - const char *branch1, - const char *branch2, - struct tree **result) -{ - int code, clean; - - if (subtree_merge) { - merge = shift_tree_object(head, merge); - common = shift_tree_object(head, common); - } - - if (sha_eq(common->object.sha1, merge->object.sha1)) { - output(0, "Already uptodate!"); - *result = head; - return 1; - } - - code = git_merge_trees(index_only, common, head, merge); - - if (code != 0) - die("merging of trees %s and %s failed", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); - - if (unmerged_cache()) { - struct string_list *entries, *re_head, *re_merge; - int i; - string_list_clear(¤t_file_set, 1); - string_list_clear(¤t_directory_set, 1); - get_files_dirs(head); - get_files_dirs(merge); - - entries = get_unmerged(); - re_head = get_renames(head, common, head, merge, entries); - re_merge = get_renames(merge, common, head, merge, entries); - clean = process_renames(re_head, re_merge, - branch1, branch2); - for (i = 0; i < entries->nr; i++) { - const char *path = entries->items[i].string; - struct stage_data *e = entries->items[i].util; - if (!e->processed - && !process_entry(path, e, branch1, branch2)) - clean = 0; - } - - string_list_clear(re_merge, 0); - string_list_clear(re_head, 0); - string_list_clear(entries, 1); - - } - else - clean = 1; - - if (index_only) - *result = write_tree_from_memory(); - - return clean; -} - -static struct commit_list *reverse_commit_list(struct commit_list *list) -{ - struct commit_list *next = NULL, *current, *backup; - for (current = list; current; current = backup) { - backup = current->next; - current->next = next; - next = current; - } - return next; -} - -/* - * Merge the commits h1 and h2, return the resulting virtual - * commit object and a flag indicating the cleanness of the merge. - */ -int merge_recursive(struct commit *h1, - struct commit *h2, - const char *branch1, - const char *branch2, - struct commit_list *ca, - struct commit **result) -{ - struct commit_list *iter; - struct commit *merged_common_ancestors; - struct tree *mrtree = mrtree; - int clean; - - if (show(4)) { - output(4, "Merging:"); - output_commit_title(h1); - output_commit_title(h2); - } - - if (!ca) { - ca = get_merge_bases(h1, h2, 1); - ca = reverse_commit_list(ca); - } - - if (show(5)) { - output(5, "found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); - } - - merged_common_ancestors = pop_commit(&ca); - if (merged_common_ancestors == NULL) { - /* if there is no common ancestor, make an empty tree */ - struct tree *tree = xcalloc(1, sizeof(struct tree)); - - tree->object.parsed = 1; - tree->object.type = OBJ_TREE; - pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); - merged_common_ancestors = make_virtual_commit(tree, "ancestor"); - } - - for (iter = ca; iter; iter = iter->next) { - call_depth++; - /* - * When the merge fails, the result contains files - * with conflict markers. The cleanness flag is - * ignored, it was never actually used, as result of - * merge_trees has always overwritten it: the committed - * "conflicts" were already resolved. - */ - discard_cache(); - merge_recursive(merged_common_ancestors, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - NULL, - &merged_common_ancestors); - call_depth--; - - if (!merged_common_ancestors) - die("merge returned no commit"); - } - - discard_cache(); - if (!call_depth) { - read_cache(); - index_only = 0; - } else - index_only = 1; - - clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, - branch1, branch2, &mrtree); - - if (index_only) { - *result = make_virtual_commit(mrtree, "merged tree"); - commit_list_insert(h1, &(*result)->parents); - commit_list_insert(h2, &(*result)->parents->next); - } - flush_output(); - return clean; -} - static const char *better_branch_name(const char *branch) { static char githead_env[8 + 40 + 1]; @@ -1334,23 +35,6 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } -static int merge_config(const char *var, const char *value, void *cb) -{ - if (!strcasecmp(var, "merge.verbosity")) { - verbosity = git_config_int(var, value); - return 0; - } - if (!strcasecmp(var, "diff.renamelimit")) { - diff_rename_limit = git_config_int(var, value); - return 0; - } - if (!strcasecmp(var, "merge.renamelimit")) { - merge_rename_limit = git_config_int(var, value); - return 0; - } - return git_default_config(var, value, cb); -} - int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { static const char *bases[20]; @@ -1361,6 +45,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) struct commit_list *ca = NULL; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; + int subtree_merge = 0; if (argv[0]) { int namelen = strlen(argv[0]); @@ -1369,10 +54,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) subtree_merge = 1; } - git_config(merge_config, NULL); - if (getenv("GIT_MERGE_VERBOSITY")) - verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); - + git_config(merge_recursive_config, NULL); + merge_recursive_setup(subtree_merge); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); @@ -1384,8 +67,6 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); - if (verbosity >= 5) - buffer_output = 0; branch1 = argv[++i]; branch2 = argv[++i]; @@ -1396,7 +77,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - if (show(3)) + if (merge_recursive_verbosity >= 3) printf("Merging %s with %s\n", branch1, branch2); index_fd = hold_locked_index(lock, 1); diff --git a/merge-recursive.c b/merge-recursive.c new file mode 100644 index 0000000000..a0cb3f47de --- /dev/null +++ b/merge-recursive.c @@ -0,0 +1,1331 @@ +/* + * Recursive Merge algorithm stolen from git-merge-recursive.py by + * Fredrik Kuivinen. + * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006 + */ +#include "cache.h" +#include "cache-tree.h" +#include "commit.h" +#include "blob.h" +#include "builtin.h" +#include "tree-walk.h" +#include "diff.h" +#include "diffcore.h" +#include "tag.h" +#include "unpack-trees.h" +#include "string-list.h" +#include "xdiff-interface.h" +#include "ll-merge.h" +#include "interpolate.h" +#include "attr.h" +#include "merge-recursive.h" + +static int subtree_merge; + +static struct tree *shift_tree_object(struct tree *one, struct tree *two) +{ + unsigned char shifted[20]; + + /* + * NEEDSWORK: this limits the recursion depth to hardcoded + * value '2' to avoid excessive overhead. + */ + shift_tree(one->object.sha1, two->object.sha1, shifted, 2); + if (!hashcmp(two->object.sha1, shifted)) + return two; + return lookup_tree(shifted); +} + +/* + * A virtual commit has + * - (const char *)commit->util set to the name, and + * - *(int *)commit->object.sha1 set to the virtual id. + */ + +struct commit *make_virtual_commit(struct tree *tree, const char *comment) +{ + struct commit *commit = xcalloc(1, sizeof(struct commit)); + static unsigned virtual_id = 1; + commit->tree = tree; + commit->util = (void*)comment; + *(int*)commit->object.sha1 = virtual_id++; + /* avoid warnings */ + commit->object.parsed = 1; + return commit; +} + +/* + * Since we use get_tree_entry(), which does not put the read object into + * the object pool, we cannot rely on a == b. + */ +static int sha_eq(const unsigned char *a, const unsigned char *b) +{ + if (!a && !b) + return 2; + return a && b && hashcmp(a, b) == 0; +} + +/* + * Since we want to write the index eventually, we cannot reuse the index + * for these (temporary) data. + */ +struct stage_data +{ + struct + { + unsigned mode; + unsigned char sha[20]; + } stages[4]; + unsigned processed:1; +}; + +static struct string_list current_file_set = {NULL, 0, 0, 1}; +static struct string_list current_directory_set = {NULL, 0, 0, 1}; + +static int call_depth = 0; +int merge_recursive_verbosity = 2; +static int diff_rename_limit = -1; +static int merge_rename_limit = -1; +static int buffer_output = 1; +static struct strbuf obuf = STRBUF_INIT; + +static int show(int v) +{ + return (!call_depth && merge_recursive_verbosity >= v) || + merge_recursive_verbosity >= 5; +} + +static void flush_output(void) +{ + if (obuf.len) { + fputs(obuf.buf, stdout); + strbuf_reset(&obuf); + } +} + +static void output(int v, const char *fmt, ...) +{ + int len; + va_list ap; + + if (!show(v)) + return; + + strbuf_grow(&obuf, call_depth * 2 + 2); + memset(obuf.buf + obuf.len, ' ', call_depth * 2); + strbuf_setlen(&obuf, obuf.len + call_depth * 2); + + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + + if (len < 0) + len = 0; + if (len >= strbuf_avail(&obuf)) { + strbuf_grow(&obuf, len + 2); + va_start(ap, fmt); + len = vsnprintf(obuf.buf + obuf.len, strbuf_avail(&obuf), fmt, ap); + va_end(ap); + if (len >= strbuf_avail(&obuf)) { + die("this should not happen, your snprintf is broken"); + } + } + strbuf_setlen(&obuf, obuf.len + len); + strbuf_add(&obuf, "\n", 1); + if (!buffer_output) + flush_output(); +} + +static void output_commit_title(struct commit *commit) +{ + int i; + flush_output(); + for (i = call_depth; i--;) + fputs(" ", stdout); + if (commit->util) + printf("virtual %s\n", (char *)commit->util); + else { + printf("%s ", find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + if (parse_commit(commit) != 0) + printf("(bad commit)\n"); + else { + const char *s; + int len; + for (s = commit->buffer; *s; s++) + if (*s == '\n' && s[1] == '\n') { + s += 2; + break; + } + for (len = 0; s[len] && '\n' != s[len]; len++) + ; /* do nothing */ + printf("%.*s\n", len, s); + } + } +} + +static int add_cacheinfo(unsigned int mode, const unsigned char *sha1, + const char *path, int stage, int refresh, int options) +{ + struct cache_entry *ce; + ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh); + if (!ce) + return error("addinfo_cache failed for path '%s'", path); + return add_cache_entry(ce, options); +} + +/* + * This is a global variable which is used in a number of places but + * only written to in the 'merge' function. + * + * index_only == 1 => Don't leave any non-stage 0 entries in the cache and + * don't update the working directory. + * 0 => Leave unmerged entries in the cache and update + * the working directory. + */ +static int index_only = 0; + +static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree) +{ + parse_tree(tree); + init_tree_desc(desc, tree->buffer, tree->size); +} + +static int git_merge_trees(int index_only, + struct tree *common, + struct tree *head, + struct tree *merge) +{ + int rc; + struct tree_desc t[3]; + struct unpack_trees_options opts; + + memset(&opts, 0, sizeof(opts)); + if (index_only) + opts.index_only = 1; + else + opts.update = 1; + opts.merge = 1; + opts.head_idx = 2; + opts.fn = threeway_merge; + opts.src_index = &the_index; + opts.dst_index = &the_index; + + init_tree_desc_from_tree(t+0, common); + init_tree_desc_from_tree(t+1, head); + init_tree_desc_from_tree(t+2, merge); + + rc = unpack_trees(3, t, &opts); + cache_tree_free(&active_cache_tree); + return rc; +} + +struct tree *write_tree_from_memory(void) +{ + struct tree *result = NULL; + + if (unmerged_cache()) { + int i; + output(0, "There are unmerged index entries:"); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (ce_stage(ce)) + output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); + } + return NULL; + } + + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree) && + cache_tree_update(active_cache_tree, + active_cache, active_nr, 0, 0) < 0) + die("error building trees"); + + result = lookup_tree(active_cache_tree->sha1); + + return result; +} + +static int save_files_dirs(const unsigned char *sha1, + const char *base, int baselen, const char *path, + unsigned int mode, int stage, void *context) +{ + int len = strlen(path); + char *newpath = xmalloc(baselen + len + 1); + memcpy(newpath, base, baselen); + memcpy(newpath + baselen, path, len); + newpath[baselen + len] = '\0'; + + if (S_ISDIR(mode)) + string_list_insert(newpath, ¤t_directory_set); + else + string_list_insert(newpath, ¤t_file_set); + free(newpath); + + return READ_TREE_RECURSIVE; +} + +static int get_files_dirs(struct tree *tree) +{ + int n; + if (read_tree_recursive(tree, "", 0, 0, NULL, save_files_dirs, NULL)) + return 0; + n = current_file_set.nr + current_directory_set.nr; + return n; +} + +/* + * Returns an index_entry instance which doesn't have to correspond to + * a real cache entry in Git's index. + */ +static struct stage_data *insert_stage_data(const char *path, + struct tree *o, struct tree *a, struct tree *b, + struct string_list *entries) +{ + struct string_list_item *item; + struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); + get_tree_entry(o->object.sha1, path, + e->stages[1].sha, &e->stages[1].mode); + get_tree_entry(a->object.sha1, path, + e->stages[2].sha, &e->stages[2].mode); + get_tree_entry(b->object.sha1, path, + e->stages[3].sha, &e->stages[3].mode); + item = string_list_insert(path, entries); + item->util = e; + return e; +} + +/* + * Create a dictionary mapping file names to stage_data objects. The + * dictionary contains one entry for every path with a non-zero stage entry. + */ +static struct string_list *get_unmerged(void) +{ + struct string_list *unmerged = xcalloc(1, sizeof(struct string_list)); + int i; + + unmerged->strdup_strings = 1; + + for (i = 0; i < active_nr; i++) { + struct string_list_item *item; + struct stage_data *e; + struct cache_entry *ce = active_cache[i]; + if (!ce_stage(ce)) + continue; + + item = string_list_lookup(ce->name, unmerged); + if (!item) { + item = string_list_insert(ce->name, unmerged); + item->util = xcalloc(1, sizeof(struct stage_data)); + } + e = item->util; + e->stages[ce_stage(ce)].mode = ce->ce_mode; + hashcpy(e->stages[ce_stage(ce)].sha, ce->sha1); + } + + return unmerged; +} + +struct rename +{ + struct diff_filepair *pair; + struct stage_data *src_entry; + struct stage_data *dst_entry; + unsigned processed:1; +}; + +/* + * Get information of all renames which occurred between 'o_tree' and + * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and + * 'b_tree') to be able to associate the correct cache entries with + * the rename information. 'tree' is always equal to either a_tree or b_tree. + */ +static struct string_list *get_renames(struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries) +{ + int i; + struct string_list *renames; + struct diff_options opts; + + renames = xcalloc(1, sizeof(struct string_list)); + diff_setup(&opts); + DIFF_OPT_SET(&opts, RECURSIVE); + opts.detect_rename = DIFF_DETECT_RENAME; + opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : + diff_rename_limit >= 0 ? diff_rename_limit : + 500; + opts.warn_on_too_large_rename = 1; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + if (diff_setup_done(&opts) < 0) + die("diff setup failed"); + diff_tree_sha1(o_tree->object.sha1, tree->object.sha1, "", &opts); + diffcore_std(&opts); + for (i = 0; i < diff_queued_diff.nr; ++i) { + struct string_list_item *item; + struct rename *re; + struct diff_filepair *pair = diff_queued_diff.queue[i]; + if (pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->pair = pair; + item = string_list_lookup(re->pair->one->path, entries); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = string_list_lookup(re->pair->two->path, entries); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = string_list_insert(pair->one->path, renames); + item->util = re; + } + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_flush(&opts); + return renames; +} + +static int update_stages(const char *path, struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + int clear) +{ + int options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE; + if (clear) + if (remove_file_from_cache(path)) + return -1; + if (o) + if (add_cacheinfo(o->mode, o->sha1, path, 1, 0, options)) + return -1; + if (a) + if (add_cacheinfo(a->mode, a->sha1, path, 2, 0, options)) + return -1; + if (b) + if (add_cacheinfo(b->mode, b->sha1, path, 3, 0, options)) + return -1; + return 0; +} + +static int remove_path(const char *name) +{ + int ret; + char *slash, *dirs; + + ret = unlink(name); + if (ret) + return ret; + dirs = xstrdup(name); + while ((slash = strrchr(name, '/'))) { + *slash = '\0'; + if (rmdir(name) != 0) + break; + } + free(dirs); + return ret; +} + +static int remove_file(int clean, const char *path, int no_wd) +{ + int update_cache = index_only || clean; + int update_working_directory = !index_only && !no_wd; + + if (update_cache) { + if (remove_file_from_cache(path)) + return -1; + } + if (update_working_directory) { + unlink(path); + if (errno != ENOENT || errno != EISDIR) + return -1; + remove_path(path); + } + return 0; +} + +static char *unique_path(const char *path, const char *branch) +{ + char *newpath = xmalloc(strlen(path) + 1 + strlen(branch) + 8 + 1); + int suffix = 0; + struct stat st; + char *p = newpath + strlen(path); + strcpy(newpath, path); + *(p++) = '~'; + strcpy(p, branch); + for (; *p; ++p) + if ('/' == *p) + *p = '_'; + while (string_list_has_string(¤t_file_set, newpath) || + string_list_has_string(¤t_directory_set, newpath) || + lstat(newpath, &st) == 0) + sprintf(p, "_%d", suffix++); + + string_list_insert(newpath, ¤t_file_set); + return newpath; +} + +static void flush_buffer(int fd, const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = write_in_full(fd, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("merge-recursive: %s", strerror(errno)); + } else if (!ret) { + die("merge-recursive: disk full?"); + } + size -= ret; + buf += ret; + } +} + +static int make_room_for_path(const char *path) +{ + int status; + const char *msg = "failed to create path '%s'%s"; + + status = safe_create_leading_directories_const(path); + if (status) { + if (status == -3) { + /* something else exists */ + error(msg, path, ": perhaps a D/F conflict?"); + return -1; + } + die(msg, path, ""); + } + + /* Successful unlink is good.. */ + if (!unlink(path)) + return 0; + /* .. and so is no existing file */ + if (errno == ENOENT) + return 0; + /* .. but not some other error (who really cares what?) */ + return error(msg, path, ": perhaps a D/F conflict?"); +} + +static void update_file_flags(const unsigned char *sha, + unsigned mode, + const char *path, + int update_cache, + int update_wd) +{ + if (index_only) + update_wd = 0; + + if (update_wd) { + enum object_type type; + void *buf; + unsigned long size; + + if (S_ISGITLINK(mode)) + die("cannot read object %s '%s': It is a submodule!", + sha1_to_hex(sha), path); + + buf = read_sha1_file(sha, &type, &size); + if (!buf) + die("cannot read object %s '%s'", sha1_to_hex(sha), path); + if (type != OBJ_BLOB) + die("blob expected for %s '%s'", sha1_to_hex(sha), path); + if (S_ISREG(mode)) { + struct strbuf strbuf; + strbuf_init(&strbuf, 0); + if (convert_to_working_tree(path, buf, size, &strbuf)) { + free(buf); + size = strbuf.len; + buf = strbuf_detach(&strbuf, NULL); + } + } + + if (make_room_for_path(path) < 0) { + update_wd = 0; + free(buf); + goto update_index; + } + if (S_ISREG(mode) || (!has_symlinks && S_ISLNK(mode))) { + int fd; + if (mode & 0100) + mode = 0777; + else + mode = 0666; + fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, mode); + if (fd < 0) + die("failed to open %s: %s", path, strerror(errno)); + flush_buffer(fd, buf, size); + close(fd); + } else if (S_ISLNK(mode)) { + char *lnk = xmemdupz(buf, size); + safe_create_leading_directories_const(path); + unlink(path); + symlink(lnk, path); + free(lnk); + } else + die("do not know what to do with %06o %s '%s'", + mode, sha1_to_hex(sha), path); + free(buf); + } + update_index: + if (update_cache) + add_cacheinfo(mode, sha, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); +} + +static void update_file(int clean, + const unsigned char *sha, + unsigned mode, + const char *path) +{ + update_file_flags(sha, mode, path, index_only || clean, !index_only); +} + +/* Low level file merging, update and removal */ + +struct merge_file_info +{ + unsigned char sha[20]; + unsigned mode; + unsigned clean:1, + merge:1; +}; + +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) +{ + unsigned long size; + enum object_type type; + + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } + + mm->ptr = read_sha1_file(sha1, &type, &size); + if (!mm->ptr || type != OBJ_BLOB) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; +} + +static int merge_3way(mmbuffer_t *result_buf, + struct diff_filespec *o, + struct diff_filespec *a, + struct diff_filespec *b, + const char *branch1, + const char *branch2) +{ + mmfile_t orig, src1, src2; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s:%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s:%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + merge_status = ll_merge(result_buf, a->path, &orig, + &src1, name1, &src2, name2, + index_only); + + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + return merge_status; +} + +static struct merge_file_info merge_file(struct diff_filespec *o, + struct diff_filespec *a, struct diff_filespec *b, + const char *branch1, const char *branch2) +{ + struct merge_file_info result; + result.merge = 0; + result.clean = 1; + + if ((S_IFMT & a->mode) != (S_IFMT & b->mode)) { + result.clean = 0; + if (S_ISREG(a->mode)) { + result.mode = a->mode; + hashcpy(result.sha, a->sha1); + } else { + result.mode = b->mode; + hashcpy(result.sha, b->sha1); + } + } else { + if (!sha_eq(a->sha1, o->sha1) && !sha_eq(b->sha1, o->sha1)) + result.merge = 1; + + /* + * Merge modes + */ + if (a->mode == b->mode || a->mode == o->mode) + result.mode = b->mode; + else { + result.mode = a->mode; + if (b->mode != o->mode) { + result.clean = 0; + result.merge = 1; + } + } + + if (sha_eq(a->sha1, b->sha1) || sha_eq(a->sha1, o->sha1)) + hashcpy(result.sha, b->sha1); + else if (sha_eq(b->sha1, o->sha1)) + hashcpy(result.sha, a->sha1); + else if (S_ISREG(a->mode)) { + mmbuffer_t result_buf; + int merge_status; + + merge_status = merge_3way(&result_buf, o, a, b, + branch1, branch2); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); + } else if (S_ISGITLINK(a->mode)) { + result.clean = 0; + hashcpy(result.sha, a->sha1); + } else if (S_ISLNK(a->mode)) { + hashcpy(result.sha, a->sha1); + + if (!sha_eq(a->sha1, b->sha1)) + result.clean = 0; + } else { + die("unsupported object type in the tree"); + } + } + + return result; +} + +static void conflict_rename_rename(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *del[2]; + int delp = 0; + const char *ren1_dst = ren1->pair->two->path; + const char *ren2_dst = ren2->pair->two->path; + const char *dst_name1 = ren1_dst; + const char *dst_name2 = ren2_dst; + if (string_list_has_string(¤t_directory_set, ren1_dst)) { + dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); + output(1, "%s is a directory in %s adding as %s instead", + ren1_dst, branch2, dst_name1); + remove_file(0, ren1_dst, 0); + } + if (string_list_has_string(¤t_directory_set, ren2_dst)) { + dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); + output(1, "%s is a directory in %s adding as %s instead", + ren2_dst, branch1, dst_name2); + remove_file(0, ren2_dst, 0); + } + if (index_only) { + remove_file_from_cache(dst_name1); + remove_file_from_cache(dst_name2); + /* + * Uncomment to leave the conflicting names in the resulting tree + * + * update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1); + * update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2); + */ + } else { + update_stages(dst_name1, NULL, ren1->pair->two, NULL, 1); + update_stages(dst_name2, NULL, NULL, ren2->pair->two, 1); + } + while (delp--) + free(del[delp]); +} + +static void conflict_rename_dir(struct rename *ren1, + const char *branch1) +{ + char *new_path = unique_path(ren1->pair->two->path, branch1); + output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); + remove_file(0, ren1->pair->two->path, 0); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); + free(new_path); +} + +static void conflict_rename_rename_2(struct rename *ren1, + const char *branch1, + struct rename *ren2, + const char *branch2) +{ + char *new_path1 = unique_path(ren1->pair->two->path, branch1); + char *new_path2 = unique_path(ren2->pair->two->path, branch2); + output(1, "Renaming %s to %s and %s to %s instead", + ren1->pair->one->path, new_path1, + ren2->pair->one->path, new_path2); + remove_file(0, ren1->pair->two->path, 0); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1); + update_file(0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2); + free(new_path2); + free(new_path1); +} + +static int process_renames(struct string_list *a_renames, + struct string_list *b_renames, + const char *a_branch, + const char *b_branch) +{ + int clean_merge = 1, i, j; + struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + string_list_insert(sre->pair->two->path, &a_by_dst)->util + = sre->dst_entry; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + string_list_insert(sre->pair->two->path, &b_by_dst)->util + = sre->dst_entry; + } + + for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { + int compare; + char *src; + struct string_list *renames1, *renames2, *renames2Dst; + struct rename *ren1 = NULL, *ren2 = NULL; + const char *branch1, *branch2; + const char *ren1_src, *ren1_dst; + + if (i >= a_renames->nr) { + compare = 1; + ren2 = b_renames->items[j++].util; + } else if (j >= b_renames->nr) { + compare = -1; + ren1 = a_renames->items[i++].util; + } else { + compare = strcmp(a_renames->items[i].string, + b_renames->items[j].string); + if (compare <= 0) + ren1 = a_renames->items[i++].util; + if (compare >= 0) + ren2 = b_renames->items[j++].util; + } + + /* TODO: refactor, so that 1/2 are not needed */ + if (ren1) { + renames1 = a_renames; + renames2 = b_renames; + renames2Dst = &b_by_dst; + branch1 = a_branch; + branch2 = b_branch; + } else { + struct rename *tmp; + renames1 = b_renames; + renames2 = a_renames; + renames2Dst = &a_by_dst; + branch1 = b_branch; + branch2 = a_branch; + tmp = ren2; + ren2 = ren1; + ren1 = tmp; + } + src = ren1->pair->one->path; + + ren1->dst_entry->processed = 1; + ren1->src_entry->processed = 1; + + if (ren1->processed) + continue; + ren1->processed = 1; + + ren1_src = ren1->pair->one->path; + ren1_dst = ren1->pair->two->path; + + if (ren2) { + const char *ren2_src = ren2->pair->one->path; + const char *ren2_dst = ren2->pair->two->path; + /* Renamed in 1 and renamed in 2 */ + if (strcmp(ren1_src, ren2_src) != 0) + die("ren1.src != ren2.src"); + ren2->dst_entry->processed = 1; + ren2->processed = 1; + if (strcmp(ren1_dst, ren2_dst) != 0) { + clean_merge = 0; + output(1, "CONFLICT (rename/rename): " + "Rename \"%s\"->\"%s\" in branch \"%s\" " + "rename \"%s\"->\"%s\" in \"%s\"%s", + src, ren1_dst, branch1, + src, ren2_dst, branch2, + index_only ? " (left unresolved)": ""); + if (index_only) { + remove_file_from_cache(src); + update_file(0, ren1->pair->one->sha1, + ren1->pair->one->mode, src); + } + conflict_rename_rename(ren1, branch1, ren2, branch2); + } else { + struct merge_file_info mfi; + remove_file(1, ren1_src, 1); + mfi = merge_file(ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + branch1, + branch2); + if (mfi.merge || !mfi.clean) + output(1, "Renaming %s->%s", src, ren1_dst); + + if (mfi.merge) + output(2, "Auto-merging %s", ren1_dst); + + if (!mfi.clean) { + output(1, "CONFLICT (content): merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + ren1->pair->one, + ren1->pair->two, + ren2->pair->two, + 1 /* clear */); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } else { + /* Renamed in 1, maybe changed in 2 */ + struct string_list_item *item; + /* we only use sha1 and mode of these */ + struct diff_filespec src_other, dst_other; + int try_merge, stage = a_renames == renames1 ? 3: 2; + + remove_file(1, ren1_src, index_only || stage == 3); + + hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); + src_other.mode = ren1->src_entry->stages[stage].mode; + hashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha); + dst_other.mode = ren1->dst_entry->stages[stage].mode; + + try_merge = 0; + + if (string_list_has_string(¤t_directory_set, ren1_dst)) { + clean_merge = 0; + output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + " directory %s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + conflict_rename_dir(ren1, branch1); + } else if (sha_eq(src_other.sha1, null_sha1)) { + clean_merge = 0; + output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " + "and deleted in %s", + ren1_src, ren1_dst, branch1, + branch2); + update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); + } else if (!sha_eq(dst_other.sha1, null_sha1)) { + const char *new_path; + clean_merge = 0; + try_merge = 1; + output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + "%s added in %s", + ren1_src, ren1_dst, branch1, + ren1_dst, branch2); + new_path = unique_path(ren1_dst, branch2); + output(1, "Adding as %s instead", new_path); + update_file(0, dst_other.sha1, dst_other.mode, new_path); + } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { + ren2 = item->util; + clean_merge = 0; + ren2->processed = 1; + output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " + "Rename %s->%s in %s", + ren1_src, ren1_dst, branch1, + ren2->pair->one->path, ren2->pair->two->path, branch2); + conflict_rename_rename_2(ren1, branch1, ren2, branch2); + } else + try_merge = 1; + + if (try_merge) { + struct diff_filespec *o, *a, *b; + struct merge_file_info mfi; + src_other.path = (char *)ren1_src; + + o = ren1->pair->one; + if (a_renames == renames1) { + a = ren1->pair->two; + b = &src_other; + } else { + b = ren1->pair->two; + a = &src_other; + } + mfi = merge_file(o, a, b, + a_branch, b_branch); + + if (mfi.clean && + sha_eq(mfi.sha, ren1->pair->two->sha1) && + mfi.mode == ren1->pair->two->mode) + /* + * This messaged is part of + * t6022 test. If you change + * it update the test too. + */ + output(3, "Skipped %s (merged same as existing)", ren1_dst); + else { + if (mfi.merge || !mfi.clean) + output(1, "Renaming %s => %s", ren1_src, ren1_dst); + if (mfi.merge) + output(2, "Auto-merging %s", ren1_dst); + if (!mfi.clean) { + output(1, "CONFLICT (rename/modify): Merge conflict in %s", + ren1_dst); + clean_merge = 0; + + if (!index_only) + update_stages(ren1_dst, + o, a, b, 1); + } + update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); + } + } + } + } + string_list_clear(&a_by_dst, 0); + string_list_clear(&b_by_dst, 0); + + return clean_merge; +} + +static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) +{ + return (is_null_sha1(sha) || mode == 0) ? NULL: (unsigned char *)sha; +} + +/* Per entry merge function */ +static int process_entry(const char *path, struct stage_data *entry, + const char *branch1, + const char *branch2) +{ + /* + printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); + print_index_entry("\tpath: ", entry); + */ + int clean_merge = 1; + unsigned o_mode = entry->stages[1].mode; + unsigned a_mode = entry->stages[2].mode; + unsigned b_mode = entry->stages[3].mode; + unsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode); + unsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode); + unsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode); + + if (o_sha && (!a_sha || !b_sha)) { + /* Case A: Deleted in one */ + if ((!a_sha && !b_sha) || + (sha_eq(a_sha, o_sha) && !b_sha) || + (!a_sha && sha_eq(b_sha, o_sha))) { + /* Deleted in both or deleted in one and + * unchanged in the other */ + if (a_sha) + output(2, "Removing %s", path); + /* do not touch working file if it did not exist */ + remove_file(1, path, !a_sha); + } else { + /* Deleted in one and changed in the other */ + clean_merge = 0; + if (!a_sha) { + output(1, "CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch1, + branch2, branch2, path); + update_file(0, b_sha, b_mode, path); + } else { + output(1, "CONFLICT (delete/modify): %s deleted in %s " + "and modified in %s. Version %s of %s left in tree.", + path, branch2, + branch1, branch1, path); + update_file(0, a_sha, a_mode, path); + } + } + + } else if ((!o_sha && a_sha && !b_sha) || + (!o_sha && !a_sha && b_sha)) { + /* Case B: Added in one. */ + const char *add_branch; + const char *other_branch; + unsigned mode; + const unsigned char *sha; + const char *conf; + + if (a_sha) { + add_branch = branch1; + other_branch = branch2; + mode = a_mode; + sha = a_sha; + conf = "file/directory"; + } else { + add_branch = branch2; + other_branch = branch1; + mode = b_mode; + sha = b_sha; + conf = "directory/file"; + } + if (string_list_has_string(¤t_directory_set, path)) { + const char *new_path = unique_path(path, add_branch); + clean_merge = 0; + output(1, "CONFLICT (%s): There is a directory with name %s in %s. " + "Adding %s as %s", + conf, path, other_branch, path, new_path); + remove_file(0, path, 0); + update_file(0, sha, mode, new_path); + } else { + output(2, "Adding %s", path); + update_file(1, sha, mode, path); + } + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ + /* case D: Modified in both, but differently. */ + const char *reason = "content"; + struct merge_file_info mfi; + struct diff_filespec o, a, b; + + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } + output(2, "Auto-merging %s", path); + o.path = a.path = b.path = (char *)path; + hashcpy(o.sha1, o_sha); + o.mode = o_mode; + hashcpy(a.sha1, a_sha); + a.mode = a_mode; + hashcpy(b.sha1, b_sha); + b.mode = b_mode; + + mfi = merge_file(&o, &a, &b, + branch1, branch2); + + clean_merge = mfi.clean; + if (mfi.clean) + update_file(1, mfi.sha, mfi.mode, path); + else if (S_ISGITLINK(mfi.mode)) + output(1, "CONFLICT (submodule): Merge conflict in %s " + "- needs %s", path, sha1_to_hex(b.sha1)); + else { + output(1, "CONFLICT (%s): Merge conflict in %s", + reason, path); + + if (index_only) + update_file(0, mfi.sha, mfi.mode, path); + else + update_file_flags(mfi.sha, mfi.mode, path, + 0 /* update_cache */, 1 /* update_working_directory */); + } + } else if (!o_sha && !a_sha && !b_sha) { + /* + * this entry was deleted altogether. a_mode == 0 means + * we had that path and want to actively remove it. + */ + remove_file(1, path, !a_mode); + } else + die("Fatal merge failure, shouldn't happen."); + + return clean_merge; +} + +int merge_trees(struct tree *head, + struct tree *merge, + struct tree *common, + const char *branch1, + const char *branch2, + struct tree **result) +{ + int code, clean; + + if (subtree_merge) { + merge = shift_tree_object(head, merge); + common = shift_tree_object(head, common); + } + + if (sha_eq(common->object.sha1, merge->object.sha1)) { + output(0, "Already uptodate!"); + *result = head; + return 1; + } + + code = git_merge_trees(index_only, common, head, merge); + + if (code != 0) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + + if (unmerged_cache()) { + struct string_list *entries, *re_head, *re_merge; + int i; + string_list_clear(¤t_file_set, 1); + string_list_clear(¤t_directory_set, 1); + get_files_dirs(head); + get_files_dirs(merge); + + entries = get_unmerged(); + re_head = get_renames(head, common, head, merge, entries); + re_merge = get_renames(merge, common, head, merge, entries); + clean = process_renames(re_head, re_merge, + branch1, branch2); + for (i = 0; i < entries->nr; i++) { + const char *path = entries->items[i].string; + struct stage_data *e = entries->items[i].util; + if (!e->processed + && !process_entry(path, e, branch1, branch2)) + clean = 0; + } + + string_list_clear(re_merge, 0); + string_list_clear(re_head, 0); + string_list_clear(entries, 1); + + } + else + clean = 1; + + if (index_only) + *result = write_tree_from_memory(); + + return clean; +} + +static struct commit_list *reverse_commit_list(struct commit_list *list) +{ + struct commit_list *next = NULL, *current, *backup; + for (current = list; current; current = backup) { + backup = current->next; + current->next = next; + next = current; + } + return next; +} + +/* + * Merge the commits h1 and h2, return the resulting virtual + * commit object and a flag indicating the cleanness of the merge. + */ +int merge_recursive(struct commit *h1, + struct commit *h2, + const char *branch1, + const char *branch2, + struct commit_list *ca, + struct commit **result) +{ + struct commit_list *iter; + struct commit *merged_common_ancestors; + struct tree *mrtree = mrtree; + int clean; + + if (show(4)) { + output(4, "Merging:"); + output_commit_title(h1); + output_commit_title(h2); + } + + if (!ca) { + ca = get_merge_bases(h1, h2, 1); + ca = reverse_commit_list(ca); + } + + if (show(5)) { + output(5, "found %u common ancestor(s):", commit_list_count(ca)); + for (iter = ca; iter; iter = iter->next) + output_commit_title(iter->item); + } + + merged_common_ancestors = pop_commit(&ca); + if (merged_common_ancestors == NULL) { + /* if there is no common ancestor, make an empty tree */ + struct tree *tree = xcalloc(1, sizeof(struct tree)); + + tree->object.parsed = 1; + tree->object.type = OBJ_TREE; + pretend_sha1_file(NULL, 0, OBJ_TREE, tree->object.sha1); + merged_common_ancestors = make_virtual_commit(tree, "ancestor"); + } + + for (iter = ca; iter; iter = iter->next) { + call_depth++; + /* + * When the merge fails, the result contains files + * with conflict markers. The cleanness flag is + * ignored, it was never actually used, as result of + * merge_trees has always overwritten it: the committed + * "conflicts" were already resolved. + */ + discard_cache(); + merge_recursive(merged_common_ancestors, iter->item, + "Temporary merge branch 1", + "Temporary merge branch 2", + NULL, + &merged_common_ancestors); + call_depth--; + + if (!merged_common_ancestors) + die("merge returned no commit"); + } + + discard_cache(); + if (!call_depth) { + read_cache(); + index_only = 0; + } else + index_only = 1; + + clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, + branch1, branch2, &mrtree); + + if (index_only) { + *result = make_virtual_commit(mrtree, "merged tree"); + commit_list_insert(h1, &(*result)->parents); + commit_list_insert(h2, &(*result)->parents->next); + } + flush_output(); + return clean; +} + +int merge_recursive_config(const char *var, const char *value, void *cb) +{ + if (!strcasecmp(var, "merge.verbosity")) { + merge_recursive_verbosity = git_config_int(var, value); + return 0; + } + if (!strcasecmp(var, "diff.renamelimit")) { + diff_rename_limit = git_config_int(var, value); + return 0; + } + if (!strcasecmp(var, "merge.renamelimit")) { + merge_rename_limit = git_config_int(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +void merge_recursive_setup(int is_subtree_merge) +{ + if (getenv("GIT_MERGE_VERBOSITY")) + merge_recursive_verbosity = + strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); + if (merge_recursive_verbosity >= 5) + buffer_output = 0; + subtree_merge = is_subtree_merge; +} diff --git a/merge-recursive.h b/merge-recursive.h index f37630a8ad..73e4413a80 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -14,7 +14,11 @@ int merge_trees(struct tree *head, const char *branch1, const char *branch2, struct tree **result); - +struct commit *make_virtual_commit(struct tree *tree, const char *comment); +int merge_recursive_config(const char *var, const char *value, void *cb); +void merge_recursive_setup(int is_subtree_merge); struct tree *write_tree_from_memory(void); +extern int merge_recursive_verbosity; + #endif -- cgit v1.2.1 From 73118f89b81f5a3ed1bb56e2517627d56e9ebdfb Mon Sep 17 00:00:00 2001 From: Stephan Beyer Date: Tue, 12 Aug 2008 22:13:59 +0200 Subject: merge-recursive.c: Add more generic merge_recursive_generic() merge_recursive_generic() takes, in comparison to to merge_recursive(), no commit ("struct commit *") arguments but SHA ids ("unsigned char *"), and no commit list of bases but an array of refs ("const char **"). This makes it more generic in the case that it can also take the SHA of a tree to merge trees without commits, for the bases, the head and the remote. merge_recursive_generic() also handles locking and updating of the index, which is a common use case of merge_recursive(). This patch also rewrites builtin-merge-recursive.c to make use of merge_recursive_generic(). By doing this, I stumbled over the limitation of 20 bases and I've added a warning if this limitation is exceeded. This patch qualifies make_virtual_commit() as static again because this function is not needed anymore outside merge-recursive.c. Signed-off-by: Stephan Beyer Signed-off-by: Junio C Hamano --- builtin-merge-recursive.c | 64 ++++++++++++++--------------------------------- merge-recursive.c | 53 +++++++++++++++++++++++++++++++++++++++ merge-recursive.h | 4 ++- 3 files changed, 75 insertions(+), 46 deletions(-) diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 8bf2fa5df3..25f540b4a8 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -15,36 +15,13 @@ static const char *better_branch_name(const char *branch) return name ? name : branch; } -static struct commit *get_ref(const char *ref) -{ - unsigned char sha1[20]; - struct object *object; - - if (get_sha1(ref, sha1)) - die("Could not resolve ref '%s'", ref); - object = deref_tag(parse_object(sha1), ref, strlen(ref)); - if (!object) - return NULL; - if (object->type == OBJ_TREE) - return make_virtual_commit((struct tree*)object, - better_branch_name(ref)); - if (object->type != OBJ_COMMIT) - return NULL; - if (parse_commit((struct commit *)object)) - die("Could not parse commit '%s'", sha1_to_hex(object->sha1)); - return (struct commit *)object; -} - int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - static const char *bases[20]; - static unsigned bases_count = 0; - int i, clean; + const char *bases[21]; + unsigned bases_count = 0; + int i, failed; const char *branch1, *branch2; - struct commit *result, *h1, *h2; - struct commit_list *ca = NULL; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd; + unsigned char h1[20], h2[20]; int subtree_merge = 0; if (argv[0]) { @@ -60,10 +37,15 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) die("Usage: %s ... -- ...\n", argv[0]); for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--")) + if (!strcmp(argv[i], "--")) { + bases[bases_count] = NULL; break; - if (bases_count < sizeof(bases)/sizeof(*bases)) + } + if (bases_count < ARRAY_SIZE(bases)-1) bases[bases_count++] = argv[i]; + else + warning("Cannot handle more than %zu bases. " + "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); @@ -71,8 +53,10 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) branch1 = argv[++i]; branch2 = argv[++i]; - h1 = get_ref(branch1); - h2 = get_ref(branch2); + if (get_sha1(branch1, h1)) + die("Could not resolve ref '%s'", branch1); + if (get_sha1(branch2, h2)) + die("Could not resolve ref '%s'", branch2); branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); @@ -80,18 +64,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (merge_recursive_verbosity >= 3) printf("Merging %s with %s\n", branch1, branch2); - index_fd = hold_locked_index(lock, 1); - - for (i = 0; i < bases_count; i++) { - struct commit *ancestor = get_ref(bases[i]); - ca = commit_list_insert(ancestor, &ca); - } - clean = merge_recursive(h1, h2, branch1, branch2, ca, &result); - - if (active_cache_changed && - (write_cache(index_fd, active_cache, active_nr) || - commit_locked_index(lock))) - die ("unable to write %s", get_index_file()); - - return clean ? 0: 1; + failed = merge_recursive_generic(bases, h1, branch1, h2, branch2); + if (failed < 0) + return 128; /* die() error code */ + return failed; } diff --git a/merge-recursive.c b/merge-recursive.c index a0cb3f47de..60d11716b3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1303,6 +1303,59 @@ int merge_recursive(struct commit *h1, return clean; } +static struct commit *get_ref(const unsigned char *sha1, const char *name) +{ + struct object *object; + + object = deref_tag(parse_object(sha1), name, strlen(name)); + if (!object) + return NULL; + if (object->type == OBJ_TREE) + return make_virtual_commit((struct tree*)object, name); + if (object->type != OBJ_COMMIT) + return NULL; + if (parse_commit((struct commit *)object)) + return NULL; + return (struct commit *)object; +} + +int merge_recursive_generic(const char **base_list, + const unsigned char *head_sha1, const char *head_name, + const unsigned char *next_sha1, const char *next_name) +{ + int clean, index_fd; + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + struct commit *result; + struct commit *head_commit = get_ref(head_sha1, head_name); + struct commit *next_commit = get_ref(next_sha1, next_name); + struct commit_list *ca = NULL; + + if (base_list) { + int i; + for (i = 0; base_list[i]; ++i) { + unsigned char sha[20]; + struct commit *base; + if (get_sha1(base_list[i], sha)) + return error("Could not resolve ref '%s'", + base_list[i]); + if (!(base = get_ref(sha, base_list[i]))) + return error("Could not parse object '%s'", + base_list[i]); + commit_list_insert(base, &ca); + } + } + + index_fd = hold_locked_index(lock, 1); + clean = merge_recursive(head_commit, next_commit, + head_name, next_name, ca, &result); + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(lock))) + return error("Unable to write index."); + + return clean ? 0 : 1; +} + int merge_recursive_config(const char *var, const char *value, void *cb) { if (!strcasecmp(var, "merge.verbosity")) { diff --git a/merge-recursive.h b/merge-recursive.h index 73e4413a80..4dd6476af6 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -14,7 +14,9 @@ int merge_trees(struct tree *head, const char *branch1, const char *branch2, struct tree **result); -struct commit *make_virtual_commit(struct tree *tree, const char *comment); +extern int merge_recursive_generic(const char **base_list, + const unsigned char *head_sha1, const char *head_name, + const unsigned char *next_sha1, const char *next_name); int merge_recursive_config(const char *var, const char *value, void *cb); void merge_recursive_setup(int is_subtree_merge); struct tree *write_tree_from_memory(void); -- cgit v1.2.1 From 8a2fce1895c058945d8e2dbd8cb7456cc7450ad8 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Mon, 25 Aug 2008 16:25:57 +0200 Subject: merge-recursive: introduce merge_options This makes it possible to avoid passing the labels of branches as arguments to merge_recursive(), merge_trees() and merge_recursive_generic(). It also takes care of subtree merge, output buffering, verbosity, and rename limits - these were global variables till now in merge-recursive.c. A new function, named init_merge_options(), is introduced as well, it clears the struct merge_info, then initializes with default values, finally updates the default values based on the config and environment variables. Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-checkout.c | 11 +- builtin-merge-recursive.c | 43 ++++---- merge-recursive.c | 265 +++++++++++++++++++++++----------------------- merge-recursive.h | 42 +++++--- 4 files changed, 192 insertions(+), 169 deletions(-) diff --git a/builtin-checkout.c b/builtin-checkout.c index b380ad6e80..f3c6b0fad2 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -264,6 +264,7 @@ static int merge_working_tree(struct checkout_opts *opts, */ struct tree *result; struct tree *work; + struct merge_options o; if (!opts->merge) return 1; parse_commit(old->commit); @@ -282,13 +283,17 @@ static int merge_working_tree(struct checkout_opts *opts, */ add_files_to_cache(NULL, NULL, 0); - work = write_tree_from_memory(); + init_merge_options(&o); + o.verbosity = 0; + work = write_tree_from_memory(&o); ret = reset_tree(new->commit->tree, opts, 1); if (ret) return ret; - merge_trees(new->commit->tree, work, old->commit->tree, - new->name, "local", &result); + o.branch1 = new->name; + o.branch2 = "local"; + merge_trees(&o, new->commit->tree, work, + old->commit->tree, &result); ret = reset_tree(new->commit->tree, opts, 0); if (ret) return ret; diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 25f540b4a8..6b534c1a66 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -17,32 +17,33 @@ static const char *better_branch_name(const char *branch) int cmd_merge_recursive(int argc, const char **argv, const char *prefix) { - const char *bases[21]; + const unsigned char *bases[21]; unsigned bases_count = 0; int i, failed; - const char *branch1, *branch2; unsigned char h1[20], h2[20]; - int subtree_merge = 0; + struct merge_options o; + struct commit *result; + init_merge_options(&o); if (argv[0]) { int namelen = strlen(argv[0]); if (8 < namelen && !strcmp(argv[0] + namelen - 8, "-subtree")) - subtree_merge = 1; + o.subtree_merge = 1; } - git_config(merge_recursive_config, NULL); - merge_recursive_setup(subtree_merge); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); for (i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--")) { - bases[bases_count] = NULL; + if (!strcmp(argv[i], "--")) break; + if (bases_count < ARRAY_SIZE(bases)-1) { + unsigned char *sha = xmalloc(20); + if (get_sha1(argv[i], sha)) + die("Could not parse object '%s'", argv[i]); + bases[bases_count++] = sha; } - if (bases_count < ARRAY_SIZE(bases)-1) - bases[bases_count++] = argv[i]; else warning("Cannot handle more than %zu bases. " "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]); @@ -50,21 +51,21 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); - branch1 = argv[++i]; - branch2 = argv[++i]; + o.branch1 = argv[++i]; + o.branch2 = argv[++i]; - if (get_sha1(branch1, h1)) - die("Could not resolve ref '%s'", branch1); - if (get_sha1(branch2, h2)) - die("Could not resolve ref '%s'", branch2); + if (get_sha1(o.branch1, h1)) + die("Could not resolve ref '%s'", o.branch1); + if (get_sha1(o.branch2, h2)) + die("Could not resolve ref '%s'", o.branch2); - branch1 = better_branch_name(branch1); - branch2 = better_branch_name(branch2); + o.branch1 = better_branch_name(o.branch1); + o.branch2 = better_branch_name(o.branch2); - if (merge_recursive_verbosity >= 3) - printf("Merging %s with %s\n", branch1, branch2); + if (o.verbosity >= 3) + printf("Merging %s with %s\n", o.branch1, o.branch2); - failed = merge_recursive_generic(bases, h1, branch1, h2, branch2); + failed = merge_recursive_generic(&o, h1, h2, bases_count, bases, &result); if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/merge-recursive.c b/merge-recursive.c index 60d11716b3..457ad845f5 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -20,8 +20,6 @@ #include "attr.h" #include "merge-recursive.h" -static int subtree_merge; - static struct tree *shift_tree_object(struct tree *one, struct tree *two) { unsigned char shifted[20]; @@ -83,16 +81,11 @@ static struct string_list current_file_set = {NULL, 0, 0, 1}; static struct string_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; -int merge_recursive_verbosity = 2; -static int diff_rename_limit = -1; -static int merge_rename_limit = -1; -static int buffer_output = 1; static struct strbuf obuf = STRBUF_INIT; -static int show(int v) +static int show(struct merge_options *o, int v) { - return (!call_depth && merge_recursive_verbosity >= v) || - merge_recursive_verbosity >= 5; + return (!call_depth && o->verbosity >= v) || o->verbosity >= 5; } static void flush_output(void) @@ -103,12 +96,12 @@ static void flush_output(void) } } -static void output(int v, const char *fmt, ...) +static void output(struct merge_options *o, int v, const char *fmt, ...) { int len; va_list ap; - if (!show(v)) + if (!show(o, v)) return; strbuf_grow(&obuf, call_depth * 2 + 2); @@ -132,7 +125,7 @@ static void output(int v, const char *fmt, ...) } strbuf_setlen(&obuf, obuf.len + len); strbuf_add(&obuf, "\n", 1); - if (!buffer_output) + if (!o->buffer_output) flush_output(); } @@ -219,17 +212,17 @@ static int git_merge_trees(int index_only, return rc; } -struct tree *write_tree_from_memory(void) +struct tree *write_tree_from_memory(struct merge_options *o) { struct tree *result = NULL; if (unmerged_cache()) { int i; - output(0, "There are unmerged index entries:"); + output(o, 0, "There are unmerged index entries:"); for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce)) - output(0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); + output(o, 0, "%d %.*s", ce_stage(ce), ce_namelen(ce), ce->name); } return NULL; } @@ -341,11 +334,12 @@ struct rename * 'b_tree') to be able to associate the correct cache entries with * the rename information. 'tree' is always equal to either a_tree or b_tree. */ -static struct string_list *get_renames(struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) +static struct string_list *get_renames(struct merge_options *o, + struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries) { int i; struct string_list *renames; @@ -355,8 +349,8 @@ static struct string_list *get_renames(struct tree *tree, diff_setup(&opts); DIFF_OPT_SET(&opts, RECURSIVE); opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = merge_rename_limit >= 0 ? merge_rename_limit : - diff_rename_limit >= 0 ? diff_rename_limit : + opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : + o->diff_rename_limit >= 0 ? o->diff_rename_limit : 500; opts.warn_on_too_large_rename = 1; opts.output_format = DIFF_FORMAT_NO_OUTPUT; @@ -717,7 +711,8 @@ static struct merge_file_info merge_file(struct diff_filespec *o, return result; } -static void conflict_rename_rename(struct rename *ren1, +static void conflict_rename_rename(struct merge_options *o, + struct rename *ren1, const char *branch1, struct rename *ren2, const char *branch2) @@ -730,13 +725,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (string_list_has_string(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output(1, "%s is a directory in %s adding as %s instead", + output(o, 1, "%s is a directory in %s adding as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (string_list_has_string(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output(1, "%s is a directory in %s adding as %s instead", + output(o, 1, "%s is a directory in %s adding as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -757,24 +752,26 @@ static void conflict_rename_rename(struct rename *ren1, free(del[delp]); } -static void conflict_rename_dir(struct rename *ren1, +static void conflict_rename_dir(struct merge_options *o, + struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output(1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); + output(o, 1, "Renaming %s to %s instead", ren1->pair->one->path, new_path); remove_file(0, ren1->pair->two->path, 0); update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path); free(new_path); } -static void conflict_rename_rename_2(struct rename *ren1, +static void conflict_rename_rename_2(struct merge_options *o, + struct rename *ren1, const char *branch1, struct rename *ren2, const char *branch2) { char *new_path1 = unique_path(ren1->pair->two->path, branch1); char *new_path2 = unique_path(ren2->pair->two->path, branch2); - output(1, "Renaming %s to %s and %s to %s instead", + output(o, 1, "Renaming %s to %s and %s to %s instead", ren1->pair->one->path, new_path1, ren2->pair->one->path, new_path2); remove_file(0, ren1->pair->two->path, 0); @@ -784,10 +781,9 @@ static void conflict_rename_rename_2(struct rename *ren1, free(new_path1); } -static int process_renames(struct string_list *a_renames, - struct string_list *b_renames, - const char *a_branch, - const char *b_branch) +static int process_renames(struct merge_options *o, + struct string_list *a_renames, + struct string_list *b_renames) { int clean_merge = 1, i, j; struct string_list a_by_dst = {NULL, 0, 0, 0}, b_by_dst = {NULL, 0, 0, 0}; @@ -832,15 +828,15 @@ static int process_renames(struct string_list *a_renames, renames1 = a_renames; renames2 = b_renames; renames2Dst = &b_by_dst; - branch1 = a_branch; - branch2 = b_branch; + branch1 = o->branch1; + branch2 = o->branch2; } else { struct rename *tmp; renames1 = b_renames; renames2 = a_renames; renames2Dst = &a_by_dst; - branch1 = b_branch; - branch2 = a_branch; + branch1 = o->branch2; + branch2 = o->branch1; tmp = ren2; ren2 = ren1; ren1 = tmp; @@ -867,7 +863,7 @@ static int process_renames(struct string_list *a_renames, ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { clean_merge = 0; - output(1, "CONFLICT (rename/rename): " + output(o, 1, "CONFLICT (rename/rename): " "Rename \"%s\"->\"%s\" in branch \"%s\" " "rename \"%s\"->\"%s\" in \"%s\"%s", src, ren1_dst, branch1, @@ -878,7 +874,7 @@ static int process_renames(struct string_list *a_renames, update_file(0, ren1->pair->one->sha1, ren1->pair->one->mode, src); } - conflict_rename_rename(ren1, branch1, ren2, branch2); + conflict_rename_rename(o, ren1, branch1, ren2, branch2); } else { struct merge_file_info mfi; remove_file(1, ren1_src, 1); @@ -888,13 +884,13 @@ static int process_renames(struct string_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); + output(o, 1, "Renaming %s->%s", src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(o, 2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output(1, "CONFLICT (content): merge conflict in %s", + output(o, 1, "CONFLICT (content): merge conflict in %s", ren1_dst); clean_merge = 0; @@ -925,14 +921,14 @@ static int process_renames(struct string_list *a_renames, if (string_list_has_string(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + output(o, 1, "CONFLICT (rename/directory): Rename %s->%s in %s " " directory %s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); - conflict_rename_dir(ren1, branch1); + conflict_rename_dir(o, ren1, branch1); } else if (sha_eq(src_other.sha1, null_sha1)) { clean_merge = 0; - output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " + output(o, 1, "CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -941,31 +937,32 @@ static int process_renames(struct string_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + output(o, 1, "CONFLICT (rename/add): Rename %s->%s in %s. " "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); new_path = unique_path(ren1_dst, branch2); - output(1, "Adding as %s instead", new_path); + output(o, 1, "Adding as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output(1, "CONFLICT (rename/rename): Rename %s->%s in %s. " + output(o, 1, "CONFLICT (rename/rename): " + "Rename %s->%s in %s. " "Rename %s->%s in %s", ren1_src, ren1_dst, branch1, ren2->pair->one->path, ren2->pair->two->path, branch2); - conflict_rename_rename_2(ren1, branch1, ren2, branch2); + conflict_rename_rename_2(o, ren1, branch1, ren2, branch2); } else try_merge = 1; if (try_merge) { - struct diff_filespec *o, *a, *b; + struct diff_filespec *one, *a, *b; struct merge_file_info mfi; src_other.path = (char *)ren1_src; - o = ren1->pair->one; + one = ren1->pair->one; if (a_renames == renames1) { a = ren1->pair->two; b = &src_other; @@ -973,8 +970,8 @@ static int process_renames(struct string_list *a_renames, b = ren1->pair->two; a = &src_other; } - mfi = merge_file(o, a, b, - a_branch, b_branch); + mfi = merge_file(one, a, b, + o->branch1, o->branch2); if (mfi.clean && sha_eq(mfi.sha, ren1->pair->two->sha1) && @@ -984,20 +981,20 @@ static int process_renames(struct string_list *a_renames, * t6022 test. If you change * it update the test too. */ - output(3, "Skipped %s (merged same as existing)", ren1_dst); + output(o, 3, "Skipped %s (merged same as existing)", ren1_dst); else { if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); + output(o, 1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(o, 2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output(1, "CONFLICT (rename/modify): Merge conflict in %s", + output(o, 1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); clean_merge = 0; if (!index_only) update_stages(ren1_dst, - o, a, b, 1); + one, a, b, 1); } update_file(mfi.clean, mfi.sha, mfi.mode, ren1_dst); } @@ -1016,9 +1013,8 @@ static unsigned char *stage_sha(const unsigned char *sha, unsigned mode) } /* Per entry merge function */ -static int process_entry(const char *path, struct stage_data *entry, - const char *branch1, - const char *branch2) +static int process_entry(struct merge_options *o, + const char *path, struct stage_data *entry) { /* printf("processing entry, clean cache: %s\n", index_only ? "yes": "no"); @@ -1040,23 +1036,23 @@ static int process_entry(const char *path, struct stage_data *entry, /* Deleted in both or deleted in one and * unchanged in the other */ if (a_sha) - output(2, "Removing %s", path); + output(o, 2, "Removing %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { /* Deleted in one and changed in the other */ clean_merge = 0; if (!a_sha) { - output(1, "CONFLICT (delete/modify): %s deleted in %s " + output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch1, - branch2, branch2, path); + path, o->branch1, + o->branch2, o->branch2, path); update_file(0, b_sha, b_mode, path); } else { - output(1, "CONFLICT (delete/modify): %s deleted in %s " + output(o, 1, "CONFLICT (delete/modify): %s deleted in %s " "and modified in %s. Version %s of %s left in tree.", - path, branch2, - branch1, branch1, path); + path, o->branch2, + o->branch1, o->branch1, path); update_file(0, a_sha, a_mode, path); } } @@ -1071,14 +1067,14 @@ static int process_entry(const char *path, struct stage_data *entry, const char *conf; if (a_sha) { - add_branch = branch1; - other_branch = branch2; + add_branch = o->branch1; + other_branch = o->branch2; mode = a_mode; sha = a_sha; conf = "file/directory"; } else { - add_branch = branch2; - other_branch = branch1; + add_branch = o->branch2; + other_branch = o->branch1; mode = b_mode; sha = b_sha; conf = "directory/file"; @@ -1086,13 +1082,13 @@ static int process_entry(const char *path, struct stage_data *entry, if (string_list_has_string(¤t_directory_set, path)) { const char *new_path = unique_path(path, add_branch); clean_merge = 0; - output(1, "CONFLICT (%s): There is a directory with name %s in %s. " + output(o, 1, "CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s", conf, path, other_branch, path, new_path); remove_file(0, path, 0); update_file(0, sha, mode, new_path); } else { - output(2, "Adding %s", path); + output(o, 2, "Adding %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -1100,32 +1096,32 @@ static int process_entry(const char *path, struct stage_data *entry, /* case D: Modified in both, but differently. */ const char *reason = "content"; struct merge_file_info mfi; - struct diff_filespec o, a, b; + struct diff_filespec one, a, b; if (!o_sha) { reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output(2, "Auto-merging %s", path); - o.path = a.path = b.path = (char *)path; - hashcpy(o.sha1, o_sha); - o.mode = o_mode; + output(o, 2, "Auto-merging %s", path); + one.path = a.path = b.path = (char *)path; + hashcpy(one.sha1, o_sha); + one.mode = o_mode; hashcpy(a.sha1, a_sha); a.mode = a_mode; hashcpy(b.sha1, b_sha); b.mode = b_mode; - mfi = merge_file(&o, &a, &b, - branch1, branch2); + mfi = merge_file(&one, &a, &b, + o->branch1, o->branch2); clean_merge = mfi.clean; if (mfi.clean) update_file(1, mfi.sha, mfi.mode, path); else if (S_ISGITLINK(mfi.mode)) - output(1, "CONFLICT (submodule): Merge conflict in %s " + output(o, 1, "CONFLICT (submodule): Merge conflict in %s " "- needs %s", path, sha1_to_hex(b.sha1)); else { - output(1, "CONFLICT (%s): Merge conflict in %s", + output(o, 1, "CONFLICT (%s): Merge conflict in %s", reason, path); if (index_only) @@ -1146,22 +1142,21 @@ static int process_entry(const char *path, struct stage_data *entry, return clean_merge; } -int merge_trees(struct tree *head, +int merge_trees(struct merge_options *o, + struct tree *head, struct tree *merge, struct tree *common, - const char *branch1, - const char *branch2, struct tree **result) { int code, clean; - if (subtree_merge) { + if (o->subtree_merge) { merge = shift_tree_object(head, merge); common = shift_tree_object(head, common); } if (sha_eq(common->object.sha1, merge->object.sha1)) { - output(0, "Already uptodate!"); + output(o, 0, "Already uptodate!"); *result = head; return 1; } @@ -1182,15 +1177,14 @@ int merge_trees(struct tree *head, get_files_dirs(merge); entries = get_unmerged(); - re_head = get_renames(head, common, head, merge, entries); - re_merge = get_renames(merge, common, head, merge, entries); - clean = process_renames(re_head, re_merge, - branch1, branch2); + re_head = get_renames(o, head, common, head, merge, entries); + re_merge = get_renames(o, merge, common, head, merge, entries); + clean = process_renames(o, re_head, re_merge); for (i = 0; i < entries->nr; i++) { const char *path = entries->items[i].string; struct stage_data *e = entries->items[i].util; if (!e->processed - && !process_entry(path, e, branch1, branch2)) + && !process_entry(o, path, e)) clean = 0; } @@ -1203,7 +1197,7 @@ int merge_trees(struct tree *head, clean = 1; if (index_only) - *result = write_tree_from_memory(); + *result = write_tree_from_memory(o); return clean; } @@ -1223,10 +1217,9 @@ static struct commit_list *reverse_commit_list(struct commit_list *list) * Merge the commits h1 and h2, return the resulting virtual * commit object and a flag indicating the cleanness of the merge. */ -int merge_recursive(struct commit *h1, +int merge_recursive(struct merge_options *o, + struct commit *h1, struct commit *h2, - const char *branch1, - const char *branch2, struct commit_list *ca, struct commit **result) { @@ -1235,8 +1228,8 @@ int merge_recursive(struct commit *h1, struct tree *mrtree = mrtree; int clean; - if (show(4)) { - output(4, "Merging:"); + if (show(o, 4)) { + output(o, 4, "Merging:"); output_commit_title(h1); output_commit_title(h2); } @@ -1246,8 +1239,8 @@ int merge_recursive(struct commit *h1, ca = reverse_commit_list(ca); } - if (show(5)) { - output(5, "found %u common ancestor(s):", commit_list_count(ca)); + if (show(o, 5)) { + output(o, 5, "found %u common ancestor(s):", commit_list_count(ca)); for (iter = ca; iter; iter = iter->next) output_commit_title(iter->item); } @@ -1264,6 +1257,7 @@ int merge_recursive(struct commit *h1, } for (iter = ca; iter; iter = iter->next) { + const char *saved_b1, *saved_b2; call_depth++; /* * When the merge fails, the result contains files @@ -1273,11 +1267,14 @@ int merge_recursive(struct commit *h1, * "conflicts" were already resolved. */ discard_cache(); - merge_recursive(merged_common_ancestors, iter->item, - "Temporary merge branch 1", - "Temporary merge branch 2", - NULL, - &merged_common_ancestors); + saved_b1 = o->branch1; + saved_b2 = o->branch2; + o->branch1 = "Temporary merge branch 1"; + o->branch2 = "Temporary merge branch 2"; + merge_recursive(o, merged_common_ancestors, iter->item, + NULL, &merged_common_ancestors); + o->branch1 = saved_b1; + o->branch2 = saved_b2; call_depth--; if (!merged_common_ancestors) @@ -1291,8 +1288,8 @@ int merge_recursive(struct commit *h1, } else index_only = 1; - clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree, - branch1, branch2, &mrtree); + clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, + &mrtree); if (index_only) { *result = make_virtual_commit(mrtree, "merged tree"); @@ -1319,35 +1316,33 @@ static struct commit *get_ref(const unsigned char *sha1, const char *name) return (struct commit *)object; } -int merge_recursive_generic(const char **base_list, - const unsigned char *head_sha1, const char *head_name, - const unsigned char *next_sha1, const char *next_name) +int merge_recursive_generic(struct merge_options *o, + const unsigned char *head, + const unsigned char *merge, + int num_base_list, + const unsigned char **base_list, + struct commit **result) { int clean, index_fd; struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - struct commit *result; - struct commit *head_commit = get_ref(head_sha1, head_name); - struct commit *next_commit = get_ref(next_sha1, next_name); + struct commit *head_commit = get_ref(head, o->branch1); + struct commit *next_commit = get_ref(merge, o->branch2); struct commit_list *ca = NULL; if (base_list) { int i; - for (i = 0; base_list[i]; ++i) { - unsigned char sha[20]; + for (i = 0; i < num_base_list; ++i) { struct commit *base; - if (get_sha1(base_list[i], sha)) - return error("Could not resolve ref '%s'", - base_list[i]); - if (!(base = get_ref(sha, base_list[i]))) + if (!(base = get_ref(base_list[i], sha1_to_hex(base_list[i])))) return error("Could not parse object '%s'", - base_list[i]); + sha1_to_hex(base_list[i])); commit_list_insert(base, &ca); } } index_fd = hold_locked_index(lock, 1); - clean = merge_recursive(head_commit, next_commit, - head_name, next_name, ca, &result); + clean = merge_recursive(o, head_commit, next_commit, ca, + result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || commit_locked_index(lock))) @@ -1356,29 +1351,35 @@ int merge_recursive_generic(const char **base_list, return clean ? 0 : 1; } -int merge_recursive_config(const char *var, const char *value, void *cb) +static int merge_recursive_config(const char *var, const char *value, void *cb) { + struct merge_options *o = cb; if (!strcasecmp(var, "merge.verbosity")) { - merge_recursive_verbosity = git_config_int(var, value); + o->verbosity = git_config_int(var, value); return 0; } if (!strcasecmp(var, "diff.renamelimit")) { - diff_rename_limit = git_config_int(var, value); + o->diff_rename_limit = git_config_int(var, value); return 0; } if (!strcasecmp(var, "merge.renamelimit")) { - merge_rename_limit = git_config_int(var, value); + o->merge_rename_limit = git_config_int(var, value); return 0; } return git_default_config(var, value, cb); } -void merge_recursive_setup(int is_subtree_merge) +void init_merge_options(struct merge_options *o) { + memset(o, 0, sizeof(struct merge_options)); + o->verbosity = 2; + o->buffer_output = 1; + o->diff_rename_limit = -1; + o->merge_rename_limit = -1; + git_config(merge_recursive_config, o); if (getenv("GIT_MERGE_VERBOSITY")) - merge_recursive_verbosity = + o->verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); - if (merge_recursive_verbosity >= 5) - buffer_output = 0; - subtree_merge = is_subtree_merge; + if (o->verbosity >= 5) + o->buffer_output = 0; } diff --git a/merge-recursive.h b/merge-recursive.h index 4dd6476af6..72f0a2895d 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -1,26 +1,42 @@ #ifndef MERGE_RECURSIVE_H #define MERGE_RECURSIVE_H -int merge_recursive(struct commit *h1, +struct merge_options { + const char *branch1; + const char *branch2; + unsigned subtree_merge : 1; + unsigned buffer_output : 1; + int verbosity; + int diff_rename_limit; + int merge_rename_limit; +}; + +/* merge_trees() but with recursive ancestor consolidation */ +int merge_recursive(struct merge_options *o, + struct commit *h1, struct commit *h2, - const char *branch1, - const char *branch2, struct commit_list *ancestors, struct commit **result); -int merge_trees(struct tree *head, +/* rename-detecting three-way merge, no recursion */ +int merge_trees(struct merge_options *o, + struct tree *head, struct tree *merge, struct tree *common, - const char *branch1, - const char *branch2, struct tree **result); -extern int merge_recursive_generic(const char **base_list, - const unsigned char *head_sha1, const char *head_name, - const unsigned char *next_sha1, const char *next_name); -int merge_recursive_config(const char *var, const char *value, void *cb); -void merge_recursive_setup(int is_subtree_merge); -struct tree *write_tree_from_memory(void); -extern int merge_recursive_verbosity; +/* + * "git-merge-recursive" can be fed trees; wrap them into + * virtual commits and call merge_recursive() proper. + */ +int merge_recursive_generic(struct merge_options *o, + const unsigned char *head, + const unsigned char *merge, + int num_ca, + const unsigned char **ca, + struct commit **result); + +void init_merge_options(struct merge_options *o); +struct tree *write_tree_from_memory(struct merge_options *o); #endif -- cgit v1.2.1 From 18668f5319b079cce29e19817bc352b1413e0908 Mon Sep 17 00:00:00 2001 From: Miklos Vajna Date: Thu, 28 Aug 2008 15:43:00 +0200 Subject: builtin-merge: avoid run_command_v_opt() for recursive and subtree The try_merge_strategy() function always ran the strategy in a separate process, though this is not always necessary. The recursive and subtree strategy can be called without a fork(). This patch adds a check, and calls recursive in the same process without wasting resources. Signed-off-by: Johannes Schindelin Signed-off-by: Miklos Vajna Signed-off-by: Junio C Hamano --- builtin-merge.c | 92 +++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/builtin-merge.c b/builtin-merge.c index 9ad9791068..b857cf6246 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -23,6 +23,7 @@ #include "color.h" #include "rerere.h" #include "help.h" +#include "merge-recursive.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -545,28 +546,64 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *j; struct strbuf buf; - args = xmalloc((4 + commit_list_count(common) + - commit_list_count(remoteheads)) * sizeof(char *)); - strbuf_init(&buf, 0); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (j = common; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remoteheads; j; j = j->next) - args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remoteheads; j; j = j->next) - free((void *)args[i++]); - free(args); - return -ret; + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + int clean; + struct commit *result; + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int index_fd; + struct commit_list *reversed = NULL; + struct merge_options o; + + if (remoteheads->next) { + error("Not handling anything other than two heads merge."); + return 2; + } + + init_merge_options(&o); + if (!strcmp(strategy, "subtree")) + o.subtree_merge = 1; + + o.branch1 = head_arg; + o.branch2 = remoteheads->item->util; + + for (j = common; j; j = j->next) + commit_list_insert(j->item, &reversed); + + index_fd = hold_locked_index(lock, 1); + clean = merge_recursive(&o, lookup_commit(head), + remoteheads->item, reversed, &result); + if (active_cache_changed && + (write_cache(index_fd, active_cache, active_nr) || + commit_locked_index(lock))) + die ("unable to write %s", get_index_file()); + return clean ? 0 : 1; + } else { + args = xmalloc((4 + commit_list_count(common) + + commit_list_count(remoteheads)) * sizeof(char *)); + strbuf_init(&buf, 0); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (j = common; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remoteheads; j; j = j->next) + args[i++] = xstrdup(sha1_to_hex(j->item->object.sha1)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remoteheads; j; j = j->next) + free((void *)args[i++]); + free(args); + discard_cache(); + if (read_cache() < 0) + die("failed to read the cache"); + return -ret; + } } static void count_diff_files(struct diff_queue_struct *q, @@ -777,10 +814,6 @@ static int evaluate_result(void) int cnt = 0; struct rev_info rev; - discard_cache(); - if (read_cache() < 0) - die("failed to read the cache"); - /* Check how many files differ. */ init_revisions(&rev, ""); setup_revisions(0, NULL, &rev, NULL); @@ -914,12 +947,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { struct object *o; + struct commit *commit; o = peel_to_type(argv[i], 0, NULL, OBJ_COMMIT); if (!o) die("%s - not something we can merge", argv[i]); - remotes = &commit_list_insert(lookup_commit(o->sha1), - remotes)->next; + commit = lookup_commit(o->sha1); + commit->util = (void *)argv[i]; + remotes = &commit_list_insert(commit, remotes)->next; strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); setenv(buf.buf, argv[i], 1); @@ -1113,7 +1148,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } /* Automerge succeeded. */ - discard_cache(); write_tree_trivial(result_tree); automerge_was_ok = 1; break; -- cgit v1.2.1 From a5a818ee4877e4458e8e6741a03ac3b19941d58a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 18 Aug 2008 20:08:09 -0700 Subject: diff: vary default prefix depending on what are compared With a new configuration "diff.mnemonicprefix", "git diff" shows the differences between various combinations of preimage and postimage trees with prefixes different from the standard "a/" and "b/". Hopefully this will make the distinction stand out for some people. "git diff" compares the (i)ndex and the (w)ork tree; "git diff HEAD" compares a (c)ommit and the (w)ork tree; "git diff --cached" compares a (c)ommit and the (i)ndex; "git-diff HEAD:file1 file2" compares an (o)bject and a (w)ork tree entity; "git diff --no-index a b" compares two non-git things (1) and (2). Because these mnemonics now have meanings, they are swapped when reverse diff is in effect and this feature is enabled. Signed-off-by: Junio C Hamano --- Documentation/config.txt | 16 ++++++++++++++++ builtin-diff.c | 2 ++ combine-diff.c | 8 ++++++-- diff-lib.c | 3 +++ diff-no-index.c | 1 + diff.c | 46 ++++++++++++++++++++++++++++++++++++++++------ diff.h | 2 ++ 7 files changed, 70 insertions(+), 8 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 81f981509a..74af36de5c 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -581,6 +581,22 @@ diff.external:: you want to use an external diff program only on a subset of your files, you might want to use linkgit:gitattributes[5] instead. +diff.mnemonicprefix:: + If set, 'git-diff' uses a prefix pair that is different from the + standard "a/" and "b/" depending on what is being compared. When + this configuration is in effect, reverse diff output also swaps + the order of the prefixes: +'git-diff';; + compares the (i)ndex and the (w)ork tree; +'git-diff HEAD';; + compares a (c)ommit and the (w)ork tree; +'git diff --cached';; + compares a (c)ommit and the (i)ndex; +'git-diff HEAD:file1 file2';; + compares an (o)bject and a (w)ork tree entity; +'git diff --no-index a b';; + compares two non-git things (1) and (2). + diff.renameLimit:: The number of files to consider when performing the copy/rename detection; equivalent to the 'git-diff' option '-l'. diff --git a/builtin-diff.c b/builtin-diff.c index 7ffea97505..266337b832 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -74,6 +74,8 @@ static int builtin_diff_b_f(struct rev_info *revs, if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) die("'%s': not a regular file or symlink", path); + diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/"); + if (blob[0].mode == S_IFINVALID) blob[0].mode = canon_mode(st.st_mode); diff --git a/combine-diff.c b/combine-diff.c index 4dfc330867..19bd60e346 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -675,9 +675,13 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int i, show_hunks; int working_tree_file = is_null_sha1(elem->sha1); int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV; + const char *a_prefix, *b_prefix; mmfile_t result_file; context = opt->context; + a_prefix = opt->a_prefix ? opt->a_prefix : "a/"; + b_prefix = opt->b_prefix ? opt->b_prefix : "b/"; + /* Read the result of merge first */ if (!working_tree_file) result = grab_blob(elem->sha1, &result_size); @@ -853,13 +857,13 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, dump_quoted_path("--- ", "", "/dev/null", c_meta, c_reset); else - dump_quoted_path("--- ", opt->a_prefix, elem->path, + dump_quoted_path("--- ", a_prefix, elem->path, c_meta, c_reset); if (deleted) dump_quoted_path("+++ ", "", "/dev/null", c_meta, c_reset); else - dump_quoted_path("+++ ", opt->b_prefix, elem->path, + dump_quoted_path("+++ ", b_prefix, elem->path, c_meta, c_reset); dump_sline(sline, cnt, num_parent, DIFF_OPT_TST(opt, COLOR_DIFF)); diff --git a/diff-lib.c b/diff-lib.c index e7eaff9a68..ae96c64ca2 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -63,6 +63,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) ? CE_MATCH_RACY_IS_DIRTY : 0); char symcache[PATH_MAX]; + diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/"); + if (diff_unmerged_stage < 0) diff_unmerged_stage = 2; entries = active_nr; @@ -469,6 +471,7 @@ int run_diff_index(struct rev_info *revs, int cached) if (unpack_trees(1, &t, &opts)) exit(128); + diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); return 0; diff --git a/diff-no-index.c b/diff-no-index.c index 7d68b7f1be..b60d3455da 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -252,6 +252,7 @@ void diff_no_index(struct rev_info *revs, if (queue_diff(&revs->diffopt, revs->diffopt.paths[0], revs->diffopt.paths[1])) exit(1); + diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); diff_flush(&revs->diffopt); diff --git a/diff.c b/diff.c index 7b4300a74a..c1804efed9 100644 --- a/diff.c +++ b/diff.c @@ -23,6 +23,7 @@ static int diff_rename_limit_default = 200; int diff_use_color_default = -1; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; +static int diff_mnemonic_prefix; static char diff_colors[][COLOR_MAXLEN] = { "\033[m", /* reset */ @@ -149,6 +150,10 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_auto_refresh_index = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.mnemonicprefix")) { + diff_mnemonic_prefix = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "diff.external")) return git_config_string(&external_diff_cmd_cfg, var, value); if (!prefixcmp(var, "diff.")) { @@ -305,6 +310,15 @@ static void emit_rewrite_diff(const char *name_a, const char *new = diff_get_color(color_diff, DIFF_FILE_NEW); const char *reset = diff_get_color(color_diff, DIFF_RESET); static struct strbuf a_name = STRBUF_INIT, b_name = STRBUF_INIT; + const char *a_prefix, *b_prefix; + + if (diff_mnemonic_prefix && DIFF_OPT_TST(o, REVERSE_DIFF)) { + a_prefix = o->b_prefix; + b_prefix = o->a_prefix; + } else { + a_prefix = o->a_prefix; + b_prefix = o->b_prefix; + } name_a += (*name_a == '/'); name_b += (*name_b == '/'); @@ -313,8 +327,8 @@ static void emit_rewrite_diff(const char *name_a, strbuf_reset(&a_name); strbuf_reset(&b_name); - quote_two_c_style(&a_name, o->a_prefix, name_a, 0); - quote_two_c_style(&b_name, o->b_prefix, name_b, 0); + quote_two_c_style(&a_name, a_prefix, name_a, 0); + quote_two_c_style(&b_name, b_prefix, name_b, 0); diff_populate_filespec(one, 0); diff_populate_filespec(two, 0); @@ -1432,6 +1446,14 @@ static const char *diff_funcname_pattern(struct diff_filespec *one) return NULL; } +void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b) +{ + if (!options->a_prefix) + options->a_prefix = a; + if (!options->b_prefix) + options->b_prefix = b; +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -1445,9 +1467,19 @@ static void builtin_diff(const char *name_a, char *a_one, *b_two; const char *set = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); + const char *a_prefix, *b_prefix; + + diff_set_mnemonic_prefix(o, "a/", "b/"); + if (DIFF_OPT_TST(o, REVERSE_DIFF)) { + a_prefix = o->b_prefix; + b_prefix = o->a_prefix; + } else { + a_prefix = o->a_prefix; + b_prefix = o->b_prefix; + } - a_one = quote_two(o->a_prefix, name_a + (*name_a == '/')); - b_two = quote_two(o->b_prefix, name_b + (*name_b == '/')); + a_one = quote_two(a_prefix, name_a + (*name_a == '/')); + b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset); @@ -2308,8 +2340,10 @@ void diff_setup(struct diff_options *options) DIFF_OPT_CLR(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; - options->a_prefix = "a/"; - options->b_prefix = "b/"; + if (!diff_mnemonic_prefix) { + options->a_prefix = "a/"; + options->b_prefix = "b/"; + } } int diff_setup_done(struct diff_options *options) diff --git a/diff.h b/diff.h index 50fb5ddb0b..9a679f58f5 100644 --- a/diff.h +++ b/diff.h @@ -160,6 +160,8 @@ extern void diff_tree_combined(const unsigned char *sha1, const unsigned char pa extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *); +void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b); + extern void diff_addremove(struct diff_options *, int addremove, unsigned mode, -- cgit v1.2.1 From 146ea068a0434a7423d8b0d77f27ccff0a584ac4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 26 Aug 2008 23:13:13 -0700 Subject: git commit --author=$name: look $name up in existing commits This allows "git commit --author=$name" to accept a name that is not in the required "A U Thor " format, and use that to look up an author name that matches from existing commits. When using this feature, it is the user's responsibility to give a name that uniquely matches the name s/he wants, as the logic returns the name from the first matching commit. Signed-off-by: Junio C Hamano --- Documentation/git-commit.txt | 6 ++++-- builtin-commit.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 0e25bb8627..eb05b0f49b 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -75,8 +75,10 @@ OPTIONS read the message from the standard input. --author=:: - Override the author name used in the commit. Use - `A U Thor ` format. + Override the author name used in the commit. You can use the + standard `A U Thor ` format. Otherwise, + an existing commit that matches the given string and its author + name is used. -m :: --message=:: diff --git a/builtin-commit.c b/builtin-commit.c index c870037b07..4182686b90 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -710,6 +710,31 @@ static int message_is_empty(struct strbuf *sb, int start) return 1; } +static const char *find_author_by_nickname(const char *name) +{ + struct rev_info revs; + struct commit *commit; + struct strbuf buf = STRBUF_INIT; + const char *av[20]; + int ac = 0; + + init_revisions(&revs, NULL); + strbuf_addf(&buf, "--author=%s", name); + av[++ac] = "--all"; + av[++ac] = "-i"; + av[++ac] = buf.buf; + av[++ac] = NULL; + setup_revisions(ac, av, &revs, NULL); + prepare_revision_walk(&revs); + commit = get_revision(&revs); + if (commit) { + strbuf_release(&buf); + format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL); + return strbuf_detach(&buf, NULL); + } + die("No existing author found with '%s'", name); +} + static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], const char *prefix) @@ -720,6 +745,9 @@ static int parse_and_validate_options(int argc, const char *argv[], logfile = parse_options_fix_filename(prefix, logfile); template_file = parse_options_fix_filename(prefix, template_file); + if (force_author && !strchr(force_author, '>')) + force_author = find_author_by_nickname(force_author); + if (logfile || message.len || use_message) use_editor = 0; if (edit_flag) -- cgit v1.2.1 From 1707adb7f2b75b3204f34475828f301ce05cb384 Mon Sep 17 00:00:00 2001 From: Teemu Likonen Date: Fri, 29 Aug 2008 10:29:42 +0300 Subject: config.txt: Add missing colons after option name gitcvs.usecrlfattr --> gitcvs.usecrlfattr:: This fixes an asciidoc markup issue. Signed-off-by: Teemu Likonen Signed-off-by: Junio C Hamano --- Documentation/config.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 81f981509a..3727239891 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -693,7 +693,7 @@ gitcvs.logfile:: Path to a log file where the CVS server interface well... logs various stuff. See linkgit:git-cvsserver[1]. -gitcvs.usecrlfattr +gitcvs.usecrlfattr:: If true, the server will look up the `crlf` attribute for files to determine the '-k' modes to use. If `crlf` is set, the '-k' mode will be left blank, so cvs clients will -- cgit v1.2.1 From f83eafdd6251af29e259da8becb7610da9f3e933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 30 Aug 2008 16:13:58 +0700 Subject: update-index: fix worktree setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin-update-index.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin-update-index.c b/builtin-update-index.c index 38eb53ccba..f2c90ff1cd 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -614,10 +614,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(path, "--refresh")) { + setup_work_tree(); has_errors |= refresh_cache(refresh_flags); continue; } if (!strcmp(path, "--really-refresh")) { + setup_work_tree(); has_errors |= refresh_cache(REFRESH_REALLY | refresh_flags); continue; } @@ -684,6 +686,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) goto finish; } if (!strcmp(path, "--again") || !strcmp(path, "-g")) { + setup_work_tree(); has_errors = do_reupdate(argc - i, argv + i, prefix, prefix_length); if (has_errors) @@ -702,6 +705,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) usage(update_index_usage); die("unknown option %s", path); } + setup_work_tree(); p = prefix_path(prefix, prefix_length, path); update_one(p, NULL, 0); if (set_executable_bit) @@ -714,6 +718,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_init(&buf, 0); strbuf_init(&nbuf, 0); + setup_work_tree(); while (strbuf_getline(&buf, stdin, line_termination) != EOF) { const char *p; if (line_termination && buf.buf[0] == '"') { -- cgit v1.2.1 From bb528633b34ac9c338a7761f3e1d251e0c560ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sat, 30 Aug 2008 16:15:32 +0700 Subject: setup_git_directory(): fix move to worktree toplevel directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When setup_git_directory() returns successfully, it is supposed to move current working directory to worktree toplevel directory. However, the code recomputing prefix inside setup_git_directory() has to move cwd back to original working directory, in order to get new prefix. After that, it should move cwd back to worktree toplevel directory as expected. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- setup.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.c b/setup.c index 6cf909463d..2e3248a0c4 100644 --- a/setup.c +++ b/setup.c @@ -581,6 +581,8 @@ const char *setup_git_directory(void) if (retval && chdir(retval)) die ("Could not jump back into original cwd"); rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree()); + if (rel && *rel && chdir(get_git_work_tree())) + die ("Could not jump to working directory"); return rel && *rel ? strcat(rel, "/") : NULL; } -- cgit v1.2.1 From 7e44c93558e7c0b12624d76cf07753d0480ed96a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 31 Aug 2008 09:39:19 -0700 Subject: 'git foo' program identifies itself without dash in die() messages This is a mechanical conversion of all '*.c' files with: s/((?:die|error|warning)\("git)-(\S+:)/$1 $2/; The result was manually inspected and no false positive was found. Signed-off-by: Junio C Hamano --- builtin-apply.c | 6 +++--- builtin-checkout-index.c | 6 +++--- builtin-commit-tree.c | 2 +- builtin-fetch-pack.c | 2 +- builtin-grep.c | 2 +- builtin-ls-files.c | 6 +++--- builtin-rm.c | 4 ++-- builtin-show-ref.c | 6 +++--- builtin-tar-tree.c | 4 ++-- builtin-update-index.c | 16 ++++++++-------- connect.c | 4 ++-- entry.c | 20 ++++++++++---------- merge-index.c | 6 +++--- tree-diff.c | 2 +- upload-pack.c | 18 +++++++++--------- 15 files changed, 52 insertions(+), 52 deletions(-) diff --git a/builtin-apply.c b/builtin-apply.c index 40eeabb625..4eb263ec58 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -506,17 +506,17 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, name = orig_name; len = strlen(name); if (isnull) - die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); + die("git apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr); another = find_name(line, NULL, p_value, TERM_TAB); if (!another || memcmp(another, name, len)) - die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); + die("git apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr); free(another); return orig_name; } else { /* expect "/dev/null" */ if (memcmp("/dev/null", line, 9) || line[9] != '\n') - die("git-apply: bad git-diff - expected /dev/null on line %d", linenr); + die("git apply: bad git-diff - expected /dev/null on line %d", linenr); return NULL; } } diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 71ebabf990..90f8523eb5 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -258,9 +258,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) const char *p; if (all) - die("git-checkout-index: don't mix '--all' and explicit filenames"); + die("git checkout-index: don't mix '--all' and explicit filenames"); if (read_from_stdin) - die("git-checkout-index: don't mix '--stdin' and explicit filenames"); + die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); checkout_file(p, prefix_length); if (p < arg || p > arg + strlen(arg)) @@ -271,7 +271,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct strbuf buf, nbuf; if (all) - die("git-checkout-index: don't mix '--all' and '--stdin'"); + die("git checkout-index: don't mix '--all' and '--stdin'"); strbuf_init(&buf, 0); strbuf_init(&nbuf, 0); diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 7a9a309be0..291c43cf70 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -118,7 +118,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) } if (strbuf_read(&buffer, 0, 0) < 0) - die("git-commit-tree: read returned %s", strerror(errno)); + die("git commit-tree: read returned %s", strerror(errno)); if (!commit_tree(buffer.buf, tree_sha1, parents, commit_sha1)) { printf("%s\n", sha1_to_hex(commit_sha1)); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 273239af3b..6b37281a95 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -609,7 +609,7 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "warning: no common commits\n"); if (get_pack(fd, pack_lockfile)) - die("git-fetch-pack: fetch failed."); + die("git fetch-pack: fetch failed."); all_done: return ref; diff --git a/builtin-grep.c b/builtin-grep.c index 631129ddfd..f59f95f175 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -774,7 +774,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* Make sure we do not get outside of paths */ for (i = 0; paths[i]; i++) if (strncmp(prefix, paths[i], opt.prefix_length)) - die("git-grep: cannot generate relative filenames containing '..'"); + die("git grep: cannot generate relative filenames containing '..'"); } } else if (prefix) { diff --git a/builtin-ls-files.c b/builtin-ls-files.c index e8d568eed7..068f424696 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -78,7 +78,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) int offset = prefix_offset; if (len >= ent->len) - die("git-ls-files: internal error - directory entry not superset of prefix"); + die("git ls-files: internal error - directory entry not superset of prefix"); if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len)) return; @@ -183,7 +183,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) int offset = prefix_offset; if (len >= ce_namelen(ce)) - die("git-ls-files: internal error - cache entry not superset of prefix"); + die("git ls-files: internal error - cache entry not superset of prefix"); if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len)) return; @@ -319,7 +319,7 @@ static const char *verify_pathspec(const char *prefix) } if (prefix_offset > max || memcmp(prev, prefix, prefix_offset)) - die("git-ls-files: cannot generate relative filenames containing '..'"); + die("git ls-files: cannot generate relative filenames containing '..'"); prefix_len = max; return max ? xmemdupz(prev, max) : NULL; diff --git a/builtin-rm.c b/builtin-rm.c index 0ed26bb8f1..6bd82111d2 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -221,7 +221,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) printf("rm '%s'\n", path); if (remove_file_from_cache(path)) - die("git-rm: unable to remove %s", path); + die("git rm: unable to remove %s", path); } if (show_only) @@ -244,7 +244,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) continue; } if (!removed) - die("git-rm: %s: %s", path, strerror(errno)); + die("git rm: %s: %s", path, strerror(errno)); } } diff --git a/builtin-show-ref.c b/builtin-show-ref.c index add16004f1..572b114119 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -62,7 +62,7 @@ match: * ref points at a nonexistent object. */ if (!has_sha1_file(sha1)) - die("git-show-ref: bad ref %s (%s)", refname, + die("git show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); if (quiet) @@ -82,12 +82,12 @@ match: else { obj = parse_object(sha1); if (!obj) - die("git-show-ref: bad ref %s (%s)", refname, + die("git show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1)); if (obj->type == OBJ_TAG) { obj = deref_tag(obj, refname, 0); if (!obj) - die("git-show-ref: bad tag at ref %s (%s)", refname, + die("git show-ref: bad tag at ref %s (%s)", refname, sha1_to_hex(sha1)); hex = find_unique_abbrev(obj->sha1, abbrev); printf("%s %s^{}\n", hex, refname); diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index f4bea4a322..cb7007e25f 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -76,7 +76,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) n = read_in_full(0, buffer, HEADERSIZE); if (n < HEADERSIZE) - die("git-get-tar-commit-id: read error"); + die("git get-tar-commit-id: read error"); if (header->typeflag[0] != 'g') return 1; if (memcmp(content, "52 comment=", 11)) @@ -84,7 +84,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix) n = write_in_full(1, content + 11, 41); if (n < 41) - die("git-get-tar-commit-id: write error"); + die("git get-tar-commit-id: write error"); return 0; } diff --git a/builtin-update-index.c b/builtin-update-index.c index 38eb53ccba..e5bb2a03cb 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -262,7 +262,7 @@ static void chmod_path(int flip, const char *path) report("chmod %cx '%s'", flip, path); return; fail: - die("git-update-index: cannot chmod %cx '%s'", flip, path); + die("git update-index: cannot chmod %cx '%s'", flip, path); } static void update_one(const char *path, const char *prefix, int prefix_length) @@ -280,7 +280,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) if (force_remove) { if (remove_file_from_cache(p)) - die("git-update-index: unable to remove %s", path); + die("git update-index: unable to remove %s", path); report("remove '%s'", path); goto free_return; } @@ -351,7 +351,7 @@ static void read_index_info(int line_termination) if (line_termination && path_name[0] == '"') { strbuf_reset(&uq); if (unquote_c_style(&uq, path_name, NULL)) { - die("git-update-index: bad quoting of path name"); + die("git update-index: bad quoting of path name"); } path_name = uq.buf; } @@ -364,7 +364,7 @@ static void read_index_info(int line_termination) if (!mode) { /* mode == 0 means there is no such path -- remove */ if (remove_file_from_cache(path_name)) - die("git-update-index: unable to remove %s", + die("git update-index: unable to remove %s", ptr); } else { @@ -374,7 +374,7 @@ static void read_index_info(int line_termination) */ ptr[-42] = ptr[-1] = 0; if (add_cacheinfo(mode, sha1, path_name, stage)) - die("git-update-index: unable to update %s", + die("git update-index: unable to update %s", path_name); } continue; @@ -626,12 +626,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) unsigned int mode; if (i+3 >= argc) - die("git-update-index: --cacheinfo "); + die("git update-index: --cacheinfo "); if (strtoul_ui(argv[i+1], 8, &mode) || get_sha1_hex(argv[i+2], sha1) || add_cacheinfo(mode, sha1, argv[i+3], 0)) - die("git-update-index: --cacheinfo" + die("git update-index: --cacheinfo" " cannot add %s", argv[i+3]); i += 3; continue; @@ -639,7 +639,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (!strcmp(path, "--chmod=-x") || !strcmp(path, "--chmod=+x")) { if (argc <= i+1) - die("git-update-index: %s ", path); + die("git update-index: %s ", path); set_executable_bit = path[8]; continue; } diff --git a/connect.c b/connect.c index 574f42fa47..dd96f8e043 100644 --- a/connect.c +++ b/connect.c @@ -97,7 +97,7 @@ int get_ack(int fd, unsigned char *result_sha1) int len = packet_read_line(fd, line, sizeof(line)); if (!len) - die("git-fetch-pack: expected ACK/NAK, got EOF"); + die("git fetch-pack: expected ACK/NAK, got EOF"); if (line[len-1] == '\n') line[--len] = 0; if (!strcmp(line, "NAK")) @@ -109,7 +109,7 @@ int get_ack(int fd, unsigned char *result_sha1) return 1; } } - die("git-fetch_pack: expected ACK/NAK, got '%s'", line); + die("git fetch_pack: expected ACK/NAK, got '%s'", line); } int path_match(const char *path, int nr, char **match) diff --git a/entry.c b/entry.c index 222aaa374b..aa2ee46a84 100644 --- a/entry.c +++ b/entry.c @@ -111,7 +111,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout case S_IFREG: new = read_blob_entry(ce, path, &size); if (!new) - return error("git-checkout-index: unable to read sha1 file of %s (%s)", + return error("git checkout-index: unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); /* @@ -132,7 +132,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout fd = create_file(path, ce->ce_mode); if (fd < 0) { free(new); - return error("git-checkout-index: unable to create file %s (%s)", + return error("git checkout-index: unable to create file %s (%s)", path, strerror(errno)); } @@ -140,12 +140,12 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout close(fd); free(new); if (wrote != size) - return error("git-checkout-index: unable to write file %s", path); + return error("git checkout-index: unable to write file %s", path); break; case S_IFLNK: new = read_blob_entry(ce, path, &size); if (!new) - return error("git-checkout-index: unable to read sha1 file of %s (%s)", + return error("git checkout-index: unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); if (to_tempfile || !has_symlinks) { if (to_tempfile) { @@ -155,31 +155,31 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout fd = create_file(path, 0666); if (fd < 0) { free(new); - return error("git-checkout-index: unable to create " + return error("git checkout-index: unable to create " "file %s (%s)", path, strerror(errno)); } wrote = write_in_full(fd, new, size); close(fd); free(new); if (wrote != size) - return error("git-checkout-index: unable to write file %s", + return error("git checkout-index: unable to write file %s", path); } else { wrote = symlink(new, path); free(new); if (wrote) - return error("git-checkout-index: unable to create " + return error("git checkout-index: unable to create " "symlink %s (%s)", path, strerror(errno)); } break; case S_IFGITLINK: if (to_tempfile) - return error("git-checkout-index: cannot create temporary subproject %s", path); + return error("git checkout-index: cannot create temporary subproject %s", path); if (mkdir(path, 0777) < 0) - return error("git-checkout-index: cannot create subproject directory %s", path); + return error("git checkout-index: cannot create subproject directory %s", path); break; default: - return error("git-checkout-index: unknown file mode for %s", path); + return error("git checkout-index: unknown file mode for %s", path); } if (state->refresh_cache) { diff --git a/merge-index.c b/merge-index.c index 7491c56ad2..7827e87a92 100644 --- a/merge-index.c +++ b/merge-index.c @@ -27,7 +27,7 @@ static int merge_entry(int pos, const char *path) int found; if (pos >= active_nr) - die("git-merge-index: %s not in the cache", path); + die("git merge-index: %s not in the cache", path); arguments[0] = pgm; arguments[1] = ""; arguments[2] = ""; @@ -53,7 +53,7 @@ static int merge_entry(int pos, const char *path) arguments[stage + 4] = ownbuf[stage]; } while (++pos < active_nr); if (!found) - die("git-merge-index: %s not in the cache", path); + die("git merge-index: %s not in the cache", path); run_program(); return found; } @@ -117,7 +117,7 @@ int main(int argc, char **argv) merge_all(); continue; } - die("git-merge-index: unknown option %s", arg); + die("git merge-index: unknown option %s", arg); } merge_file(arg); } diff --git a/tree-diff.c b/tree-diff.c index bbb126fc46..9f67af6c1f 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -303,7 +303,7 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru update_tree_entry(t2); continue; } - die("git-diff-tree: internal error"); + die("git diff-tree: internal error"); } return 0; } diff --git a/upload-pack.c b/upload-pack.c index c911e70c9a..e5adbc011e 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -157,7 +157,7 @@ static void create_pack_file(void) /* .data is just a boolean: any non-NULL value will do */ rev_list.data = create_full_pack ? &rev_list : NULL; if (start_async(&rev_list)) - die("git-upload-pack: unable to fork git-rev-list"); + die("git upload-pack: unable to fork git-rev-list"); argv[arg++] = "pack-objects"; argv[arg++] = "--stdout"; @@ -177,7 +177,7 @@ static void create_pack_file(void) pack_objects.argv = argv; if (start_command(&pack_objects)) - die("git-upload-pack: unable to fork git-pack-objects"); + die("git upload-pack: unable to fork git-pack-objects"); /* We read from pack_objects.err to capture stderr output for * progress bar, and pack_objects.out to capture the pack data. @@ -271,7 +271,7 @@ static void create_pack_file(void) } if (finish_command(&pack_objects)) { - error("git-upload-pack: git-pack-objects died with error."); + error("git upload-pack: git-pack-objects died with error."); goto fail; } if (finish_async(&rev_list)) @@ -291,7 +291,7 @@ static void create_pack_file(void) fail: send_client_data(3, abort_msg, sizeof(abort_msg)); - die("git-upload-pack: %s", abort_msg); + die("git upload-pack: %s", abort_msg); } static int got_sha1(char *hex, unsigned char *sha1) @@ -300,7 +300,7 @@ static int got_sha1(char *hex, unsigned char *sha1) int we_knew_they_have = 0; if (get_sha1_hex(hex, sha1)) - die("git-upload-pack: expected SHA1 object, got '%s'", hex); + die("git upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) return -1; @@ -440,7 +440,7 @@ static int get_common_commits(void) packet_write(1, "NAK\n"); return -1; } - die("git-upload-pack: expected SHA1 list, got '%s'", line); + die("git upload-pack: expected SHA1 list, got '%s'", line); } } @@ -485,7 +485,7 @@ static void receive_needs(void) } if (prefixcmp(line, "want ") || get_sha1_hex(line+5, sha1_buf)) - die("git-upload-pack: protocol error, " + die("git upload-pack: protocol error, " "expected to get sha, not '%s'", line); if (strstr(line+45, "multi_ack")) multi_ack = 1; @@ -512,7 +512,7 @@ static void receive_needs(void) */ o = lookup_object(sha1_buf); if (!o || !(o->flags & OUR_REF)) - die("git-upload-pack: not our ref %s", line+5); + die("git upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; add_object_array(o, NULL, &want_obj); @@ -577,7 +577,7 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo struct object *o = parse_object(sha1); if (!o) - die("git-upload-pack: cannot find object %s:", sha1_to_hex(sha1)); + die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); if (capabilities) packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname, -- cgit v1.2.1 From 8af84dadb142f7321ff0ce8690385e99da8ede2f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 31 Aug 2008 15:50:23 +0200 Subject: git wrapper: DWIM mistyped commands This patch introduces a modified Damerau-Levenshtein algorithm into Git's code base, and uses it with the following penalties to show some similar commands when an unknown command was encountered: swap = 0, insertion = 1, substitution = 2, deletion = 4 A typical output would now look like this: $ git sm git: 'sm' is not a git-command. See 'git --help'. Did you mean one of these? am rm The cut-off is at similarity rating 6, which was empirically determined to give sensible results. As a convenience, if there is only one candidate, Git continues under the assumption that the user mistyped it. Example: $ git reabse WARNING: You called a Git program named 'reabse', which does not exist. Continuing under the assumption that you meant 'rebase' [...] Signed-off-by: Johannes Schindelin Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- Makefile | 2 ++ builtin.h | 2 +- git.c | 4 +++- help.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- help.h | 2 +- levenshtein.c | 47 ++++++++++++++++++++++++++++++++++++++ levenshtein.h | 8 +++++++ 7 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 levenshtein.c create mode 100644 levenshtein.h diff --git a/Makefile b/Makefile index bf400e64f3..3daa6dcdb0 100644 --- a/Makefile +++ b/Makefile @@ -358,6 +358,7 @@ LIB_H += graph.h LIB_H += grep.h LIB_H += hash.h LIB_H += help.h +LIB_H += levenshtein.h LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h @@ -433,6 +434,7 @@ LIB_OBJS += hash.o LIB_OBJS += help.o LIB_OBJS += ident.o LIB_OBJS += interpolate.o +LIB_OBJS += levenshtein.o LIB_OBJS += list-objects.o LIB_OBJS += ll-merge.o LIB_OBJS += lockfile.o diff --git a/builtin.h b/builtin.h index f3502d305e..e67cb2090e 100644 --- a/builtin.h +++ b/builtin.h @@ -11,7 +11,7 @@ extern const char git_usage_string[]; extern const char git_more_info_string[]; extern void list_common_cmds_help(void); -extern void help_unknown_cmd(const char *cmd); +extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); extern int read_line_with_nul(char *buf, int size, FILE *file); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, diff --git a/git.c b/git.c index 37b1d76a08..54c5bfa69b 100644 --- a/git.c +++ b/git.c @@ -499,7 +499,9 @@ int main(int argc, const char **argv) cmd, argv[0]); exit(1); } - help_unknown_cmd(cmd); + argv[0] = help_unknown_cmd(cmd); + handle_internal_command(argc, argv); + execv_dashed_external(argv); } fprintf(stderr, "Failed to run command '%s': %s\n", diff --git a/help.c b/help.c index 1afbac0927..b1ebca4091 100644 --- a/help.c +++ b/help.c @@ -1,6 +1,7 @@ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" +#include "levenshtein.h" #include "help.h" /* most GUI terminals set COLUMNS (although some don't export it) */ @@ -37,6 +38,16 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len) cmds->names[cmds->cnt++] = ent; } +static void clean_cmdnames(struct cmdnames *cmds) +{ + int i; + for (i = 0; i < cmds->cnt; ++i) + free(cmds->names[i]); + free(cmds->names); + cmds->cnt = 0; + cmds->alloc = 0; +} + static int cmdname_compare(const void *a_, const void *b_) { struct cmdname *a = *(struct cmdname **)a_; @@ -257,9 +268,68 @@ int is_in_cmdlist(struct cmdnames *c, const char *s) return 0; } -void help_unknown_cmd(const char *cmd) +static int levenshtein_compare(const void *p1, const void *p2) +{ + const struct cmdname *const *c1 = p1, *const *c2 = p2; + const char *s1 = (*c1)->name, *s2 = (*c2)->name; + int l1 = (*c1)->len; + int l2 = (*c2)->len; + return l1 != l2 ? l1 - l2 : strcmp(s1, s2); +} + +const char *help_unknown_cmd(const char *cmd) { + int i, n, best_similarity = 0; + struct cmdnames main_cmds, other_cmds; + + memset(&main_cmds, 0, sizeof(main_cmds)); + memset(&other_cmds, 0, sizeof(main_cmds)); + + load_command_list("git-", &main_cmds, &other_cmds); + + ALLOC_GROW(main_cmds.names, main_cmds.cnt + other_cmds.cnt, + main_cmds.alloc); + memcpy(main_cmds.names + main_cmds.cnt, other_cmds.names, + other_cmds.cnt * sizeof(other_cmds.names[0])); + main_cmds.cnt += other_cmds.cnt; + free(other_cmds.names); + + /* This reuses cmdname->len for similarity index */ + for (i = 0; i < main_cmds.cnt; ++i) + main_cmds.names[i]->len = + levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4); + + qsort(main_cmds.names, main_cmds.cnt, + sizeof(*main_cmds.names), levenshtein_compare); + + if (!main_cmds.cnt) + die ("Uh oh. Your system reports no Git commands at all."); + + best_similarity = main_cmds.names[0]->len; + n = 1; + while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len) + ++n; + if (n == 1) { + const char *assumed = main_cmds.names[0]->name; + main_cmds.names[0] = NULL; + clean_cmdnames(&main_cmds); + fprintf(stderr, "WARNING: You called a Git program named '%s', " + "which does not exist.\n" + "Continuing under the assumption that you meant '%s'\n", + cmd, assumed); + return assumed; + } + fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd); + + if (best_similarity < 6) { + fprintf(stderr, "\nDid you mean %s?\n", + n < 2 ? "this": "one of these"); + + for (i = 0; i < n; i++) + fprintf(stderr, "\t%s\n", main_cmds.names[i]->name); + } + exit(1); } diff --git a/help.h b/help.h index 3f1ae89dd6..5fc7892705 100644 --- a/help.h +++ b/help.h @@ -5,7 +5,7 @@ struct cmdnames { int alloc; int cnt; struct cmdname { - size_t len; + size_t len; /* also used for similarity index in help.c */ char name[FLEX_ARRAY]; } **names; }; diff --git a/levenshtein.c b/levenshtein.c new file mode 100644 index 0000000000..db52f2c205 --- /dev/null +++ b/levenshtein.c @@ -0,0 +1,47 @@ +#include "cache.h" +#include "levenshtein.h" + +int levenshtein(const char *string1, const char *string2, + int w, int s, int a, int d) +{ + int len1 = strlen(string1), len2 = strlen(string2); + int *row0 = xmalloc(sizeof(int) * (len2 + 1)); + int *row1 = xmalloc(sizeof(int) * (len2 + 1)); + int *row2 = xmalloc(sizeof(int) * (len2 + 1)); + int i, j; + + for (j = 0; j <= len2; j++) + row1[j] = j * a; + for (i = 0; i < len1; i++) { + int *dummy; + + row2[0] = (i + 1) * d; + for (j = 0; j < len2; j++) { + /* substitution */ + row2[j + 1] = row1[j] + s * (string1[i] != string2[j]); + /* swap */ + if (i > 0 && j > 0 && string1[i - 1] == string2[j] && + string1[i] == string2[j - 1] && + row2[j + 1] > row0[j - 1] + w) + row2[j + 1] = row0[j - 1] + w; + /* deletion */ + if (j + 1 < len2 && row2[j + 1] > row1[j + 1] + d) + row2[j + 1] = row1[j + 1] + d; + /* insertion */ + if (row2[j + 1] > row2[j] + a) + row2[j + 1] = row2[j] + a; + } + + dummy = row0; + row0 = row1; + row1 = row2; + row2 = dummy; + } + + i = row1[len2]; + free(row0); + free(row1); + free(row2); + + return i; +} diff --git a/levenshtein.h b/levenshtein.h new file mode 100644 index 0000000000..0173abeef5 --- /dev/null +++ b/levenshtein.h @@ -0,0 +1,8 @@ +#ifndef LEVENSHTEIN_H +#define LEVENSHTEIN_H + +int levenshtein(const char *string1, const char *string2, + int swap_penalty, int substition_penalty, + int insertion_penalty, int deletion_penalty); + +#endif -- cgit v1.2.1 From f0e90716d47b429284702b75425a247c9fc41adb Mon Sep 17 00:00:00 2001 From: Alex Riesen Date: Sun, 31 Aug 2008 15:54:58 +0200 Subject: Add help.autocorrect to enable/disable autocorrecting It is off(0) by default, to avoid scaring people unless they asked to. If set to a non-0 value, wait for that amount of deciseconds before running the corrected command. Suggested by Junio, so he has a chance to hit Ctrl-C. Signed-off-by: Alex Riesen Signed-off-by: Junio C Hamano --- Documentation/config.txt | 9 +++++++++ help.c | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index af57d94304..8c644ab8f6 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -790,6 +790,15 @@ help.format:: Values 'man', 'info', 'web' and 'html' are supported. 'man' is the default. 'web' and 'html' are the same. +help.autocorrect:: + Automatically correct and execute mistyped commands after + waiting for the given number of deciseconds (0.1 sec). If more + than one command can be deduced from the entered text, nothing + will be executed. If the value of this option is negative, + the corrected command will be executed immediately. If the + value is 0 - the command will be just shown but not executed. + This is the default. + http.proxy:: Override the HTTP proxy, normally configured using the 'http_proxy' environment variable (see linkgit:curl[1]). This can be overridden diff --git a/help.c b/help.c index b1ebca4091..74499bf840 100644 --- a/help.c +++ b/help.c @@ -268,6 +268,16 @@ int is_in_cmdlist(struct cmdnames *c, const char *s) return 0; } +static int autocorrect; + +static int git_unknown_cmd_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "help.autocorrect")) + autocorrect = git_config_int(var,value); + + return git_default_config(var, value, cb); +} + static int levenshtein_compare(const void *p1, const void *p2) { const struct cmdname *const *c1 = p1, *const *c2 = p2; @@ -285,6 +295,8 @@ const char *help_unknown_cmd(const char *cmd) memset(&main_cmds, 0, sizeof(main_cmds)); memset(&other_cmds, 0, sizeof(main_cmds)); + git_config(git_unknown_cmd_config, NULL); + load_command_list("git-", &main_cmds, &other_cmds); ALLOC_GROW(main_cmds.names, main_cmds.cnt + other_cmds.cnt, @@ -309,7 +321,7 @@ const char *help_unknown_cmd(const char *cmd) n = 1; while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len) ++n; - if (n == 1) { + if (autocorrect && n == 1) { const char *assumed = main_cmds.names[0]->name; main_cmds.names[0] = NULL; clean_cmdnames(&main_cmds); @@ -317,6 +329,11 @@ const char *help_unknown_cmd(const char *cmd) "which does not exist.\n" "Continuing under the assumption that you meant '%s'\n", cmd, assumed); + if (autocorrect > 0) { + fprintf(stderr, "in %0.1f seconds automatically...\n", + (float)autocorrect/10.0); + poll(NULL, 0, autocorrect * 100); + } return assumed; } -- cgit v1.2.1 From 394258190c76dd7944688a3a28931071ec02087d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 21 Aug 2008 01:44:53 -0700 Subject: git-add --intent-to-add (-N) This adds "--intent-to-add" option to "git add". This is to let the system know that you will tell it the final contents to be staged later, iow, just be aware of the presense of the path with the type of the blob for now. It is implemented by staging an empty blob as the content. With this sequence: $ git reset --hard $ edit newfile $ git add -N newfile $ edit newfile oldfile $ git diff the diff will show all changes relative to the current commit. Then you can do: $ git commit -a ;# commit everything or $ git commit oldfile ;# only oldfile, newfile not yet added to pretend you are working with an index-free system like CVS. Signed-off-by: Junio C Hamano --- builtin-add.c | 4 +++- cache.h | 2 ++ read-cache.c | 29 ++++++++++++++++++++++++----- t/t2203-add-intent.sh | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100755 t/t2203-add-intent.sh diff --git a/builtin-add.c b/builtin-add.c index 7c874e3115..ea4e77169a 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -166,7 +166,7 @@ static const char ignore_error[] = "The following paths are ignored by one of your .gitignore files:\n"; static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0; -static int ignore_add_errors, addremove; +static int ignore_add_errors, addremove, intent_to_add; static struct option builtin_add_options[] = { OPT__DRY_RUN(&show_only), @@ -176,6 +176,7 @@ static struct option builtin_add_options[] = { OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), + OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), OPT_BOOLEAN('A', "all", &addremove, "add all, noticing removal of tracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), @@ -246,6 +247,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | (show_only ? ADD_CACHE_PRETEND : 0) | + (intent_to_add ? ADD_CACHE_INTENT : 0) | (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | (!(addremove || take_worktree_changes) ? ADD_CACHE_IGNORE_REMOVAL : 0)); diff --git a/cache.h b/cache.h index de8c2b6266..f725783b80 100644 --- a/cache.h +++ b/cache.h @@ -371,6 +371,7 @@ extern int index_name_pos(const struct index_state *, const char *name, int name #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ #define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ +#define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option); extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); @@ -380,6 +381,7 @@ extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_PRETEND 2 #define ADD_CACHE_IGNORE_ERRORS 4 #define ADD_CACHE_IGNORE_REMOVAL 8 +#define ADD_CACHE_INTENT 16 extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); diff --git a/read-cache.c b/read-cache.c index 5150c1e14b..276b09e66f 100644 --- a/read-cache.c +++ b/read-cache.c @@ -13,6 +13,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "blob.h" /* Index extensions. * @@ -511,6 +512,14 @@ static struct cache_entry *create_alias_ce(struct cache_entry *ce, struct cache_ return new; } +static void record_intent_to_add(struct cache_entry *ce) +{ + unsigned char sha1[20]; + if (write_sha1_file("", 0, blob_type, sha1)) + die("cannot create an empty blob in the object database"); + hashcpy(ce->sha1, sha1); +} + int add_to_index(struct index_state *istate, const char *path, struct stat *st, int flags) { int size, namelen, was_same; @@ -519,6 +528,9 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY; int verbose = flags & (ADD_CACHE_VERBOSE | ADD_CACHE_PRETEND); int pretend = flags & ADD_CACHE_PRETEND; + int intent_only = flags & ADD_CACHE_INTENT; + int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE| + (intent_only ? ADD_CACHE_NEW_ONLY : 0)); if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode)) return error("%s: can only add regular files, symbolic links or git-directories", path); @@ -532,7 +544,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, ce = xcalloc(1, size); memcpy(ce->name, path, namelen); ce->ce_flags = namelen; - fill_stat_cache_info(ce, st); + if (!intent_only) + fill_stat_cache_info(ce, st); if (trust_executable_bit && has_symlinks) ce->ce_mode = create_ce_mode(st_mode); @@ -555,8 +568,12 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, alias->ce_flags |= CE_ADDED; return 0; } - if (index_path(ce->sha1, path, st, 1)) - return error("unable to index file %s", path); + if (!intent_only) { + if (index_path(ce->sha1, path, st, 1)) + return error("unable to index file %s", path); + } else + record_intent_to_add(ce); + if (ignore_case && alias && different_name(ce, alias)) ce = create_alias_ce(ce, alias); ce->ce_flags |= CE_ADDED; @@ -569,7 +586,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, if (pretend) ; - else if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE)) + else if (add_index_entry(istate, ce, add_option)) return error("unable to add %s to index",path); if (verbose && !was_same) printf("add '%s'\n", path); @@ -848,13 +865,15 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int ok_to_add = option & ADD_CACHE_OK_TO_ADD; int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE; int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; + int new_only = option & ADD_CACHE_NEW_ONLY; cache_tree_invalidate_path(istate->cache_tree, ce->name); pos = index_name_pos(istate, ce->name, ce->ce_flags); /* existing match? Just replace it. */ if (pos >= 0) { - replace_index_entry(istate, pos, ce); + if (!new_only) + replace_index_entry(istate, pos, ce); return 0; } pos = -pos-1; diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh new file mode 100755 index 0000000000..d4de35ea06 --- /dev/null +++ b/t/t2203-add-intent.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='Intent to add' + +. ./test-lib.sh + +test_expect_success 'intent to add' ' + echo hello >file && + echo hello >elif && + git add -N file && + git add elif +' + +test_expect_success 'check result of "add -N"' ' + git ls-files -s file >actual && + empty=$(git hash-object --stdin expect && + test_cmp expect actual +' + +test_expect_success 'intent to add is just an ordinary empty blob' ' + git add -u && + git ls-files -s file >actual && + git ls-files -s elif | sed -e "s/elif/file/" >expect && + test_cmp expect actual +' + +test_expect_success 'intent to add does not clobber existing paths' ' + git add -N file elif && + empty=$(git hash-object --stdin actual && + ! grep "$empty" actual +' + +test_done + -- cgit v1.2.1 From e5b5c1d2cf0416b0e597c4b974b0efbd1da54267 Mon Sep 17 00:00:00 2001 From: Gustaf Hendeby Date: Sun, 31 Aug 2008 18:00:27 +0200 Subject: Document clarification: gitmodules, gitattributes The SYNOPSIS section of gitattibutes and gitmodule fail to clearly specify the name of the in tree files used. This patch brings in the initial `.' and the fact that the `.gitmodules' file should reside at the top-level of the working tree. Signed-off-by: Gustaf Hendeby Signed-off-by: Junio C Hamano --- Documentation/gitattributes.txt | 2 +- Documentation/gitmodules.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 1993887937..49a167f241 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -7,7 +7,7 @@ gitattributes - defining attributes per path SYNOPSIS -------- -$GIT_DIR/info/attributes, gitattributes +$GIT_DIR/info/attributes, .gitattributes DESCRIPTION diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index f8d122a8b9..d1a17e2625 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -7,7 +7,7 @@ gitmodules - defining submodule properties SYNOPSIS -------- -gitmodules +$GIT_WORK_DIR/.gitmodules DESCRIPTION -- cgit v1.2.1 From 14877436871a1e5e38deb64abb884b480ff02567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ask=20Bj=C3=B8rn=20Hansen?= Date: Sun, 31 Aug 2008 13:32:43 -0700 Subject: Document sendemail.envelopesender configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ask Bjørn Hansen Signed-off-by: Junio C Hamano --- Documentation/git-send-email.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index e2437f30ca..3c3e1b0e77 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -179,6 +179,9 @@ user is prompted for a password while the input is masked for privacy. This is useful if your default address is not the address that is subscribed to a list. If you use the sendmail binary, you must have suitable privileges for the -f parameter. + Default is the value of the 'sendemail.envelopesender' configuration + variable; if that is unspecified, choosing the envelope sender is left + to your MTA. --to:: Specify the primary recipient of the emails generated. -- cgit v1.2.1 From 9d13dec5499ec31a93dd22db9b0c971133a10613 Mon Sep 17 00:00:00 2001 From: Thomas Rast Date: Mon, 1 Sep 2008 00:31:37 +0200 Subject: t6013: replace use of 'tac' with equivalent Perl 'tac' is not available everywhere, so substitute the equivalent Perl code 'print reverse <>'. Noticed by Brian Gernhardt. Signed-off-by: Thomas Rast Signed-off-by: Junio C Hamano --- t/t6013-rev-list-reverse-parents.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh index d294466427..59fc2f06e0 100755 --- a/t/t6013-rev-list-reverse-parents.sh +++ b/t/t6013-rev-list-reverse-parents.sh @@ -25,7 +25,7 @@ test_expect_success 'set up --reverse example' ' test_expect_success '--reverse --parents --full-history combines correctly' ' git rev-list --parents --full-history master -- foo | - tac > expected && + perl -e "print reverse <>" > expected && git rev-list --reverse --parents --full-history master -- foo \ > actual && test_cmp actual expected @@ -33,7 +33,7 @@ test_expect_success '--reverse --parents --full-history combines correctly' ' test_expect_success '--boundary does too' ' git rev-list --boundary --parents --full-history master ^root -- foo | - tac > expected && + perl -e "print reverse <>" > expected && git rev-list --boundary --reverse --parents --full-history \ master ^root -- foo > actual && test_cmp actual expected -- cgit v1.2.1 From 85e72830697a23dd6b1af8b6bfb3c1a7be60dfae Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Sun, 31 Aug 2008 14:09:39 +0200 Subject: cast pid_t's to uintmax_t to improve portability Some systems (like e.g. OpenSolaris) define pid_t as long, therefore all our sprintf that use %i/%d cause a compiler warning beacuse of the implicit long->int cast. To make sure that we fit the limits, we display pids as PRIuMAX and cast them explicitly to uintmax_t. Signed-off-by: David Soria Parra Signed-off-by: Junio C Hamano --- builtin-commit.c | 2 +- builtin-fetch-pack.c | 2 +- daemon.c | 6 +++--- fast-import.c | 6 +++--- receive-pack.c | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/builtin-commit.c b/builtin-commit.c index c870037b07..b75d5e931d 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -320,7 +320,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) die("unable to write new_index file"); fd = hold_lock_file_for_update(&false_lock, - git_path("next-index-%d", getpid()), 1); + git_path("next-index-%"PRIuMAX, (uintmax_t) getpid()), 1); create_base_index(); add_remove_files(&partial); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 273239af3b..17a5a422c2 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -540,7 +540,7 @@ static int get_pack(int xd[2], char **pack_lockfile) *av++ = "--fix-thin"; if (args.lock_pack || unpack_limit) { int s = sprintf(keep_arg, - "--keep=fetch-pack %d on ", getpid()); + "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) strcpy(keep_arg + s, "localhost"); *av++ = keep_arg; diff --git a/daemon.c b/daemon.c index 23278e28dc..c315932ced 100644 --- a/daemon.c +++ b/daemon.c @@ -86,7 +86,7 @@ static void logreport(int priority, const char *err, va_list params) * Since stderr is set to linebuffered mode, the * logging of different processes will not overlap */ - fprintf(stderr, "[%d] ", (int)getpid()); + fprintf(stderr, "[%"PRIuMAX"] ", (uintmax_t)getpid()); vfprintf(stderr, err, params); fputc('\n', stderr); } @@ -658,7 +658,7 @@ static void check_dead_children(void) remove_child(pid); if (!WIFEXITED(status) || (WEXITSTATUS(status) > 0)) dead = " (with error)"; - loginfo("[%d] Disconnected%s", (int)pid, dead); + loginfo("[%"PRIuMAX"] Disconnected%s", (uintmax_t)pid, dead); } } @@ -923,7 +923,7 @@ static void store_pid(const char *path) FILE *f = fopen(path, "w"); if (!f) die("cannot open pid file %s: %s", path, strerror(errno)); - if (fprintf(f, "%d\n", getpid()) < 0 || fclose(f) != 0) + if (fprintf(f, "%"PRIuMAX"\n", (uintmax_t) getpid()) < 0 || fclose(f) != 0) die("failed to write pid file %s: %s", path, strerror(errno)); } diff --git a/fast-import.c b/fast-import.c index 7089e6f9e6..acb8e2e360 100644 --- a/fast-import.c +++ b/fast-import.c @@ -376,7 +376,7 @@ static void dump_marks_helper(FILE *, uintmax_t, struct mark_set *); static void write_crash_report(const char *err) { - char *loc = git_path("fast_import_crash_%d", getpid()); + char *loc = git_path("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); FILE *rpt = fopen(loc, "w"); struct branch *b; unsigned long lu; @@ -390,8 +390,8 @@ static void write_crash_report(const char *err) fprintf(stderr, "fast-import: dumping crash report to %s\n", loc); fprintf(rpt, "fast-import crash report:\n"); - fprintf(rpt, " fast-import process: %d\n", getpid()); - fprintf(rpt, " parent process : %d\n", getppid()); + fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid()); + fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid()); fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_LOCAL)); fputc('\n', rpt); diff --git a/receive-pack.c b/receive-pack.c index d44c19e6b5..b81678a970 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -407,7 +407,7 @@ static const char *unpack(void) char keep_arg[256]; struct child_process ip; - s = sprintf(keep_arg, "--keep=receive-pack %i on ", getpid()); + s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid()); if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) strcpy(keep_arg + s, "localhost"); -- cgit v1.2.1 From eac5a401512181cd315a1031af2b8a25430e335a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 31 Aug 2008 19:32:40 -0700 Subject: checkout --conflict=