[PATCH] git object store: make it possible to work with checked-out git branches

Linus Torvalds torvalds at linux-foundation.org
Wed Mar 19 13:41:25 PDT 2014


From: Linus Torvalds <torvalds at linux-foundation.org>
Date: Wed, 19 Mar 2014 13:28:47 -0700
Subject: [PATCH] git object store: make it possible to work with checked-out git branches

This makes the git object save logic also check out the changes in the
working tree and index if the branch we save to is checked out.  It used
to be that we would just update the object store (and the branch ref, of
course), but leave any checked-out state untouched.

Note that if the working directory is dirty (ie you have made changes by
hand and not committed them), the checkout will skip any dirty files and
report it as a warning to the user.  However, the save still succeeds
(since the _real_ save goes to the backing store).

NOTE NOTE NOTE! Both loading and saving very fundamentally work on the
git object store level, and if you are working with a checked-out branch
and make modifications to the working tree, saving will not touch those
dirty files (so that you can try to recover your edits manually in the
working tree), but it's worth pointing out that subsufrace loading state
will totally ignore the working tree.

So the only way to make subsurface *see* your changes is to commit them.
Having edited state checked out in the working tree will only confuse
you when subsurface first ignores it on reading, and then refuses to
touch the checked-out state on writing.

Put another way: working with a checked-out branch is now _possible_,
but you need to be aware of the limitations.

Signed-off-by: Linus Torvalds <torvalds at linux-foundation.org>
---

The whole "work with a checked-out branch" is actually pretty convenient, 
but hopefully the above explanation makes it very clear that working with 
dirty state in that checked-out branch is just going to confuse you. Don't 
do it.

Also note that we now do showError() whether the save fails or succeeds, 
since the error buffer can contain warnings for the user despite the save 
itself being successful.

I'd like to just do that "showError()" in some mainloop place, but I have 
no idea where that would be, so I'm instead randomly sprinkling them 
around places that call things that can result in error messages. Some Qt 
guru might want to clean it up and just have that showError() in one 
single place.

 qt-ui/mainwindow.cpp |  2 ++
 save-git.c           | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index b66ef997abf9..8fd51a5b9d74 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -909,6 +909,7 @@ int MainWindow::file_save_as(void)
 		return -1;
 	}
 
+	showError(get_error_string());
 	set_filename(filename.toUtf8().data(), true);
 	setTitle(MWTF_FILENAME);
 	mark_divelist_changed(false);
@@ -938,6 +939,7 @@ int MainWindow::file_save(void)
 		showError(get_error_string());
 		return -1;
 	}
+	showError(get_error_string());
 	mark_divelist_changed(false);
 	addRecentFile(QStringList() << QString(existing_filename));
 	return 0;
diff --git a/save-git.c b/save-git.c
index 74eab55bb074..a21e1180aba2 100644
--- a/save-git.c
+++ b/save-git.c
@@ -776,6 +776,39 @@ static git_object *try_to_find_parent(const char *hex_id, git_repository *repo)
 	return (git_object *)commit;
 }
 
+static int notify_cb(git_checkout_notify_t why,
+	const char *path,
+	const git_diff_file *baseline,
+	const git_diff_file *target,
+	const git_diff_file *workdir,
+	void *payload)
+{
+fprintf(stderr, "%d %s\n", why, path);
+	report_error("File '%s' does not match in working tree", path);
+	return 0; /* Continue with checkout */
+}
+
+static git_tree *get_git_tree(git_repository *repo, git_object *parent)
+{
+	git_tree *tree;
+	if (!parent)
+		return NULL;
+	if (git_tree_lookup(&tree, repo, git_commit_tree_id((const git_commit *) parent)))
+		return NULL;
+	return tree;
+}
+
+static int update_git_checkout(git_repository *repo, git_object *parent, git_tree *tree)
+{
+	git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT;
+
+	opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+	opts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_DIRTY;
+	opts.notify_cb = notify_cb;
+	opts.baseline = get_git_tree(repo, parent);
+	return git_checkout_tree(repo, (git_object *) tree, &opts);
+}
+
 /*
  * libgit2 revision 0.20 and earlier do not have the signature and
  * message log arguments.
@@ -849,6 +882,22 @@ static int create_new_commit(git_repository *repo, const char *branch, git_oid *
 		if (git_branch_create(&ref, repo, branch, commit, 0, author, "Create branch"))
 			return report_error("Failed to create branch '%s'", branch);
 	}
+	/*
+	 * If it's a checked-out branch, try to also update the working
+	 * tree and index. If that fails (dirty working tree or whatever),
+	 * this is not technically a save error (we did save things in
+	 * the object database), but it can cause extreme confusion, so
+	 * warn about it.
+	 */
+	if (git_branch_is_head(ref) && !git_repository_is_bare(repo)) {
+		if (update_git_checkout(repo, parent, tree)) {
+			const git_error *err = giterr_last();
+			const char *errstr = err ? err->message : strerror(errno);
+			report_error("Git branch '%s' is checked out, but worktree is dirty (%s)",
+				branch, errstr);
+		}
+	}
+
 	if (git_reference_set_target(&ref, ref, &commit_id, author, "Subsurface save event"))
 		return report_error("Failed to update branch '%s'", branch);
 	set_git_id(&commit_id);
-- 
1.9.0



More information about the subsurface mailing list