From ccb365047a1081455b767867f0887e7b4334f9d8 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 19 Apr 2006 10:05:12 -0700 Subject: [PATCH 1/9] Allow "git repack" users to specify repacking window/depth .. but don't even bother documenting it. I don't think any normal person is supposed to ever really care, but it simplifies testing when you want to use the "git repack" wrapper rather than forcing you to use the core programs (which already do support the window/depth arguments, of course). Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git-repack.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-repack.sh b/git-repack.sh index a5d349fd09..e0c9f323c3 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -5,9 +5,9 @@ USAGE='[-a] [-d] [-f] [-l] [-n] [-q]' . git-sh-setup - + no_update_info= all_into_one= remove_redundant= -local= quiet= no_reuse_delta= +local= quiet= no_reuse_delta= extra= while case "$#" in 0) break ;; esac do case "$1" in @@ -17,6 +17,8 @@ do -q) quiet=-q ;; -f) no_reuse_delta=--no-reuse-delta ;; -l) local=--local ;; + --window=*) extra="$extra $1" ;; + --depth=*) extra="$extra $1" ;; *) usage ;; esac shift @@ -40,7 +42,7 @@ case ",$all_into_one," in find . -type f \( -name '*.pack' -o -name '*.idx' \) -print` ;; esac -pack_objects="$pack_objects $local $quiet $no_reuse_delta" +pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra" name=$(git-rev-list --objects --all $rev_list 2>&1 | git-pack-objects --non-empty $pack_objects .tmp-pack) || exit 1 From 1aec7917dc52901c6df301ddc8fea70f5ce0db09 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Wed, 19 Apr 2006 10:20:49 -0700 Subject: [PATCH 2/9] git log: don't do merge diffs by default I personally prefer "ignore_merges" to be on by default, because quite often the merge diff is distracting and not interesting. That's true both with "-p" and with "--stat" output. If you want output from merges, you can trivially use the "-m", "-c" or "--cc" flags to tell that you're interested in merges, which also tells the diff generator what kind of diff to do (for --stat, any of the three will do, of course, but they differ for plain patches or for --patch-with-stat). This trivial patch just removes the two lines that tells "git log" not to ignore merges. It will still show the commit log message, of course, due to the "always_show_header" part. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- git.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/git.c b/git.c index 0be14bb487..40b7e42ae9 100644 --- a/git.c +++ b/git.c @@ -331,8 +331,6 @@ static int cmd_log(int argc, const char **argv, char **envp) init_revisions(&rev); rev.always_show_header = 1; rev.diffopt.recursive = 1; - rev.combine_merges = 1; - rev.ignore_merges = 0; return cmd_log_wc(argc, argv, envp, &rev); } From 041a7308de3e6af36c5a6cc3412b542f42314f3f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 11:56:07 -0700 Subject: [PATCH 3/9] sha1_name.c: prepare to make get_tree_entry() reusable from others. Signed-off-by: Junio C Hamano --- sha1_name.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 0cd1139e06..35e8dfb9c7 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -450,18 +450,17 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) return get_short_sha1(name, len, sha1, 0); } -static int get_tree_entry(const unsigned char *, const char *, unsigned char *); +static int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); -static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result) +static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) { int namelen = strlen(name); while (t->size) { const char *entry; const unsigned char *sha1; int entrylen, cmp; - unsigned mode; - sha1 = tree_entry_extract(t, &entry, &mode); + sha1 = tree_entry_extract(t, &entry, mode); update_tree_entry(t); entrylen = strlen(entry); if (entrylen > namelen) @@ -477,18 +476,18 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char } if (name[entrylen] != '/') continue; - if (!S_ISDIR(mode)) + if (!S_ISDIR(*mode)) break; if (++entrylen == namelen) { memcpy(result, sha1, 20); return 0; } - return get_tree_entry(sha1, name + entrylen, result); + return get_tree_entry(sha1, name + entrylen, result, mode); } return -1; } -static int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1) +static int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode) { int retval; void *tree; @@ -498,7 +497,7 @@ static int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsi if (!tree) return -1; t.buf = tree; - retval = find_tree_entry(&t, name, sha1); + retval = find_tree_entry(&t, name, sha1, mode); free(tree); return retval; } @@ -510,6 +509,7 @@ static int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsi int get_sha1(const char *name, unsigned char *sha1) { int ret; + unsigned unused; prepare_alt_odb(); ret = get_sha1_1(name, strlen(name), sha1); @@ -518,7 +518,8 @@ int get_sha1(const char *name, unsigned char *sha1) if (cp) { unsigned char tree_sha1[20]; if (!get_sha1_1(name, cp-name, tree_sha1)) - return get_tree_entry(tree_sha1, cp+1, sha1); + return get_tree_entry(tree_sha1, cp+1, sha1, + &unused); } } return ret; From f3ab49db1b88b753fc628d7b241f20f73950e162 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 11:56:53 -0700 Subject: [PATCH 4/9] sha1_name.c: no need to include diff.h; tree-walk.h will do. Signed-off-by: Junio C Hamano --- sha1_name.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 35e8dfb9c7..7ad20b59f9 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -3,7 +3,7 @@ #include "commit.h" #include "tree.h" #include "blob.h" -#include "diff.h" +#include "tree-walk.h" static int find_short_object_filename(int len, const char *name, unsigned char *sha1) { From 4dcff634e6294bcd7a40c8a51762b675b35be237 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 14:05:47 -0700 Subject: [PATCH 5/9] get_tree_entry(): make it available from tree-walk Signed-off-by: Junio C Hamano --- sha1_name.c | 52 ---------------------------------------------------- tree-walk.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ tree-walk.h | 2 ++ 3 files changed, 52 insertions(+), 52 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 7ad20b59f9..345935bb2b 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -450,58 +450,6 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1) return get_short_sha1(name, len, sha1, 0); } -static int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); - -static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) -{ - int namelen = strlen(name); - while (t->size) { - const char *entry; - const unsigned char *sha1; - int entrylen, cmp; - - sha1 = tree_entry_extract(t, &entry, mode); - update_tree_entry(t); - entrylen = strlen(entry); - if (entrylen > namelen) - continue; - cmp = memcmp(name, entry, entrylen); - if (cmp > 0) - continue; - if (cmp < 0) - break; - if (entrylen == namelen) { - memcpy(result, sha1, 20); - return 0; - } - if (name[entrylen] != '/') - continue; - if (!S_ISDIR(*mode)) - break; - if (++entrylen == namelen) { - memcpy(result, sha1, 20); - return 0; - } - return get_tree_entry(sha1, name + entrylen, result, mode); - } - return -1; -} - -static int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode) -{ - int retval; - void *tree; - struct tree_desc t; - - tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL); - if (!tree) - return -1; - t.buf = tree; - retval = find_tree_entry(&t, name, sha1, mode); - free(tree); - return retval; -} - /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" diff --git a/tree-walk.c b/tree-walk.c index bf8bfdfdf8..9f7abb7cb3 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -115,3 +115,53 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb free(entry); } +static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode) +{ + int namelen = strlen(name); + while (t->size) { + const char *entry; + const unsigned char *sha1; + int entrylen, cmp; + + sha1 = tree_entry_extract(t, &entry, mode); + update_tree_entry(t); + entrylen = strlen(entry); + if (entrylen > namelen) + continue; + cmp = memcmp(name, entry, entrylen); + if (cmp > 0) + continue; + if (cmp < 0) + break; + if (entrylen == namelen) { + memcpy(result, sha1, 20); + return 0; + } + if (name[entrylen] != '/') + continue; + if (!S_ISDIR(*mode)) + break; + if (++entrylen == namelen) { + memcpy(result, sha1, 20); + return 0; + } + return get_tree_entry(sha1, name + entrylen, result, mode); + } + return -1; +} + +int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode) +{ + int retval; + void *tree; + struct tree_desc t; + + tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL); + if (!tree) + return -1; + t.buf = tree; + retval = find_tree_entry(&t, name, sha1, mode); + free(tree); + return retval; +} + diff --git a/tree-walk.h b/tree-walk.h index 76893e36c3..47438fe1c0 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -22,4 +22,6 @@ typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback); +int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *); + #endif From 50ac7408018209a2829b7948119270fec7e14ec8 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 14:54:27 -0700 Subject: [PATCH 6/9] git-merge: a bit more readable user guidance. We said "fix up by hand" after failed automerge, which was a big "Huh? Now what?". Be a bit more explicit without being too verbose. Suggested by Carl Worth. Signed-off-by: Junio C Hamano --- git-merge.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-merge.sh b/git-merge.sh index 78ab422e4e..b834e79c98 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -335,5 +335,5 @@ Conflicts: then git-rerere fi - die "Automatic merge failed; fix up by hand" + die "Automatic merge failed; fix conflicts and then commit the result." fi From 61c2bcbd11e3b66a328b3850c01592e5dc1c67bb Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 14:58:24 -0700 Subject: [PATCH 7/9] pre-commit hook: complain about conflict markers. Several <<< or === or >>> characters at the beginning of a line is very likely to be leftover conflict markers from a failed automerge the user resolved incorrectly, so detect them. As usual, this can be defeated with "git commit --no-verify" if you really do want to have those files, just like changes that introduce trailing whitespaces. Signed-off-by: Junio C Hamano --- templates/hooks--pre-commit | 3 +++ 1 file changed, 3 insertions(+) diff --git a/templates/hooks--pre-commit b/templates/hooks--pre-commit index 43d3b6ef4a..723a9ef210 100644 --- a/templates/hooks--pre-commit +++ b/templates/hooks--pre-commit @@ -61,6 +61,9 @@ perl -e ' if (/^\s* /) { bad_line("indent SP followed by a TAB", $_); } + if (/^(?:[<>=]){7}/) { + bad_line("unresolved merge conflict", $_); + } } } exit($found_bad); From ba580aeafb52921025de1efe1c50db34393f9907 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 15:12:02 -0700 Subject: [PATCH 8/9] diff: move diff.c to diff-lib.c to make room. Now I am not doing any real "git-diff in C" yet, but this would help before doing so. Signed-off-by: Junio C Hamano --- Makefile | 2 +- diff.c => diff-lib.c | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename diff.c => diff-lib.c (100%) diff --git a/Makefile b/Makefile index 8aed3af016..3ecd674c14 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ LIB_H = \ tree-walk.h log-tree.h DIFF_OBJS = \ - diff.o diffcore-break.o diffcore-order.o \ + diff-lib.o diffcore-break.o diffcore-order.o \ diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ diffcore-delta.o log-tree.o diff --git a/diff.c b/diff-lib.c similarity index 100% rename from diff.c rename to diff-lib.c From ec167793d84ba7b765e1eb71b0257ce7baca2d26 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 19 Apr 2006 12:51:29 -0700 Subject: [PATCH 9/9] Add git-unresolve ... This is an attempt to address the issue raised on #git channel recently by Carl Worth. After a conflicted automerge, "git diff" shows a combined diff to give you how the tentative automerge result differs from what came from each branch. During a complex merge, it is tempting to be able to resolve a few paths at a time, mark them "I've dealt with them" with git-update-index to unclutter the next "git diff" output, and keep going. However, when the final result does not compile or otherwise found to be a mismerge, the workflow to fix the mismerged paths suddenly changes to "git diff HEAD -- path" (to get a diff from our HEAD before merging) and "git diff MERGE_HEAD -- path" (to get a diff from theirs), and it cannot show the combined anymore. With git-unresolve ..., the versions from our branch and their branch for specified blobs are placed in stage #2 and stage #3, without touching the working tree files. This gives you the combined diff back for easier review, along with "diff --ours" and "diff --theirs". One thing it does not do is to place the base in stage #1; this means "diff --base" would behave differently between the run immediately after a conflicted three-way merge, and the run after an update-index by mistake followed by a git-unresolve. We could theoretically run merge-base between HEAD and MERGE_HEAD to find which tree to place in stage #1, but reviewing "diff --base" is not that useful so.... Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 3 +- unresolve.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 unresolve.c diff --git a/.gitignore b/.gitignore index b5959d6311..1e4ba7b209 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,7 @@ git-tag git-tar-tree git-unpack-file git-unpack-objects +git-unresolve git-update-index git-update-ref git-update-server-info diff --git a/Makefile b/Makefile index 3ecd674c14..51dcce3aa3 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,8 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X + git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ + git-unresolve$X BUILT_INS = git-log$X diff --git a/unresolve.c b/unresolve.c new file mode 100644 index 0000000000..0b23b9bc75 --- /dev/null +++ b/unresolve.c @@ -0,0 +1,146 @@ +#include "cache.h" +#include "tree-walk.h" + +static const char unresolve_usage[] = +"git-unresolve ..."; + +static struct cache_file cache_file; +static unsigned char head_sha1[20]; +static unsigned char merge_head_sha1[20]; + +static struct cache_entry *read_one_ent(const char *which, + unsigned char *ent, const char *path, + int namelen, int stage) +{ + unsigned mode; + unsigned char sha1[20]; + int size; + struct cache_entry *ce; + + if (get_tree_entry(ent, path, sha1, &mode)) { + error("%s: not in %s branch.", path, which); + return NULL; + } + if (mode == S_IFDIR) { + error("%s: not a blob in %s branch.", path, which); + return NULL; + } + size = cache_entry_size(namelen); + ce = xcalloc(1, size); + + memcpy(ce->sha1, sha1, 20); + memcpy(ce->name, path, namelen); + ce->ce_flags = create_ce_flags(namelen, stage); + ce->ce_mode = create_ce_mode(mode); + return ce; +} + +static int unresolve_one(const char *path) +{ + int namelen = strlen(path); + int pos; + int ret = 0; + struct cache_entry *ce_2 = NULL, *ce_3 = NULL; + + /* See if there is such entry in the index. */ + pos = cache_name_pos(path, namelen); + if (pos < 0) { + /* If there isn't, either it is unmerged, or + * resolved as "removed" by mistake. We do not + * want to do anything in the former case. + */ + pos = -pos-1; + if (pos < active_nr) { + struct cache_entry *ce = active_cache[pos]; + if (ce_namelen(ce) == namelen && + !memcmp(ce->name, path, namelen)) { + fprintf(stderr, + "%s: skipping still unmerged path.\n", + path); + goto free_return; + } + } + } + + /* Grab blobs from given path from HEAD and MERGE_HEAD, + * stuff HEAD version in stage #2, + * stuff MERGE_HEAD version in stage #3. + */ + ce_2 = read_one_ent("our", head_sha1, path, namelen, 2); + ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3); + + if (!ce_2 || !ce_3) { + ret = -1; + goto free_return; + } + if (!memcmp(ce_2->sha1, ce_3->sha1, 20) && + ce_2->ce_mode == ce_3->ce_mode) { + fprintf(stderr, "%s: identical in both, skipping.\n", + path); + goto free_return; + } + + remove_file_from_cache(path); + if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { + error("%s: cannot add our version to the index.", path); + ret = -1; + goto free_return; + } + if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD)) + return 0; + error("%s: cannot add their version to the index.", path); + ret = -1; + free_return: + free(ce_2); + free(ce_3); + return ret; +} + +static void read_head_pointers(void) +{ + if (read_ref(git_path("HEAD"), head_sha1)) + die("Cannot read HEAD -- no initial commit yet?"); + if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { + fprintf(stderr, "Not in the middle of a merge.\n"); + exit(0); + } +} + +int main(int ac, char **av) +{ + int i; + int err = 0; + int newfd; + + if (ac < 2) + usage(unresolve_usage); + + git_config(git_default_config); + + /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we + * are not doing a merge, so exit with success status. + */ + read_head_pointers(); + + /* Otherwise we would need to update the cache. */ + newfd= hold_index_file_for_update(&cache_file, get_index_file()); + if (newfd < 0) + die("unable to create new cachefile"); + + if (read_cache() < 0) + die("cache corrupted"); + + for (i = 1; i < ac; i++) { + char *arg = av[i]; + err |= unresolve_one(arg); + } + if (err) + die("Error encountered; index not updated."); + + if (active_cache_changed) { + if (write_cache(newfd, active_cache, active_nr) || + commit_index_file(&cache_file)) + die("Unable to write new cachefile"); + } + return 0; +}