diff --git a/Makefile b/Makefile index 9a8c6c79d72da9..e5111d4083e5be 100644 --- a/Makefile +++ b/Makefile @@ -967,6 +967,7 @@ LIB_OBJS += apply.o LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o LIB_OBJS += archive.o +LIB_OBJS += attach.o LIB_OBJS += attr.o LIB_OBJS += base85.o LIB_OBJS += bisect.o diff --git a/attach.c b/attach.c new file mode 100644 index 00000000000000..a3bdefd872af5c --- /dev/null +++ b/attach.c @@ -0,0 +1,15 @@ +#include "git-compat-util.h" +#include "environment.h" +#include "attach.h" + +const char *default_attachments_ref(void) +{ + const char *attachments_ref = NULL; + if (!attachments_ref) + attachments_ref = getenv(GIT_ATTACHMENTS_REF_ENVIRONMENT); + if (!attachments_ref) + attachments_ref = attachments_ref_name; + if (!attachments_ref) + attachments_ref = GIT_ATTACHMENTS_DEFAULT_REF; + return attachments_ref; +} diff --git a/attach.h b/attach.h new file mode 100644 index 00000000000000..12819f0db6f9cb --- /dev/null +++ b/attach.h @@ -0,0 +1,15 @@ +#ifndef ATTACH_H +#define ATTACH_H + +/* + * Return the default attachments ref. + * + * This the first of the following to be defined: + * 1. The '--ref' option to 'git attach', if given + * 2. The $GIT_ATTACHMENTS_REF environment variable, if set + * 3. The value of the core.attachmentsRef config variable, if set + * 4. GIT_NOTES_DEFAULT_REF (i.e. "refs/attachments/commits") + */ +const char *default_attachments_ref(void); + +#endif diff --git a/builtin/attach.c b/builtin/attach.c index 1a7dc23809c336..63a59f80e980a0 100644 --- a/builtin/attach.c +++ b/builtin/attach.c @@ -1,7 +1,187 @@ #include "builtin.h" #include "gettext.h" +#include "parse-options.h" +#include "config.h" +#include "pathspec.h" +#include "setup.h" +#include "object-store.h" +#include "object-file.h" +#include "object-name.h" +#include "hex.h" +#include "attach.h" +#include "refs.h" +#include "tree-walk.h" +#include "tree.h" + +static const char * const git_attach_usage[] = { + N_("git attach add [--commitish] "), + NULL +}; + +static const char *const git_attach_add_usage[] = { + N_("git attach add [--commitish] "), + NULL +}; + +static int write_attach_ref(const char *ref, struct object_id *attach_tree, + const char *msg) +{ + const char *attachments_ref; + struct object_id commit_oid; + struct object_id parent_oid; + struct tree_desc desc; + struct name_entry entry; + struct tree *tree = NULL; + struct commit_list *parents = NULL; + struct strbuf buf = STRBUF_INIT; + + if (!ref) + attachments_ref = default_attachments_ref(); + + if (!read_ref(attachments_ref, &parent_oid)) { + struct commit *parent = lookup_commit(the_repository, &parent_oid); + if (repo_parse_commit(the_repository, parent)) + die("Failed to find/parse commit %s", attachments_ref); + commit_list_insert(parent, &parents); + } + + tree = repo_get_commit_tree(the_repository, + lookup_commit(the_repository, &parent_oid)); + init_tree_desc(&desc, tree->buffer, tree->size); + + while (tree_entry(&desc, &entry)) { + switch (object_type(entry.mode)) { + case OBJ_TREE: + fprintf(stderr, "entry.path: %s\n", entry.path); + continue; + default: + continue; + } + } + + if (commit_tree(msg, strlen(msg), attach_tree, parents, &commit_oid, + NULL, NULL)) + die(_("Failed to commit attachment tree to database")); + + strbuf_addstr(&buf, msg); + update_ref(buf.buf, attachments_ref, &commit_oid, NULL, 0, + UPDATE_REFS_DIE_ON_ERR); + + strbuf_release(&buf); + return 0; +} + +/* + * * Returns 0 on success, -1 if no attachments, 1 on failure + */ +static int write_attach_tree(const struct pathspec *pathspec, + struct object_id *attach_tree, + struct object_id *attach_commit) +{ + struct strbuf file_buf = STRBUF_INIT; + struct strbuf attach_tree_buf = STRBUF_INIT; + struct dir_struct dir = DIR_INIT; + struct object_id oid; + struct stat st; + unsigned flags = HASH_FORMAT_CHECK | HASH_WRITE_OBJECT; + + dir.flags |= DIR_SHOW_OTHER_DIRECTORIES | DIR_SHOW_IGNORED_TOO | + DIR_KEEP_UNTRACKED_CONTENTS | DIR_HIDE_EMPTY_DIRECTORIES; + fill_directory(&dir, the_repository->index, pathspec); + + // 有没有现成的API解决这个问题, 将dir转换为tree? + // 没找到,自己写吧 + for (int i = 0; i < dir.nr; i++) { + strbuf_reset(&file_buf); + + if (lstat(dir.entries[i]->name, &st)) + die_errno(_("fail to stat file '%s'"), + dir.entries[i]->name); + + if (!(S_ISREG(st.st_mode))) { + warning(_("attachment is not a regular file, skipped '%s'"), + dir.entries[i]->name); + continue; + } + + if (strbuf_read_file(&file_buf, dir.entries[i]->name, 0) < 0) + die(_("unable to read filepath '%s'"), + dir.entries[i]->name); + + if (write_object_file_flags(file_buf.buf, file_buf.len, + OBJ_BLOB, &oid, flags)) + die(_("unable to add blob from %s to database"), + dir.entries[i]->name); + + strbuf_addf(&attach_tree_buf, "%o %s%c", 0100644, + dir.entries[i]->name, '\0'); + strbuf_add(&attach_tree_buf, oid.hash, the_hash_algo->rawsz); + } + + if (attach_tree_buf.len <= 0) + die(_("no attachments found in given pathspec")); + else + write_object_file(attach_tree_buf.buf, attach_tree_buf.len, + OBJ_TREE, attach_tree); + + strbuf_release(&file_buf); + strbuf_release(&attach_tree_buf); + return 0; +} + +static int add(int argc, const char **argv, const char *prefix) +{ + struct pathspec pathspec; + struct object_id attach_tree; + struct object_id attach_commit; + char *attach_commitish = NULL; + char *attachments_msg = "Attachments updated by 'git attach add'"; + + struct option options[] = { + OPT_STRING(0, "commit", &attach_commitish, N_("commit"), + N_("the commit which the attachments reference to")), + OPT_END() + }; + + parse_options(argc, argv, prefix, options, git_attach_add_usage, + PARSE_OPT_KEEP_ARGV0); + attach_commitish = attach_commitish ? attach_commitish : "HEAD"; + + if (repo_get_oid_commit(the_repository, attach_commitish, + &attach_commit)) + die(_("unable to find commit %s"), attach_commitish); + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | PATHSPEC_SYMLINK_LEADING_PATH, + prefix, argv + 1); + if (!pathspec.nr) + die(_("nothing specified, nothing to attach")); + + if (write_attach_tree(&pathspec, &attach_tree, &attach_commit)) + die(_("unable to write attach tree object")); + + if (write_attach_ref(NULL, &attach_tree, attachments_msg)) + die(_("unable to write attach ref")); + + clear_pathspec(&pathspec); + return 0; +} int cmd_attach(int argc, const char **argv, const char *prefix) { - return error(_("subcommand `%s' not implement yet"), argv[0]); + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { OPT_SUBCOMMAND("add", &fn, add), + OPT_END() }; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, options, git_attach_usage, + PARSE_OPT_SUBCOMMAND_OPTIONAL); + + if (!fn) { + error(_("subcommand `%s' not implement yet"), argv[0]); + usage_with_options(git_attach_usage, options); + } + + return !!fn(argc, argv, prefix); } diff --git a/config.c b/config.c index b330c7adb4a5ef..3ba9f57bfff0fb 100644 --- a/config.c +++ b/config.c @@ -1717,6 +1717,11 @@ static int git_default_branch_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.attachmentsref")) { + attachments_ref_name = xstrdup(value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 9e37bf58c0c682..2c76145e0d17b7 100644 --- a/environment.c +++ b/environment.c @@ -73,6 +73,7 @@ enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; char *notes_ref_name; +char *attachments_ref_name; int grafts_keep_true_parents; int core_apply_sparse_checkout; int core_sparse_checkout_cone; diff --git a/environment.h b/environment.h index e5351c9dd95ea6..c8effe1d49b708 100644 --- a/environment.h +++ b/environment.h @@ -55,6 +55,8 @@ const char *getenv_safe(struct strvec *argv, const char *name); #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR" #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE" +#define GIT_ATTACHMENTS_REF_ENVIRONMENT "GIT_ATTACHMENTS_REF" +#define GIT_ATTACHMENTS_DEFAULT_REF "refs/attachments/commits" /* * Environment variable used in handshaking the wire protocol. @@ -190,6 +192,8 @@ enum object_creation_mode { OBJECT_CREATION_USES_RENAMES = 1 }; +extern char *attachments_ref_name; + extern enum object_creation_mode object_creation_mode; extern char *notes_ref_name; diff --git a/t/t6700-attach.sh b/t/t6700-attach.sh new file mode 100755 index 00000000000000..9cb9b41646e18e --- /dev/null +++ b/t/t6700-attach.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +test_description='basic git attach test cases' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit 1st +' + +test_expect_success 'attach: add regular files' ' + echo "content of foo" >foo && + echo "conteng of bar" >bar && + foo_blob=`cat foo | git hash-object --stdin` && + bar_blob=`cat bar | git hash-object --stdin` && + git attach add foo bar +' +test_done \ No newline at end of file