git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH] builtin-commit: fix "git add x y && git commit y" committing x, too

From
Junio C Hamano <gitster@pobox.com>
Date
Nov 17, 2007, 08:45 UTC
Message-ID
<7vk5ohuunv.fsf@gitster.siamese.dyndns.org>
In-Reply-To
<Pine.LNX.4.64.0711160036450.30886@racer.site>
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> It's not only about discarding the cache.  It's also about avoiding do 
> regenerate the index completely; this would waste time, especially for big 
> trees.

I was looking at this code earlier tonight but I am too tired so here are a few comments before I stop.

> But the code you are referencing is only updating the index.  The code I 
> added is to build the temporary index in a correct manner.

Yes, except that it is only in the partial commit codepath and there is not much point optimizing it, as there are more to it.

If a path that was not in the HEAD was added to the index earlier, and the path was named on the command line, the add_files_to_index() function you are borrowing from the implementation of "add -u" would not notice it. Look at the script version of git-commit.sh and look for places near "ls-files --error-unmatch --with-tree=HEAD".

I _think_ we need to do the equivalent of this, keep the affected paths in a path-list and use add_file_to_cache() instead. We need to feed the same set of paths to update the index twice (once for the fake one for partial commit, and another for the real index to be used after the commit is made), and (1) using add_files_to_index() is more expensive than walking a path-list, and (2) add_files_to_index() is a wrong thing to use anyway (by definition you cannot notice addition when you are comparing the index and the work tree, so I think your patch to update_callback() is a no-op).

I noticed that the implementation left next-index crufts almost every time it was run, and started to clean it up. Here is still a WIP and it does not optimize the read_tree(HEAD) part, but you should be able to replace that part with your one-way merge easily. As I haven't done that ls-files --error-unmatch equivalent, this does not pass tests that involve partial commits with added or removed paths.

---
 builtin-commit.c |  174 +++++++++++++++++++++++++++++++++++++++++++-----------
 1 files changed, 139 insertions(+), 35 deletions(-)
diff --git a/builtin-commit.c b/builtin-commit.c
index 3e7d281..187d613 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -7,6 +7,7 @@
 
 #include "cache.h"
 #include "cache-tree.h"
+#include "dir.h"
 #include "builtin.h"
 #include "diff.h"
 #include "diffcore.h"
@@ -28,7 +29,13 @@ static const char * const builtin_commit_usage[] = {
 static unsigned char head_sha1[20], merge_head_sha1[20];
 static char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
-static struct lock_file lock_file;
+static struct lock_file index_lock; /* real index */
+static struct lock_file false_lock; /* used only for partial commits */
+static enum {
+	COMMIT_AS_IS = 1,
+	COMMIT_NORMAL,
+	COMMIT_PARTIAL,
+} commit_style;
 
 static char *logfile, *force_author, *template_file;
 static char *edit_message, *use_message;
@@ -78,41 +85,122 @@ static struct option builtin_commit_options[] = {
 	OPT_END()
 };
 
+static void rollback_index_files(void)
+{
+	switch (commit_style) {
+	case COMMIT_AS_IS:
+		break; /* nothing to do */
+	case COMMIT_NORMAL:
+		rollback_lock_file(&index_lock);
+		break;
+	case COMMIT_PARTIAL:
+		rollback_lock_file(&index_lock);
+		rollback_lock_file(&false_lock);
+		break;
+	}
+}
+
+static void commit_index_files(void)
+{
+	switch (commit_style) {
+	case COMMIT_AS_IS:
+		break; /* nothing to do */
+	case COMMIT_NORMAL:
+		commit_lock_file(&index_lock);
+		break;
+	case COMMIT_PARTIAL:
+		commit_lock_file(&index_lock);
+		rollback_lock_file(&false_lock);
+		break;
+	}
+}
+
 static char *prepare_index(const char **files, const char *prefix)
 {
 	int fd;
 	struct tree *tree;
-	struct lock_file *next_index_lock;
 
 	if (interactive) {
 		interactive_add();
 		return get_index_file();
 	}
 
-	fd = hold_locked_index(&lock_file, 1);
 	if (read_cache() < 0)
 		die("index file corrupt");
 
+	/*
+	 * Non partial, non as-is commit.
+	 *
+	 * (1) get the real index;
+	 * (2) update the_index as necessary;
+	 * (3) write the_index out to the real index (still locked);
+	 * (4) return the name of the locked index file.
+	 *
+	 * The caller should run hooks on the locked real index, and
+	 * (A) if all goes well, commit the real index;
+	 * (B) on failure, rollback the real index.
+	 */
 	if (all || also) {
+		fd = hold_locked_index(&index_lock, 1);
 		add_files_to_cache(verbose, also ? prefix : NULL, files);
 		refresh_cache(REFRESH_QUIET);
 		if (write_cache(fd, active_cache, active_nr) || close(fd))
 			die("unable to write new_index file");
-		return lock_file.filename;
+		commit_style = COMMIT_NORMAL;
+		return index_lock.filename;
 	}
 
+	/*
+	 * As-is commit.
+	 *
+	 * (1) return the name of the real index file.
+	 *
+	 * The caller should run hooks on the real index, and run
+	 * hooks on the real index, and create commit from the_index.
+	 * No lockfile is needed.
+	 */
 	if (*files == NULL) {
-		/* Commit index as-is. */
-		rollback_lock_file(&lock_file);
+		fd = hold_locked_index(&index_lock, 1);
+		refresh_cache(REFRESH_QUIET);
+		if (write_cache(fd, active_cache, active_nr) ||
+		    close(fd) || commit_locked_index(&index_lock))
+			die("unable to write new_index file");
+		commit_style = COMMIT_AS_IS;
 		return get_index_file();
 	}
 
-	/* update the user index file */
+	/*
+	 * A partial commit.
+	 *
+	 * (0) find the set of affected paths [NEEDSWORK: NOT DONE YET]
+	 * (1) get lock on the real index file;
+	 * (2) update the_index with the given paths;
+	 * (3) write the_index out to the real index (still locked);
+	 * (4) get lock on the false index file;
+	 * (5) reset the_index from HEAD, but keep the addition;
+	 * (6) update the_index the same way as (2);
+	 * (7) write the_index out to the false index file;
+	 * (8) return the name of the false index file (still locked);
+	 *
+	 * The caller should run hooks on the locked false index, and
+	 * (A) if all goes well, commit the real index;
+	 * (B) on failure, rollback the real index;
+	 * In either case, rollback the false index.
+	 */
+	commit_style = COMMIT_PARTIAL;
+
+	if (file_exists(git_path("MERGE_HEAD")))
+		die("cannot do a partial commit during a merge.");
+
+	fd = hold_locked_index(&index_lock, 1);
 	add_files_to_cache(verbose, prefix, files);
 	refresh_cache(REFRESH_QUIET);
 	if (write_cache(fd, active_cache, active_nr) || close(fd))
 		die("unable to write new_index file");
 
+	fd = hold_lock_file_for_update(&false_lock,
+				       git_path("next-index-%d", getpid()), 1);
+	discard_cache();
 	if (!initial_commit) {
 		tree = parse_tree_indirect(head_sha1);
 		if (!tree)
@@ -120,17 +208,12 @@ static char *prepare_index(const char **files, const char *prefix)
 		if (read_tree(tree, 0, NULL))
 			die("failed to read HEAD tree object");
 	}
-
-	/* Use a lock file to garbage collect the temporary index file. */
-	next_index_lock = xmalloc(sizeof(*next_index_lock));
-	fd = hold_lock_file_for_update(next_index_lock,
-				       git_path("next-index-%d", getpid()), 1);
 	add_files_to_cache(verbose, prefix, files);
 	refresh_cache(REFRESH_QUIET);
-	if (write_cache(fd, active_cache, active_nr) || close(fd))
-		die("unable to write new_index file");
 
-	return next_index_lock->filename;
+	if (write_cache(fd, active_cache, active_nr) || close(fd))
+		die("unable to write temporary index file");
+	return false_lock.filename;
 }
 
 static int run_status(FILE *fp, const char *index_file, const char *prefix)
@@ -437,7 +520,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
 
 	commitable = run_status(stdout, index_file, prefix);
 
-	rollback_lock_file(&lock_file);
+	rollback_index_files();
 
 	return commitable ? 0 : 1;
 }
@@ -527,23 +610,36 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
 	index_file = prepare_index(argv, prefix);
 
-	if (!no_verify && run_hook(index_file, "pre-commit", NULL))
-		exit(1);
+	if (!no_verify && run_hook(index_file, "pre-commit", NULL)) {
+		rollback_index_files();
+		return 1;
+	}
 
 	if (!prepare_log_message(index_file, prefix) && !in_merge) {
 		run_status(stdout, index_file, prefix);
+		rollback_index_files();
 		unlink(commit_editmsg);
 		return 1;
 	}
 
-	strbuf_init(&sb, 0);
-
-	/* Start building up the commit header */
+	/*
+	 * Re-read the index as pre-commit hook could have updated it,
+	 * and write it out as a tree.
+	 */
+	discard_cache();
 	read_cache_from(index_file);
-	active_cache_tree = cache_tree();
+	if (!active_cache_tree)
+		active_cache_tree = cache_tree();
 	if (cache_tree_update(active_cache_tree,
-			      active_cache, active_nr, 0, 0) < 0)
+			      active_cache, active_nr, 0, 0) < 0) {
+		rollback_index_files();
 		die("Error building trees");
+	}
+
+	/*
+	 * The commit object
+	 */
+	strbuf_init(&sb, 0);
 	strbuf_addf(&sb, "tree %s\n",
 		    sha1_to_hex(active_cache_tree->sha1));
 
@@ -592,20 +688,27 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	header_len = sb.len;
 	if (!no_edit)
 		launch_editor(git_path(commit_editmsg), &sb);
-	else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0)
+	else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) {
+		rollback_index_files();
 		die("could not read commit message\n");
-	if (run_hook(index_file, "commit-msg", commit_editmsg))
+	}
+	if (run_hook(index_file, "commit-msg", commit_editmsg)) {
+		rollback_index_files();
 		exit(1);
+	}
 	stripspace(&sb, 1);
-	if (sb.len < header_len ||
-	    message_is_empty(&sb, header_len))
+	if (sb.len < header_len || message_is_empty(&sb, header_len)) {
+		rollback_index_files();
 		die("* no commit message?  aborting commit.");
+	}
 	strbuf_addch(&sb, '\0');
 	if (is_encoding_utf8(git_commit_encoding) && !is_utf8(sb.buf))
 		fprintf(stderr, commit_utf8_warn);
 
-	if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1))
+	if (write_sha1_file(sb.buf, sb.len - 1, commit_type, commit_sha1)) {
+		rollback_index_files();
 		die("failed to write commit object");
+	}
 
 	ref_lock = lock_any_ref_for_update("HEAD",
 					   initial_commit ? NULL : head_sha1,
@@ -620,21 +723,22 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
 	strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
 
-	if (!ref_lock)
+	if (!ref_lock) {
+		rollback_index_files();
 		die("cannot lock HEAD ref");
-	if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0)
+	}
+	if (write_ref_sha1(ref_lock, commit_sha1, sb.buf) < 0) {
+		rollback_index_files();
 		die("cannot update HEAD ref");
+	}
 
 	unlink(git_path("MERGE_HEAD"));
 	unlink(git_path("MERGE_MSG"));
 
-	if (lock_file.filename[0] && commit_locked_index(&lock_file))
-		die("failed to write new index");
+	commit_index_files();
 
 	rerere();
-
-	run_hook(index_file, "post-commit", NULL);
-
+	run_hook(get_index_file(), "post-commit", NULL);
 	if (!quiet)
 		print_summary(prefix, commit_sha1);
 
Previous: Johannes SchindelinNext: Junio C Hamano
Message 89 of 168 in “What's cooking in git/spearce.git (topics)”
  1. Shawn O. PearceOct 22, 2007
  2. Jeff KingOct 22, 2007
  3. Jeff KingOct 22, 2007
  4. Linus TorvaldsOct 23, 2007
  5. Jeff KingOct 23, 2007
  6. Pierre HabouzitOct 22, 2007
  7. Steffen ProhaskaOct 22, 2007
  8. Junio C HamanoOct 23, 2007
  9. Shawn O. PearceOct 23, 2007
  10. What's cooking in git.git (topics)Junio C Hamano, Oct 24, 2007
  11. David SymondsOct 24, 2007
  12. Scott ParishOct 24, 2007
  13. Andreas EricssonOct 24, 2007
  14. Scott ParishOct 25, 2007
  15. What's cooking in git.git (topics)Junio C Hamano, Nov 1, 2007
  16. Jakub NarebskiNov 1, 2007
  17. Junio C HamanoNov 1, 2007
  18. Linus TorvaldsNov 1, 2007
  19. Geert BoschNov 1, 2007
  20. Junio C HamanoNov 1, 2007
  21. Mike HommeyNov 1, 2007
  22. Junio C HamanoNov 1, 2007
  23. Junio C HamanoNov 2, 2007
  24. Pierre HabouzitNov 1, 2007
  25. Geert BoschNov 1, 2007
  26. Jonas FonsecaNov 2, 2007
  27. Theodore TsoNov 1, 2007
  28. Melchior FRANZNov 1, 2007
  29. Johan HerlandNov 1, 2007
  30. Junio C HamanoNov 1, 2007
  31. Linus TorvaldsNov 1, 2007
  32. Bill LearNov 1, 2007
  33. Junio C HamanoNov 1, 2007
  34. Petr BaudisNov 2, 2007
  35. Pierre HabouzitNov 1, 2007
  36. Andreas EricssonNov 2, 2007
  37. Pierre HabouzitNov 1, 2007
  38. Jakub NarebskiNov 2, 2007
  39. Petr BaudisNov 2, 2007
  40. Jakub NarebskiNov 2, 2007
  41. Jakub NarebskiNov 2, 2007
  42. Pierre HabouzitNov 2, 2007
  43. Miles BaderNov 2, 2007
  44. Miles BaderNov 2, 2007
  45. Andreas EricssonNov 2, 2007
  46. Johannes SchindelinNov 2, 2007
  47. Brian DowningNov 1, 2007
  48. Pierre HabouzitNov 1, 2007
  49. Wincent ColaiutaNov 2, 2007
  50. What's cooking in git.git (topics)Junio C Hamano, Nov 4, 2007
  51. Jakub NarebskiNov 4, 2007
  52. Pierre HabouzitNov 4, 2007
  53. What's cooking in git.git (topics)Junio C Hamano, Nov 8, 2007
  54. Steffen ProhaskaNov 8, 2007
  55. What's cooking in git.git (topics)Junio C Hamano, Nov 12, 2007
  56. Johannes SchindelinNov 12, 2007
  57. Pierre HabouzitNov 12, 2007
  58. Johannes SchindelinNov 12, 2007
  59. rebase: brown paper bag fix after the detached HEAD patchJohannes Schindelin, Nov 12, 2007
  60. Pierre HabouzitNov 12, 2007
  61. Steffen ProhaskaNov 12, 2007
  62. Johannes SchindelinNov 12, 2007
  63. 1/2 push: Add '--matching' option and print warning if it should be usedSteffen Prohaska, Nov 18, 2007
  64. 2/2 push: Add '--current', which pushes only the current branchSteffen Prohaska, Nov 18, 2007
  65. Junio C HamanoNov 19, 2007
  66. Steffen ProhaskaNov 19, 2007
  67. Junio C HamanoNov 19, 2007
  68. Junio C HamanoNov 19, 2007
  69. Andreas EricssonNov 19, 2007
  70. Steffen ProhaskaNov 19, 2007
  71. Junio C HamanoNov 19, 2007
  72. Steffen ProhaskaNov 19, 2007
  73. push: Add "--current", which pushes only the current branchSteffen Prohaska, Nov 19, 2007
  74. Jakub NarebskiNov 19, 2007
  75. Junio C HamanoNov 19, 2007
  76. Jakub NarebskiNov 19, 2007
  77. Junio C HamanoNov 19, 2007
  78. Jakub NarebskiNov 19, 2007
  79. Andreas EricssonNov 19, 2007
  80. git-commit: Add tests for invalid usage of -a/--interactive with pathsBjörn Steinbrink, Nov 12, 2007
  81. What's cooking in git.git (topics)Junio C Hamano, Nov 15, 2007
  82. Johannes SchindelinNov 15, 2007
  83. t7501-commit: Add test for git commit <file> with dirty index.Kristian Høgsberg, Nov 15, 2007
  84. Johannes SchindelinNov 15, 2007
  85. builtin-commit: fix "git add x y && git commit y" committing x, tooJohannes Schindelin, Nov 15, 2007
  86. Johannes SchindelinNov 15, 2007
  87. Kristian HøgsbergNov 15, 2007
  88. Johannes SchindelinNov 16, 2007
  89. Junio C HamanoNov 17, 2007
  90. Junio C HamanoNov 18, 2007
  91. Jeff KingNov 17, 2007
  92. What's cooking in git.git (topics)Junio C Hamano, Nov 17, 2007
  93. Alex RiesenNov 17, 2007
  94. Junio C HamanoNov 18, 2007
  95. What's cooking in git.git (topics)Junio C Hamano, Nov 21, 2007
  96. What's cooking in git.git (topics)Junio C Hamano, Nov 23, 2007
  97. Jeff KingNov 23, 2007
  98. Johannes SchindelinNov 23, 2007
  99. Jeff KingNov 24, 2007
  100. Nicolas PitreNov 24, 2007
  101. Junio C HamanoNov 24, 2007
  102. J. Bruce FieldsNov 25, 2007
  103. Junio C HamanoNov 25, 2007
  104. J. Bruce FieldsNov 25, 2007
  105. Nicolas PitreNov 26, 2007
  106. J. Bruce FieldsNov 26, 2007
  107. Nicolas PitreNov 26, 2007
  108. J. Bruce FieldsNov 26, 2007
  109. Jakub NarebskiNov 26, 2007
  110. Andreas EricssonNov 26, 2007
  111. Nicolas PitreNov 26, 2007
  112. David KastrupNov 26, 2007
  113. Nicolas PitreNov 26, 2007
  114. Junio C HamanoNov 26, 2007
  115. David KastrupNov 26, 2007
  116. Nicolas PitreNov 26, 2007
  117. David KastrupNov 26, 2007
  118. Nicolas PitreNov 26, 2007
  119. David KastrupNov 26, 2007
  120. Nicolas PitreNov 26, 2007
  121. David KastrupNov 26, 2007
  122. Nicolas PitreNov 27, 2007
  123. Miles BaderDec 5, 2007
  124. Jakub NarebskiNov 26, 2007
  125. Johannes SchindelinNov 26, 2007
  126. Nicolas PitreNov 26, 2007
  127. Jan HudecNov 26, 2007
  128. What's cooking in git.git (topics)Junio C Hamano, Nov 25, 2007
  129. Jakub NarebskiNov 25, 2007
  130. J. Bruce FieldsNov 25, 2007
  131. What's cooking in git.git (topics)Junio C Hamano, Dec 1, 2007
  132. Eric WongDec 1, 2007
  133. Add 'git fast-export', the sister of 'git fast-import'Johannes Schindelin, Dec 2, 2007
  134. Johannes SchindelinDec 2, 2007
  135. What's cooking in git.git (topics)Junio C Hamano, Dec 4, 2007
  136. Johannes SixtDec 4, 2007
  137. msysGit on FAT32 (was: What's cooking in git.git (topics))Jakub Narebski, Dec 4, 2007
  138. Johannes SchindelinDec 4, 2007
  139. Johannes SixtDec 4, 2007
  140. Johannes SchindelinDec 4, 2007
  141. Steffen ProhaskaDec 4, 2007
  142. What's cooking in git.git (topics)Junio C Hamano, Dec 5, 2007
  143. Jakub NarebskiDec 5, 2007
  144. Jakub NarebskiDec 5, 2007
  145. Jeff KingDec 6, 2007
  146. Soft aliases: add "less" and minimal documentationJohannes Schindelin, Dec 5, 2007
  147. Junio C HamanoDec 5, 2007
  148. Jeff KingDec 6, 2007
  149. Jeff KingDec 6, 2007
  150. What's cooking in git.git (topics)Junio C Hamano, Dec 7, 2007
  151. Jakub NarebskiDec 7, 2007
  152. Junio C HamanoDec 7, 2007
  153. Miklos VajnaDec 7, 2007
  154. What's cooking in git.git (topics)Junio C Hamano, Dec 9, 2007
  155. What's cooking in git.git (topics)Junio C Hamano, Dec 13, 2007
  156. Nicolas PitreDec 13, 2007
  157. 1/2 xdl_diff: identify call sites.Junio C Hamano, Dec 13, 2007
  158. Junio C HamanoDec 14, 2007
  159. 2/2 xdi_diff: trim common trailing linesJunio C Hamano, Dec 13, 2007
  160. Peter BaumannDec 14, 2007
  161. Junio C HamanoDec 14, 2007
  162. What's cooking in git.git (topics)Junio C Hamano, Dec 17, 2007
  163. What's cooking in git.git (topics)Junio C Hamano, Dec 23, 2007
  164. checkout --push/--pop idea (Re: What's cooking in git.git (topics))Jan Hudec, Dec 31, 2007
  165. What's cooking in git.git (topics)Junio C Hamano, Jan 5, 2008
  166. Johannes SchindelinJan 5, 2008
  167. What will be cooking in git.git post 1.5.4 (topics)Junio C Hamano, Jan 22, 2008
  168. Brian DowningDec 4, 2007

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.