From 533b70390e540de4e0faed4823ee561c8368e5ec Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 12 Jan 2007 12:52:03 -0800 Subject: [PATCH 1/9] Allow whole-tree operations to be started from a subdirectory This updates five commands (merge, pull, rebase, revert and cherry-pick) so that they can be started from a subdirectory. This may not actually be what we want to do. These commands are inherently whole-tree operations, and an inexperienced user may mistakenly expect a "git pull" from a subdirectory would merge only the subdirectory the command started from. Signed-off-by: Junio C Hamano --- git-merge.sh | 4 +++- git-pull.sh | 4 +++- git-rebase.sh | 3 +++ git-revert.sh | 3 +++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/git-merge.sh b/git-merge.sh index 3eef048efc..7de83dc76c 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -5,12 +5,14 @@ USAGE='[-n] [--no-commit] [--squash] [-s ] [-m=] +' +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action "merge $*" require_work_tree +cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in a middle of conflicted merge." + die "You are in the middle of a conflicted merge." LF=' ' diff --git a/git-pull.sh b/git-pull.sh index e9826fc4ce..959261757c 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -6,12 +6,14 @@ USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [] ...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action "pull $*" require_work_tree +cd_to_toplevel test -z "$(git ls-files -u)" || - die "You are in a middle of conflicted merge." + die "You are in the middle of a conflicted merge." strategy_args= no_summary= no_commit= squash= while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac diff --git a/git-rebase.sh b/git-rebase.sh index 98f9558145..c8bd0f99d1 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -27,9 +27,12 @@ Example: git-rebase master~1 topic / --> / D---E---F---G master D---E---F---G master ' + +SUBDIRECTORY_OK=Yes . git-sh-setup set_reflog_action rebase require_work_tree +cd_to_toplevel RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". diff --git a/git-revert.sh b/git-revert.sh index fcca3ebb90..224e6540ca 100755 --- a/git-revert.sh +++ b/git-revert.sh @@ -19,8 +19,11 @@ case "$0" in echo >&2 "What are you talking about?" exit 1 ;; esac + +SUBDIRECTORY_OK=Yes ;# we will cd up . git-sh-setup require_work_tree +cd_to_toplevel no_commit= while case "$#" in 0) break ;; esac From dcbc7bbe393d6bfee0ac36cc97fbc51ebf112ed6 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Sat, 13 Jan 2007 21:23:55 -0500 Subject: [PATCH 2/9] simplify the "no changes added to commit" message Suggesting the use of [-a|-i|-o] with git-commit is unnecessarily complex and confusing. In this context -o is totally useless and -i requires extra arguments which are not mentioned. The only sensible hint (besides reading the man page but let's not go there) is "commit -a". Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- wt-status.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wt-status.c b/wt-status.c index daba9a6105..b7250e4310 100644 --- a/wt-status.c +++ b/wt-status.c @@ -335,7 +335,7 @@ void wt_status_print(struct wt_status *s) if (s->amend) printf("# No changes\n"); else if (s->workdir_dirty) - printf("no changes added to commit (use \"git add\" and/or \"git commit [-a|-i|-o]\")\n"); + printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); else if (s->workdir_untracked) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) From 38434f2eed45f42ed706d07564079c23ee686511 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 03:22:47 -0500 Subject: [PATCH 3/9] Hide output about SVN::Core not being found during tests. If the user doesn't have SVN::Core installed or working then the SVN tests properly turn themselves off. But the user doesn't need to know that SVN::Core isn't loadable as a Perl module. Unless of course they are trying to debug the test, so lets relegate the Perl failures to --verbose only. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/lib-git-svn.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index a0f2814083..bb1d7b84bc 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -26,7 +26,7 @@ use SVN::Core; use SVN::Repos; \$SVN::Core::VERSION gt '1.1.0' or exit(42); system(qw/svnadmin create --fs-type fsfs/, '$svnrepo') == 0 or exit(41); -" +" >&3 2>&4 x=$? if test $x -ne 0 then From e6e2bd6201d32342df7a713c847161ab296885ea Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 01:01:49 -0500 Subject: [PATCH 4/9] Remove read_or_die in favor of better error messages. Originally I introduced read_or_die for the purpose of reading the pack header and trailer, and I was too lazy to print proper error messages. Linus Torvalds : > For a read error, at the very least you have to say WHICH FILE > couldn't be read, because it's usually a matter of some file just > being too short, not some system-wide problem. and of course Linus is right. Make it so. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- cache.h | 1 - sha1_file.c | 6 ++++-- write_or_die.c | 12 ------------ 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/cache.h b/cache.h index c482c32a03..620b6a4ed4 100644 --- a/cache.h +++ b/cache.h @@ -434,7 +434,6 @@ extern char *git_log_output_encoding; extern int copy_fd(int ifd, int ofd); extern int read_in_full(int fd, void *buf, size_t count); -extern void read_or_die(int fd, void *buf, size_t count); extern int write_in_full(int fd, const void *buf, size_t count); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); diff --git a/sha1_file.c b/sha1_file.c index 2a5be53fac..1b1c0f7b4d 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -572,7 +572,8 @@ static void open_packed_git(struct packed_git *p) die("cannot set FD_CLOEXEC"); /* Verify we recognize this pack file format. */ - read_or_die(p->pack_fd, &hdr, sizeof(hdr)); + if (read_in_full(p->pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) + die("file %s is far too short to be a packfile", p->pack_name); if (hdr.hdr_signature != htonl(PACK_SIGNATURE)) die("file %s is not a GIT packfile", p->pack_name); if (!pack_version_ok(hdr.hdr_version)) @@ -588,7 +589,8 @@ static void open_packed_git(struct packed_git *p) num_packed_objects(p)); if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1) die("end of packfile %s is unavailable", p->pack_name); - read_or_die(p->pack_fd, sha1, sizeof(sha1)); + if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1)) + die("packfile %s signature is unavailable", p->pack_name); idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40; if (hashcmp(sha1, idx_sha1)) die("packfile %s does not match index", p->pack_name); diff --git a/write_or_die.c b/write_or_die.c index 4e8183e93e..046e79d485 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -17,18 +17,6 @@ int read_in_full(int fd, void *buf, size_t count) return total; } -void read_or_die(int fd, void *buf, size_t count) -{ - ssize_t loaded; - - loaded = read_in_full(fd, buf, count); - if (loaded != count) { - if (loaded < 0) - die("read error (%s)", strerror(errno)); - die("read error: end of file"); - } -} - int write_in_full(int fd, const void *buf, size_t count) { const char *p = buf; From 63889639bbb47399b5ede784b1afe48679d52bb2 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:33 -0500 Subject: [PATCH 5/9] Remove unnecessary call_depth parameter in merge-recursive. Because the output_indent always matches the call_depth value there is no reason to pass around the call_depth to the merge function during each recursive invocation. This is a simple refactoring that will make the code easier to follow later on as I start to add output verbosity controls. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index b4acbb7408..8738f090c3 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -70,13 +70,13 @@ struct stage_data static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; -static int output_indent = 0; +static int call_depth = 0; static void output(const char *fmt, ...) { va_list args; int i; - for (i = output_indent; i--;) + for (i = call_depth; i--;) fputs(" ", stdout); va_start(args, fmt); vfprintf(stdout, fmt, args); @@ -87,7 +87,7 @@ static void output(const char *fmt, ...) static void output_commit_title(struct commit *commit) { int i; - for (i = output_indent; i--;) + for (i = call_depth; i--;) fputs(" ", stdout); if (commit->util) printf("virtual %s\n", (char *)commit->util); @@ -1095,7 +1095,6 @@ static int merge(struct commit *h1, struct commit *h2, const char *branch1, const char *branch2, - int call_depth /* =0 */, struct commit_list *ca, struct commit **result) { @@ -1129,7 +1128,7 @@ static int merge(struct commit *h1, } for (iter = ca; iter; iter = iter->next) { - output_indent = call_depth + 1; + call_depth++; /* * When the merge fails, the result contains files * with conflict markers. The cleanness flag is @@ -1141,17 +1140,16 @@ static int merge(struct commit *h1, merge(merged_common_ancestors, iter->item, "Temporary merge branch 1", "Temporary merge branch 2", - call_depth + 1, NULL, &merged_common_ancestors); - output_indent = call_depth; + call_depth--; if (!merged_common_ancestors) die("merge returned no commit"); } discard_cache(); - if (call_depth == 0) { + if (!call_depth) { read_cache(); index_only = 0; } else @@ -1239,7 +1237,7 @@ int main(int argc, char *argv[]) struct commit *ancestor = get_ref(bases[i]); ca = commit_list_insert(ancestor, &ca); } - clean = merge(h1, h2, branch1, branch2, 0, ca, &result); + clean = merge(h1, h2, branch1, branch2, ca, &result); if (active_cache_changed && (write_cache(index_fd, active_cache, active_nr) || From 8c3275abcacb83ea3f4c4f4ceb2376799fc282bd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:48 -0500 Subject: [PATCH 6/9] Allow the user to control the verbosity of merge-recursive. Junio C Hamano writes: > > I think the output from merge-recursive can be categorized into 5 > verbosity levels: > > 1. "CONFLICT", "Rename", "Adding here instead due to D/F conflict" > (outermost) > > 2. "Auto-merged successfully" (outermost) > > 3. The first "Merging X with Y". > > 4. outermost "Merging:\ntitle1\ntitle2". > > 5. outermost "found N common ancestors\nancestor1\nancestor2\n..." > and anything from inner merge. > > I would prefer the default verbosity level to be 2 (that is, show > both 1 and 2). and this change makes it so. I think level 3 is probably pointless as its only one line of output above level 2, but I can see how some users may want to view it but not view the slightly more verbose output of level 4. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- Documentation/config.txt | 7 +++ merge-recursive.c | 100 ++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index f7dba8977f..faa17ba848 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -321,6 +321,13 @@ merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. +merge.verbosity:: + Controls the amount of output shown by the recursive merge + strategy. Level 0 outputs nothing except a final error + message if conflicts were detected. Level 1 outputs only + conflicts, 2 outputs conflicts and file changes. Level 5 and + above outputs debugging information. The default is level 2. + pack.window:: The size of the window used by gitlink:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. diff --git a/merge-recursive.c b/merge-recursive.c index 8738f090c3..ef9932a68c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -71,17 +71,25 @@ static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; +static int verbosity = 2; -static void output(const char *fmt, ...) +static int show (int v) +{ + return (!call_depth && verbosity >= v) || verbosity >= 5; +} + +static void output(int v, const char *fmt, ...) { va_list args; - int i; - for (i = call_depth; i--;) - fputs(" ", stdout); va_start(args, fmt); - vfprintf(stdout, fmt, args); + if (show(v)) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + vfprintf(stdout, fmt, args); + fputc('\n', stdout); + } va_end(args); - fputc('\n', stdout); } static void output_commit_title(struct commit *commit) @@ -640,13 +648,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (path_list_has_path(¤t_directory_set, ren1_dst)) { dst_name1 = del[delp++] = unique_path(ren1_dst, branch1); - output("%s is a directory in %s adding 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 (path_list_has_path(¤t_directory_set, ren2_dst)) { dst_name2 = del[delp++] = unique_path(ren2_dst, branch2); - output("%s is a directory in %s adding 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); } @@ -660,7 +668,7 @@ static void conflict_rename_dir(struct rename *ren1, const char *branch1) { char *new_path = unique_path(ren1->pair->two->path, branch1); - output("Renaming %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); @@ -673,7 +681,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("Renaming %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); @@ -766,7 +774,7 @@ static int process_renames(struct path_list *a_renames, ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { clean_merge = 0; - output("CONFLICT (rename/rename): " + output(1, "CONFLICT (rename/rename): " "Rename %s->%s in branch %s " "rename %s->%s in %s", src, ren1_dst, branch1, @@ -781,13 +789,13 @@ static int process_renames(struct path_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output("Renaming %s->%s", src, ren1_dst); + output(1, "Renaming %s->%s", src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (content): merge conflict in %s", + output(1, "CONFLICT (content): merge conflict in %s", ren1_dst); clean_merge = 0; @@ -818,14 +826,14 @@ static int process_renames(struct path_list *a_renames, if (path_list_has_path(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output("CONFLICT (rename/directory): Rename %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("CONFLICT (rename/delete): Rename %s->%s in %s " + output(1, "CONFLICT (rename/delete): Rename %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -834,18 +842,18 @@ static int process_renames(struct path_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output("CONFLICT (rename/add): Rename %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("Adding 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 = path_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; ren2->processed = 1; - output("CONFLICT (rename/rename): Rename %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); @@ -870,11 +878,11 @@ static int process_renames(struct path_list *a_renames, a_branch, b_branch); if (mfi.merge || !mfi.clean) - output("Renaming %s => %s", ren1_src, ren1_dst); + output(1, "Renaming %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output("Auto-merging %s", ren1_dst); + output(2, "Auto-merging %s", ren1_dst); if (!mfi.clean) { - output("CONFLICT (rename/modify): Merge conflict in %s", + output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); clean_merge = 0; @@ -922,20 +930,20 @@ 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("Removing %s", path); + 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("CONFLICT (delete/modify): %s deleted in %s " + 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("CONFLICT (delete/modify): %s deleted in %s " + 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); @@ -968,13 +976,13 @@ static int process_entry(const char *path, struct stage_data *entry, if (path_list_has_path(¤t_directory_set, path)) { const char *new_path = unique_path(path, add_branch); clean_merge = 0; - output("CONFLICT (%s): There is a directory with name %s in %s. " + 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("Adding %s", path); + output(2, "Adding %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -988,7 +996,7 @@ static int process_entry(const char *path, struct stage_data *entry, reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output("Auto-merging %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; @@ -1004,7 +1012,7 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (%s): Merge conflict in %s", + output(1, "CONFLICT (%s): Merge conflict in %s", reason, path); if (index_only) @@ -1028,7 +1036,7 @@ static int merge_trees(struct tree *head, { int code, clean; if (sha_eq(common->object.sha1, merge->object.sha1)) { - output("Already uptodate!"); + output(0, "Already uptodate!"); *result = head; return 1; } @@ -1103,18 +1111,22 @@ static int merge(struct commit *h1, struct tree *mrtree; int clean; - output("Merging:"); - output_commit_title(h1); - output_commit_title(h2); + 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); } - output("found %u common ancestor(s):", commit_list_count(ca)); - for (iter = ca; iter; iter = iter->next) - output_commit_title(iter->item); + 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) { @@ -1196,6 +1208,15 @@ static struct commit *get_ref(const char *ref) return (struct commit *)object; } +static int merge_config(const char *var, const char *value) +{ + if (!strcasecmp(var, "merge.verbosity")) { + verbosity = git_config_int(var, value); + return 0; + } + return git_default_config(var, value); +} + int main(int argc, char *argv[]) { static const char *bases[20]; @@ -1207,7 +1228,9 @@ int main(int argc, char *argv[]) struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); int index_fd; - git_config(git_default_config); /* core.filemode */ + git_config(merge_config); + if (getenv("GIT_MERGE_VERBOSITY")) + verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10); if (argc < 4) die("Usage: %s ... -- ...\n", argv[0]); @@ -1229,7 +1252,8 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - printf("Merging %s with %s\n", branch1, branch2); + if (show(3)) + printf("Merging %s with %s\n", branch1, branch2); index_fd = hold_lock_file_for_update(lock, get_index_file(), 1); From 66a155bc129b12f1f13be8e3f20e57db5ace0e6f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:53 -0500 Subject: [PATCH 7/9] Enable output buffering in merge-recursive. Buffering all message output until a merge invocation is complete is necessary to prevent intereferring with a progress meter that would indicate the number of files completely merged, and how many remain. This change does not introduce a progress meter, but merely lays the groundwork to buffer the output. To aid debugging output buffering is only enabled if verbosity is lower than 5. When using verbosity levels above 5 the user is probably debugging the merge program itself and does not want to see the output delayed, especially if they are stepping through portions of the code in a debugger. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/merge-recursive.c b/merge-recursive.c index ef9932a68c..9237a57f8e 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -67,11 +67,19 @@ struct stage_data unsigned processed:1; }; +struct output_buffer +{ + struct output_buffer *next; + char *str; +}; + static struct path_list current_file_set = {NULL, 0, 0, 1}; static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; +static int buffer_output = 1; +static struct output_buffer *output_list, *output_end; static int show (int v) { @@ -82,7 +90,16 @@ static void output(int v, const char *fmt, ...) { va_list args; va_start(args, fmt); - if (show(v)) { + if (buffer_output && show(v)) { + struct output_buffer *b = xmalloc(sizeof(*b)); + nfvasprintf(&b->str, fmt, args); + b->next = NULL; + if (output_end) + output_end->next = b; + else + output_list = b; + output_end = b; + } else if (show(v)) { int i; for (i = call_depth; i--;) fputs(" ", stdout); @@ -92,9 +109,27 @@ static void output(int v, const char *fmt, ...) va_end(args); } +static void flush_output() +{ + struct output_buffer *b, *n; + for (b = output_list; b; b = n) { + int i; + for (i = call_depth; i--;) + fputs(" ", stdout); + fputs(b->str, stdout); + fputc('\n', stdout); + n = b->next; + free(b->str); + free(b); + } + output_list = NULL; + output_end = NULL; +} + static void output_commit_title(struct commit *commit) { int i; + flush_output(); for (i = call_depth; i--;) fputs(" ", stdout); if (commit->util) @@ -1175,6 +1210,7 @@ static int merge(struct commit *h1, commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); } + flush_output(); return clean; } @@ -1252,6 +1288,8 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); + if (verbosity >= 5) + buffer_output = 0; if (show(3)) printf("Merging %s with %s\n", branch1, branch2); From 3f6ee2d15ab4be8690c17c0af0338b8495f6f706 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 00:28:58 -0500 Subject: [PATCH 8/9] Display a progress meter during merge-recursive. Because large merges on slow systems can take up to a minute to execute we should try to keep the user entertained with a progress meter to let them know how far we have progressed through the current merge. The progress meter considers each entry in the in-memory index to be a unit, which means a single recursive merge will double the number of units in the progress meter. Files which are unmerged after the 3-way tree merge are also considered a unit within the progress meter. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 73 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 9237a57f8e..966d8e987f 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -79,6 +79,11 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1}; static int call_depth = 0; static int verbosity = 2; static int buffer_output = 1; +static int do_progress = 1; +static unsigned last_percent; +static unsigned merged_cnt; +static unsigned total_cnt; +static volatile sig_atomic_t progress_update; static struct output_buffer *output_list, *output_end; static int show (int v) @@ -153,6 +158,39 @@ static void output_commit_title(struct commit *commit) } } +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static void display_progress() +{ + unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0; + if (progress_update || percent != last_percent) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, merged_cnt, total_cnt); + progress_update = 0; + last_percent = percent; + } +} + static struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh) { @@ -315,11 +353,14 @@ static struct path_list *get_unmerged(void) int i; unmerged->strdup_paths = 1; + total_cnt += active_nr; - for (i = 0; i < active_nr; i++) { + for (i = 0; i < active_nr; i++, merged_cnt++) { struct path_list_item *item; struct stage_data *e; struct cache_entry *ce = active_cache[i]; + if (do_progress) + display_progress(); if (!ce_stage(ce)) continue; @@ -1096,13 +1137,15 @@ static int merge_trees(struct tree *head, 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++) { + total_cnt += entries->nr; + for (i = 0; i < entries->nr; i++, merged_cnt++) { const char *path = entries->items[i].path; struct stage_data *e = entries->items[i].util; - if (e->processed) - continue; - if (!process_entry(path, e, branch1, branch2)) + if (!e->processed + && !process_entry(path, e, branch1, branch2)) clean = 0; + if (do_progress) + display_progress(); } path_list_clear(re_merge, 0); @@ -1210,6 +1253,15 @@ static int merge(struct commit *h1, commit_list_insert(h1, &(*result)->parents); commit_list_insert(h2, &(*result)->parents->next); } + if (!call_depth && do_progress) { + /* Make sure we end at 100% */ + if (!total_cnt) + total_cnt = 1; + merged_cnt = total_cnt; + progress_update = 1; + display_progress(); + fputc('\n', stderr); + } flush_output(); return clean; } @@ -1279,6 +1331,12 @@ int main(int argc, char *argv[]) } if (argc - i != 3) /* "--" "" "" */ die("Not handling anything other than two heads merge."); + if (verbosity >= 5) { + buffer_output = 0; + do_progress = 0; + } + else + do_progress = isatty(1); branch1 = argv[++i]; branch2 = argv[++i]; @@ -1288,8 +1346,9 @@ int main(int argc, char *argv[]) branch1 = better_branch_name(branch1); branch2 = better_branch_name(branch2); - if (verbosity >= 5) - buffer_output = 0; + + if (do_progress) + setup_progress_signal(); if (show(3)) printf("Merging %s with %s\n", branch1, branch2); From 89f40be294363ce4d14ed6931a65561a4e8e9140 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sun, 14 Jan 2007 03:11:28 -0500 Subject: [PATCH 9/9] Convert output messages in merge-recursive to past tense. Now that we are showing the output messages for verbosity levels <5 after all actions have been performed (due to the progress meter running during the actions) it can be confusing to see messages in the present tense when the user is looking at a '100% done' message right above them. Converting the messages to past tense will appear more correct in this case, and shouldn't affect a developer who is debugging the application and running it at a verbosity level >=5. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- merge-recursive.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 966d8e987f..fa320eb6b1 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -724,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1, const char *dst_name2 = ren2_dst; if (path_list_has_path(¤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(1, "%s is a directory in %s added as %s instead", ren1_dst, branch2, dst_name1); remove_file(0, ren1_dst, 0); } if (path_list_has_path(¤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(1, "%s is a directory in %s added as %s instead", ren2_dst, branch1, dst_name2); remove_file(0, ren2_dst, 0); } @@ -744,7 +744,7 @@ 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); + output(1, "Renamed %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); @@ -757,7 +757,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, "Renaming %s to %s and %s to %s instead", + output(1, "Renamed %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); @@ -865,10 +865,10 @@ static int process_renames(struct path_list *a_renames, branch1, branch2); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s->%s", src, ren1_dst); + output(1, "Renamed %s->%s", src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (content): merge conflict in %s", @@ -902,14 +902,14 @@ static int process_renames(struct path_list *a_renames, if (path_list_has_path(¤t_directory_set, ren1_dst)) { clean_merge = 0; - output(1, "CONFLICT (rename/directory): Rename %s->%s in %s " + output(1, "CONFLICT (rename/directory): Renamed %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 " + output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s " "and deleted in %s", ren1_src, ren1_dst, branch1, branch2); @@ -918,19 +918,19 @@ static int process_renames(struct path_list *a_renames, const char *new_path; clean_merge = 0; try_merge = 1; - output(1, "CONFLICT (rename/add): Rename %s->%s in %s. " + output(1, "CONFLICT (rename/add): Renamed %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(1, "Added as %s instead", new_path); update_file(0, dst_other.sha1, dst_other.mode, new_path); } else if ((item = path_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", + output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. " + "Renamed %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); @@ -954,9 +954,9 @@ static int process_renames(struct path_list *a_renames, a_branch, b_branch); if (mfi.merge || !mfi.clean) - output(1, "Renaming %s => %s", ren1_src, ren1_dst); + output(1, "Renamed %s => %s", ren1_src, ren1_dst); if (mfi.merge) - output(2, "Auto-merging %s", ren1_dst); + output(2, "Auto-merged %s", ren1_dst); if (!mfi.clean) { output(1, "CONFLICT (rename/modify): Merge conflict in %s", ren1_dst); @@ -1006,7 +1006,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, "Removing %s", path); + output(2, "Removed %s", path); /* do not touch working file if it did not exist */ remove_file(1, path, !a_sha); } else { @@ -1053,12 +1053,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. " - "Adding %s as %s", + "Added %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(2, "Added %s", path); update_file(1, sha, mode, path); } } else if (a_sha && b_sha) { @@ -1072,7 +1072,7 @@ static int process_entry(const char *path, struct stage_data *entry, reason = "add/add"; o_sha = (unsigned char *)null_sha1; } - output(2, "Auto-merging %s", path); + output(2, "Auto-merged %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); o.mode = o_mode;