{"thread":{"id":"4786","subject":"More fully-fleshed-out builtin \"git prune\"","startedAt":"2006-07-06T17:16:22Z","lastAt":"2006-07-06T17:16:22Z","messageCount":1,"participants":["Linus Torvalds"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"23326","messageId":"Pine.LNX.4.64.0607061009440.12404@g5.osdl.org","threadId":"4786","inReplyTo":null,"subject":"More fully-fleshed-out builtin \"git prune\"","fromName":"Linus Torvalds","fromEmail":"torvalds@osdl.org","sentAt":"2006-07-06T17:16:22Z","receivedAt":"2006-07-06T17:16:22Z","isPatch":false,"sender":{"key":"torvalds@linux-foundation.org","avatar":"https://avatars.githubusercontent.com/u/1024025?v=4"},"body":"\nThis actually removes the objects to be pruned, unless you specify \"-n\" \n(at which point it will just tell you which files it would prune).\n\nThis doesn't do the pack-file pruning that the shell-script used to do, \nbut if somebody really wants to, they could add it easily enough. I wonder \nhow useful it is, though, considering that \"git repack -a -d\" is just a \nlot more efficient and generates a better end result.\n\nSigned-off-by: Linus Torvalds <torvalds@osdl.org>\n---\n\nI've tested this somewhat, including the \"object only reachable through \nthe index\" case, and used it to do a few real prunes. It's a lot faster \n(because it only does the trivial reachability part), and it all _seems_ \nfine and the code is fairly obvious and certainly simple enough. \n\nThat said, this is \"git prune\". Getting things wrong here would be bad. \nPeople should double- and triple-check this.\n\n Makefile        |    4 -\n builtin-prune.c |  259 +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n builtin.h       |    2 \n git-prune.sh    |   44 ---------\n git.c           |    3 -\n 5 files changed, 265 insertions(+), 47 deletions(-)\n\ndiff --git a/Makefile b/Makefile\nindex 7fa4a27..d355622 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -120,7 +120,7 @@ SCRIPT_SH = \\\n \tgit-fetch.sh \\\n \tgit-ls-remote.sh \\\n \tgit-merge-one-file.sh git-parse-remote.sh \\\n-\tgit-prune.sh git-pull.sh git-rebase.sh \\\n+\tgit-pull.sh git-rebase.sh \\\n \tgit-repack.sh git-request-pull.sh git-reset.sh \\\n \tgit-resolve.sh git-revert.sh git-sh-setup.sh \\\n \tgit-tag.sh git-verify-tag.sh \\\n@@ -230,7 +230,7 @@ BUILTIN_OBJS = \\\n \tbuiltin-apply.o builtin-show-branch.o builtin-diff-files.o \\\n \tbuiltin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \\\n \tbuiltin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \\\n-\tbuiltin-update-ref.o builtin-fmt-merge-msg.o\n+\tbuiltin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o\n \n GITLIBS = $(LIB_FILE) $(XDIFF_LIB)\n LIBS = $(GITLIBS) -lz\ndiff --git a/builtin-prune.c b/builtin-prune.c\nnew file mode 100644\nindex 0000000..fc55b76\n--- /dev/null\n+++ b/builtin-prune.c\n@@ -0,0 +1,259 @@\n+#include \"cache.h\"\n+#include \"refs.h\"\n+#include \"tag.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"blob.h\"\n+#include \"tree-walk.h\"\n+#include \"diff.h\"\n+#include \"revision.h\"\n+#include \"builtin.h\"\n+#include \"cache-tree.h\"\n+\n+static const char prune_usage[] = \"git prune [-n]\";\n+static int show_only = 0;\n+static struct rev_info revs;\n+\n+static int prune_object(char *path, const char *filename, const unsigned char *sha1)\n+{\n+\tif (show_only) {\n+\t\tprintf(\"would prune %s/%s\\n\", path, filename);\n+\t\treturn 0;\n+\t}\n+\tunlink(mkpath(\"%s/%s\", path, filename));\n+\trmdir(path);\n+\treturn 0;\n+}\n+\n+static int prune_dir(int i, char *path)\n+{\n+\tDIR *dir = opendir(path);\n+\tstruct dirent *de;\n+\n+\tif (!dir)\n+\t\treturn 0;\n+\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tchar name[100];\n+\t\tunsigned char sha1[20];\n+\t\tint len = strlen(de->d_name);\n+\n+\t\tswitch (len) {\n+\t\tcase 2:\n+\t\t\tif (de->d_name[1] != '.')\n+\t\t\t\tbreak;\n+\t\tcase 1:\n+\t\t\tif (de->d_name[0] != '.')\n+\t\t\t\tbreak;\n+\t\t\tcontinue;\n+\t\tcase 38:\n+\t\t\tsprintf(name, \"%02x\", i);\n+\t\t\tmemcpy(name+2, de->d_name, len+1);\n+\t\t\tif (get_sha1_hex(name, sha1) < 0)\n+\t\t\t\tbreak;\n+\n+\t\t\t/*\n+\t\t\t * Do we know about this object?\n+\t\t\t * It must have been reachable\n+\t\t\t */\n+\t\t\tif (lookup_object(sha1))\n+\t\t\t\tcontinue;\n+\n+\t\t\tprune_object(path, de->d_name, sha1);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tfprintf(stderr, \"bad sha1 file: %s/%s\\n\", path, de->d_name);\n+\t}\n+\tclosedir(dir);\n+\treturn 0;\n+}\n+\n+static void prune_object_dir(const char *path)\n+{\n+\tint i;\n+\tfor (i = 0; i < 256; i++) {\n+\t\tstatic char dir[4096];\n+\t\tsprintf(dir, \"%s/%02x\", path, i);\n+\t\tprune_dir(i, dir);\n+\t}\n+}\n+\n+static void process_blob(struct blob *blob,\n+\t\t\t struct object_array *p,\n+\t\t\t struct name_path *path,\n+\t\t\t const char *name)\n+{\n+\tstruct object *obj = &blob->object;\n+\n+\tif (obj->flags & SEEN)\n+\t\treturn;\n+\tobj->flags |= SEEN;\n+\t/* Nothing to do, really .. The blob lookup was the important part */\n+}\n+\n+static void process_tree(struct tree *tree,\n+\t\t\t struct object_array *p,\n+\t\t\t struct name_path *path,\n+\t\t\t const char *name)\n+{\n+\tstruct object *obj = &tree->object;\n+\tstruct tree_desc desc;\n+\tstruct name_entry entry;\n+\tstruct name_path me;\n+\n+\tif (obj->flags & SEEN)\n+\t\treturn;\n+\tobj->flags |= SEEN;\n+\tif (parse_tree(tree) < 0)\n+\t\tdie(\"bad tree object %s\", sha1_to_hex(obj->sha1));\n+\tname = strdup(name);\n+\tadd_object(obj, p, path, name);\n+\tme.up = path;\n+\tme.elem = name;\n+\tme.elem_len = strlen(name);\n+\n+\tdesc.buf = tree->buffer;\n+\tdesc.size = tree->size;\n+\n+\twhile (tree_entry(&desc, &entry)) {\n+\t\tif (S_ISDIR(entry.mode))\n+\t\t\tprocess_tree(lookup_tree(entry.sha1), p, &me, entry.path);\n+\t\telse\n+\t\t\tprocess_blob(lookup_blob(entry.sha1), p, &me, entry.path);\n+\t}\n+\tfree(tree->buffer);\n+\ttree->buffer = NULL;\n+}\n+\n+static void process_tag(struct tag *tag, struct object_array *p, const char *name)\n+{\n+\tstruct object *obj = &tag->object;\n+\tstruct name_path me;\n+\n+\tif (obj->flags & SEEN)\n+\t\treturn;\n+\tobj->flags |= SEEN;\n+\n+\tme.up = NULL;\n+\tme.elem = \"tag:/\";\n+\tme.elem_len = 5;\n+\n+\tif (parse_tag(tag) < 0)\n+\t\tdie(\"bad tag object %s\", sha1_to_hex(obj->sha1));\n+\tadd_object(tag->tagged, p, NULL, name);\n+}\n+\n+static void walk_commit_list(struct rev_info *revs)\n+{\n+\tint i;\n+\tstruct commit *commit;\n+\tstruct object_array objects = { 0, 0, NULL };\n+\n+\t/* Walk all commits, process their trees */\n+\twhile ((commit = get_revision(revs)) != NULL)\n+\t\tprocess_tree(commit->tree, &objects, NULL, \"\");\n+\n+\t/* Then walk all the pending objects, recursively processing them too */\n+\tfor (i = 0; i < revs->pending.nr; i++) {\n+\t\tstruct object_array_entry *pending = revs->pending.objects + i;\n+\t\tstruct object *obj = pending->item;\n+\t\tconst char *name = pending->name;\n+\t\tif (obj->type == TYPE_TAG) {\n+\t\t\tprocess_tag((struct tag *) obj, &objects, name);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (obj->type == TYPE_TREE) {\n+\t\t\tprocess_tree((struct tree *)obj, &objects, NULL, name);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (obj->type == TYPE_BLOB) {\n+\t\t\tprocess_blob((struct blob *)obj, &objects, NULL, name);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tdie(\"unknown pending object %s (%s)\", sha1_to_hex(obj->sha1), name);\n+\t}\n+}\n+\n+static int add_one_ref(const char *path, const unsigned char *sha1)\n+{\n+\tstruct object *object = parse_object(sha1);\n+\tif (!object)\n+\t\tdie(\"bad object ref: %s:%s\", path, sha1_to_hex(sha1));\n+\tadd_pending_object(&revs, object, \"\");\n+\treturn 0;        \n+}\n+\n+static void add_one_tree(const unsigned char *sha1)\n+{\n+\tstruct tree *tree = lookup_tree(sha1);\n+\tadd_pending_object(&revs, &tree->object, \"\");\n+}\n+\n+static void add_cache_tree(struct cache_tree *it)\n+{\n+\tint i;\n+\n+\tif (it->entry_count >= 0)\n+\t\tadd_one_tree(it->sha1);\n+\tfor (i = 0; i < it->subtree_nr; i++)\n+\t\tadd_cache_tree(it->down[i]->cache_tree);\n+}\n+\n+static void add_cache_refs(void)\n+{\n+\tint i;\n+\n+ \tread_cache();\n+\tfor (i = 0; i < active_nr; i++) {\n+\t\tlookup_blob(active_cache[i]->sha1);\n+\t\t/*\n+\t\t * We could add the blobs to the pending list, but quite\n+\t\t * frankly, we don't care. Once we've looked them up, and\n+\t\t * added them as objects, we've really done everything \n+\t\t * there is to do for a blob\n+\t\t */\n+\t}\n+\tif (active_cache_tree)\n+\t\tadd_cache_tree(active_cache_tree);\n+}\n+\n+int cmd_prune(int argc, const char **argv, char **envp)\n+{\n+\tint i;\n+\n+\tfor (i = 1; i < argc; i++) {\n+\t\tconst char *arg = argv[i];\n+\t\tif (!strcmp(arg, \"-n\")) {\n+\t\t\tshow_only = 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tusage(prune_usage);\n+\t}\n+\n+\t/*\n+\t * Set up revision parsing, and mark us as being interested\n+\t * in all object types, not just commits.\n+\t */\n+\tinit_revisions(&revs);\n+\trevs.tag_objects = 1;\n+\trevs.blob_objects = 1;\n+\trevs.tree_objects = 1;\n+\n+\t/* Add all external refs */\n+\tfor_each_ref(add_one_ref);\n+\n+\t/* Add all refs from the index file */\n+\tadd_cache_refs();\n+\n+\t/*\n+\t * Set up the revision walk - this will move all commits\n+\t * from the pending list to the commit walking list.\n+\t */\n+\tprepare_revision_walk(&revs);\n+\n+\twalk_commit_list(&revs);\n+\n+\tprune_object_dir(get_object_directory());\n+\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex d9e5483..5339d86 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -25,6 +25,8 @@ extern int cmd_diff(int argc, const char\n extern int cmd_format_patch(int argc, const char **argv, char **envp);\n extern int cmd_count_objects(int argc, const char **argv, char **envp);\n \n+extern int cmd_prune(int argc, const char **argv, char **envp);\n+\n extern int cmd_push(int argc, const char **argv, char **envp);\n extern int cmd_grep(int argc, const char **argv, char **envp);\n extern int cmd_rm(int argc, const char **argv, char **envp);\ndiff --git a/git-prune.sh b/git-prune.sh\ndeleted file mode 100755\nindex c5a5d29..0000000\n--- a/git-prune.sh\n+++ /dev/null\n@@ -1,44 +0,0 @@\n-#!/bin/sh\n-\n-USAGE='[-n] [--] [<head>...]'\n-. git-sh-setup\n-\n-dryrun=\n-echo=\n-while case \"$#\" in 0) break ;; esac\n-do\n-    case \"$1\" in\n-    -n) dryrun=-n echo=echo ;;\n-    --) break ;;\n-    -*) usage ;;\n-    *)  break ;;\n-    esac\n-    shift;\n-done\n-\n-sync\n-case \"$#\" in\n-0) git-fsck-objects --full --cache --unreachable ;;\n-*) git-fsck-objects --full --cache --unreachable $(git-rev-parse --all) \"$@\" ;;\n-esac |\n-\n-sed -ne '/unreachable /{\n-    s/unreachable [^ ][^ ]* //\n-    s|\\(..\\)|\\1/|p\n-}' | {\n-\tcd \"$GIT_OBJECT_DIRECTORY\" || exit\n-\txargs $echo rm -f\n-\trmdir 2>/dev/null [0-9a-f][0-9a-f]\n-}\n-\n-git-prune-packed $dryrun\n-\n-if redundant=$(git-pack-redundant --all 2>/dev/null) && test \"\" != \"$redundant\"\n-then\n-\tif test \"\" = \"$dryrun\"\n-\tthen\n-\t\techo \"$redundant\" | xargs rm -f\n-\telse\n-\t\techo rm -f \"$redundant\"\n-\tfi\n-fi\ndiff --git a/git.c b/git.c\nindex 2567301..16e37e5 100644\n--- a/git.c\n+++ b/git.c\n@@ -188,7 +188,8 @@ static void handle_internal_command(int \n \t\t{ \"stripspace\", cmd_stripspace },\n \t\t{ \"update-index\", cmd_update_index },\n \t\t{ \"update-ref\", cmd_update_ref },\n-\t\t{ \"fmt-merge-msg\", cmd_fmt_merge_msg }\n+\t\t{ \"fmt-merge-msg\", cmd_fmt_merge_msg },\n+\t\t{ \"prune\", cmd_prune },\n \t};\n \tint i;\n \n"}]}