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

[PATCH v2 17/21] prune: strategies for split repositories

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Dec 14, 2013, 10:55 UTC
Message-ID
<1387018507-21999-18-git-send-email-pclouds@gmail.com>
In-Reply-To
<1387018507-21999-1-git-send-email-pclouds@gmail.com>
alias REPO=$GIT_SUPER_DIR/repos/<id>
 - split repos are supposed to update mtime of $REPO/gitdir
 - split repos are supposed to keep its location in $REPO/gitdir up to
   date
 - "git checkout --to" is supposed to create $REPO/locked if the new
   repo is on a different partition than the shared one.
 - "git checkout --to" is supposed to make hardlink named $REPO/link
   pointing to the .git file
The pruning rules are:
 - if $REPO/locked exists, repos/<id> is not supposed to be pruned.
 - if $REPO/locked exists and $REPO/gitdir's mtimer is older than a
   really long limit, warn about old unused repo.
 - if $REPO/link exists and its link count is 1, repos/<id> is to be
   pruned.
 - if $REPO/gitdir's mtime is older than a limit, and it points to
   nowhere, repos/<id> is to be pruned. Warn about it some time before
   the prune time.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-prune.txt            |  4 +++
 Documentation/gitrepository-layout.txt | 19 ++++++++++
 builtin/checkout.c                     | 36 ++++++++++++++++++-
 builtin/prune.c                        | 65 ++++++++++++++++++++++++++++++++++
 setup.c                                | 13 +++++++
 5 files changed, 136 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt
index bf82410..55a9341 100644
--- a/Documentation/git-prune.txt
+++ b/Documentation/git-prune.txt
@@ -46,6 +46,10 @@ OPTIONS
 --expire <time>::
 	Only expire loose objects older than <time>.
 
+--repos::
+	Prune directories in $GIT_DIR/repos other than `<time>` given
+	by `--expire` (or default 6 months)
+
 <head>...::
 	In addition to objects
 	reachable from any of our references, keep objects
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 3c6149e..a8a0426 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -225,6 +225,25 @@ repos/<id>::
 	repository in question (e.g. HEAD or index). Such .git files
 	are created by `git checkout --to`.
 
+repos/<id>/gitdir::
+	A text file containing the absolute path back to the .git file
+	that points to here. This is used to check if the linked
+	repository has been manually removed and there is no need to
+	keep this directory any more. mtime of this file should be
+	updated every time the linked repository is accessed.
+
+repos/<id>/locked::
+	If this file exists, the linked repository may be on a
+	portable device and not available. It does not mean that the
+	linked repository is gone and `repos/<id>` could be
+	removed. The file's content contains a reason string on why
+	the repository is locked.
+
+repos/<id>/link::
+	If this file exists, it is a hard link to the linked .git
+	file. It is used to detect if the linked repository is
+	manually removed.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 6353557..aece20d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -856,6 +856,17 @@ static void remove_junk_on_signal(int signo)
 	raise(signo);
 }
 
+static dev_t get_device_or_die(const char *path)
+{
+	struct stat buf;
+	if (stat(path, &buf))
+		die_errno("failed to stat '%s'", path);
+	/* Ah Windows! Make different drives different "partitions" */
+	if (buf.st_dev == 0 && has_dos_drive_prefix("c:\\"))
+		buf.st_dev = toupper(real_path(path)[0]);
+	return buf.st_dev;
+}
+
 static int checkout_new_worktree(const struct checkout_opts *opts,
 				 struct branch_info *new)
 {
@@ -865,7 +876,7 @@ static int checkout_new_worktree(const struct checkout_opts *opts,
 	struct stat st;
 	const char *name;
 	struct child_process cp;
-	int counter = 0, len, ret;
+	int counter = 0, len, keep_locked = 0, ret;
 
 	if (!new->commit)
 		die(_("no branch specified"));
@@ -900,17 +911,35 @@ static int checkout_new_worktree(const struct checkout_opts *opts,
 	junk_git_dir = sb_repo.buf;
 	is_junk = 1;
 
+	strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+	write_to_file(sb.buf, "initializing\n");
+
 	strbuf_addf(&sb_git, "%s/.git", path);
 	if (safe_create_leading_directories_const(sb_git.buf))
 		die_errno(_("could not create leading directories of '%s'"),
 			  sb_git.buf);
 	junk_work_tree = path;
 
+	strbuf_reset(&sb);
+	strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
+	write_to_file(sb.buf, "%s\n", real_path(sb_git.buf));
 	write_to_file(sb_git.buf, "gitsuper: %s\ngitdir: %s\n",
 		      real_path(get_git_dir()), name);
+	strbuf_reset(&sb);
 	strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
 	write_to_file(sb.buf, "%s\n", sha1_to_hex(new->commit->object.sha1));
 
+	if (get_device_or_die(path) != get_device_or_die(get_git_dir())) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		write_to_file(sb.buf, "located on a different file system\n");
+		keep_locked = 1;
+	} else {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/link", sb_repo.buf);
+		link(sb_git.buf, sb.buf); /* it's ok to fail */
+	}
+
 	if (!opts->quiet)
 		fprintf_ln(stderr, _("Enter %s (identifier %s)"), path, name);
 
@@ -928,6 +957,11 @@ static int checkout_new_worktree(const struct checkout_opts *opts,
 	ret = run_command(&cp);
 	if (!ret)
 		is_junk = 0;
+	if (!keep_locked) {
+		strbuf_reset(&sb);
+		strbuf_addf(&sb, "%s/locked", sb_repo.buf);
+		unlink_or_warn(sb.buf);
+	}
 	strbuf_release(&sb);
 	strbuf_release(&sb_repo);
 	strbuf_release(&sb_git);
diff --git a/builtin/prune.c b/builtin/prune.c
index 6366917..16eb99c 100644
--- a/builtin/prune.c
+++ b/builtin/prune.c
@@ -102,6 +102,62 @@ static void prune_object_dir(const char *path)
 	}
 }
 
+static const char *prune_repo_dir(const char *id)
+{
+	struct stat st;
+	char *path;
+	int fd, len;
+	if (file_exists(git_path("repos/%s/locked", id)))
+		return NULL;
+	if (!stat(git_path("repos/%s/link", id), &st) && st.st_nlink == 1)
+		return _("hard link count is 1");
+	if (stat(git_path("repos/%s/gitdir", id), &st))
+		return "gitdir does not exist";
+	if (st.st_mtime > expire)
+		return NULL;
+	fd = open(git_path("repos/%s/gitdir", id), O_RDONLY);
+	len = st.st_size;
+	path = xmalloc(len + 1);
+	read_in_full(fd, path, len);
+	close(fd);
+	while (path[len - 1] == '\n' || path[len - 1] == '\r')
+		len--;
+	path[len] = '\0';
+	if (!file_exists(path)) {
+		free(path);
+		return "gitdir points to non-existing file";
+	}
+	free(path);
+	return NULL;
+}
+
+static void prune_repos_dir(void)
+{
+	const char *reason;
+	DIR *dir = opendir(git_path("repos"));
+	struct dirent *d;
+	int removed = 0;
+	if (!dir)
+		return;
+	while ((d = readdir(dir)) != NULL) {
+		if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
+			continue;
+		if ((reason = prune_repo_dir(d->d_name)) != NULL) {
+			struct strbuf sb = STRBUF_INIT;
+			printf(_("Removing repos/%s: %s\n"), d->d_name, reason);
+			if (show_only)
+				continue;
+			strbuf_addstr(&sb, git_path("repos/%s", d->d_name));
+			remove_dir_recursively(&sb, 0);
+			strbuf_release(&sb);
+			removed = 1;
+		}
+	}
+	closedir(dir);
+	if (removed)
+		rmdir(git_path("repos"));
+}
+
 /*
  * Write errors (particularly out of space) can result in
  * failed temporary packs (and more rarely indexes and other
@@ -128,10 +184,12 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 {
 	struct rev_info revs;
 	struct progress *progress = NULL;
+	int prune_repos = 0;
 	const struct option options[] = {
 		OPT__DRY_RUN(&show_only, N_("do not remove, show only")),
 		OPT__VERBOSE(&verbose, N_("report pruned objects")),
 		OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
+		OPT_BOOL(0, "repos", &prune_repos, N_("prune .git/repos/")),
 		OPT_EXPIRY_DATE(0, "expire", &expire,
 				N_("expire objects older than <time>")),
 		OPT_END()
@@ -156,6 +214,13 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
 			die("unrecognized argument: %s", name);
 	}
 
+	if (prune_repos) {
+		if (expire == ULONG_MAX)
+			expire = time(NULL) - 6 * 30 * 24 * 3600; /* 6 months */
+		prune_repos_dir();
+		return 0;
+	}
+
 	if (show_progress == -1)
 		show_progress = isatty(2);
 	if (show_progress)
diff --git a/setup.c b/setup.c
index ac7d90c..95a3f40 100644
--- a/setup.c
+++ b/setup.c
@@ -765,6 +765,17 @@ static const char *setup_git_directory_gently_1(int *nongit_ok,
 	}
 }
 
+static int update_super_gitdir(char *path)
+{
+	FILE *fp = fopen(git_path("gitdir"), "w");
+	if (!fp)
+		return error(_("unable to update %s: %s"),
+			     git_path("gitdir"), strerror(errno));
+	fprintf(stderr, "%s\n", path);
+	fclose(fp);
+	return 0;
+}
+
 const char *setup_git_directory_gently(int *nongit_ok)
 {
 	const char *prefix;
@@ -780,6 +791,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
 		startup_info->have_repository = !nongit_ok || !*nongit_ok;
 		startup_info->prefix = prefix;
 	}
+	if (get_git_super_dir() && gitfile)
+		update_super_gitdir(gitfile);
 	free(gitfile);
 	return prefix;
 }
-- 
1.8.5.1.77.g42c48fa
Previous: Nguyễn Thái Ngọc DuyNext: Nguyễn Thái Ngọc Duy
Message 44 of 49 in “Support multiple worktrees”
  1. 0/7 Support multiple worktreesNguyễn Thái Ngọc Duy, Dec 11, 2013
  2. 1/7 Make git_path() beware of file relocation in $GIT_DIRNguyễn Thái Ngọc Duy, Dec 11, 2013
  3. Junio C HamanoDec 13, 2013
  4. 2/7 Add new environment variable $GIT_SUPER_DIRNguyễn Thái Ngọc Duy, Dec 11, 2013
  5. Junio C HamanoDec 13, 2013
  6. Duy NguyenDec 14, 2013
  7. Junio C HamanoDec 14, 2013
  8. 3/7 setup.c: add split-repo support to .git filesNguyễn Thái Ngọc Duy, Dec 11, 2013
  9. Junio C HamanoDec 13, 2013
  10. Jonathan NiederDec 13, 2013
  11. Duy NguyenDec 14, 2013
  12. Duy NguyenDec 23, 2013
  13. 4/7 setup.c: add split-repo support to is_git_directory()Nguyễn Thái Ngọc Duy, Dec 11, 2013
  14. 5/7 setup.c: reduce cleanup sites in setup_explicit_git_dir()Nguyễn Thái Ngọc Duy, Dec 11, 2013
  15. 6/7 setup.c: add split-repo support to setup_git_directory*Nguyễn Thái Ngọc Duy, Dec 11, 2013
  16. 7/7 init: add --split-repo with the same functionality as git-new-workdirNguyễn Thái Ngọc Duy, Dec 11, 2013
  17. 00/21 Support multiple worktreesNguyễn Thái Ngọc Duy, Dec 14, 2013
  18. 01/21 path.c: avoid PATH_MAX as buffer size from get_pathname()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  19. Torsten BögershausenDec 15, 2013
  20. Duy NguyenDec 15, 2013
  21. Antoine PelisseDec 15, 2013
  22. 02/21 path.c: rename vsnpath() to git_vsnpath()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  23. Ramsay JonesDec 14, 2013
  24. Duy NguyenDec 15, 2013
  25. Ramsay JonesDec 15, 2013
  26. Duy NguyenDec 16, 2013
  27. Jonathan NiederDec 16, 2013
  28. Junio C HamanoDec 16, 2013
  29. Ramsay JonesDec 16, 2013
  30. 03/21 path.c: move git_path() closer to similar functions git_pathdup()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  31. 04/21 Make git_path() aware of file relocation in $GIT_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  32. 05/21 reflog: use avoid constructing .lock path with git_pathNguyễn Thái Ngọc Duy, Dec 14, 2013
  33. 06/21 fast-import: use git_path() for accessing .git dir instead of get_git_dir()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  34. 07/21 Add new environment variable $GIT_SUPER_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  35. 08/21 setup.c: refactor path manipulation out of read_gitfile()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  36. 09/21 setup.c: add split-repo support to .git filesNguyễn Thái Ngọc Duy, Dec 14, 2013
  37. 10/21 setup.c: add split-repo support to is_git_directory()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  38. 11/21 setup.c: reduce cleanup sites in setup_explicit_git_dir()Nguyễn Thái Ngọc Duy, Dec 14, 2013
  39. 12/21 environment.c: support super .git file specified by $GIT_DIRNguyễn Thái Ngọc Duy, Dec 14, 2013
  40. 13/21 setup: support $GIT_SUPER_DIR as well as super .git filesNguyễn Thái Ngọc Duy, Dec 14, 2013
  41. 14/21 checkout: support checking out into a new working directoryNguyễn Thái Ngọc Duy, Dec 14, 2013
  42. 15/21 checkout: clean up half-prepared directories in --to modeNguyễn Thái Ngọc Duy, Dec 14, 2013
  43. 16/21 setup.c: keep track of the .git file location if readNguyễn Thái Ngọc Duy, Dec 14, 2013
  44. 17/21 prune: strategies for split repositoriesNguyễn Thái Ngọc Duy, Dec 14, 2013
  45. 18/21 refs: adjust reflog path for repos/<id>/HEADNguyễn Thái Ngọc Duy, Dec 14, 2013
  46. 19/21 refs: detach split repos' HEAD when the linked ref is updated/deletedNguyễn Thái Ngọc Duy, Dec 14, 2013
  47. 20/21 refs.c: refactor do_head_ref(... to do_one_head_ref("HEAD", ...Nguyễn Thái Ngọc Duy, Dec 14, 2013
  48. 21/21 revision: include repos/../HEAD in --allNguyễn Thái Ngọc Duy, Dec 14, 2013
  49. Duy NguyenDec 15, 2013

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.