static void describe_detached_head(char *msg, struct commit *commit)
{
struct strbuf sb = STRBUF_INIT;
+ struct pretty_print_context ctx = {0};
parse_commit(commit);
- pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, NULL, NULL, 0, 0);
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, &ctx);
fprintf(stderr, "%s %s... %s\n", msg,
find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
strbuf_release(&sb);
return run_add_interactive(revision, "--patch=checkout", pathspec);
}
+struct tracking_name_data {
+ const char *name;
+ char *remote;
+ int unique;
+};
+
+static int check_tracking_name(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct tracking_name_data *cb = cb_data;
+ const char *slash;
+
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ slash = strchr(refname + 13, '/');
+ if (!slash || strcmp(slash + 1, cb->name))
+ return 0;
+ if (cb->remote) {
+ cb->unique = 0;
+ return 0;
+ }
+ cb->remote = xstrdup(refname);
+ return 0;
+}
+
+static const char *unique_tracking_name(const char *name)
+{
+ struct tracking_name_data cb_data = { name, NULL, 1 };
+ for_each_ref(check_tracking_name, &cb_data);
+ if (cb_data.unique)
+ return cb_data.remote;
+ free(cb_data.remote);
+ return NULL;
+}
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
struct tree *source_tree = NULL;
char *conflict_style = NULL;
int patch_mode = 0;
+ int dwim_new_local_branch = 1;
struct option options[] = {
OPT__QUIET(&opts.quiet),
OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
OPT_STRING(0, "conflict", &conflict_style, "style",
"conflict style (merge or diff3)"),
OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"),
+ { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
+ "second guess 'git checkout no-such-branch'",
+ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
OPT_END(),
};
int has_dash_dash;
opts.new_branch = argv0 + 1;
}
- if (opts.track == BRANCH_TRACK_UNSPECIFIED)
- opts.track = git_branch_track;
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
* With no paths, if <something> is a commit, that is to
* switch to the branch or detach HEAD at it.
*
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote tracking branch.
+ *
* Otherwise <something> shall not be ambiguous.
* - If it's *only* a reference, treat it like case (1).
* - If it's only a path, treat it like case (2).
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
- goto no_reference; /* case (3 -> 2) */
+ if (!patch_mode &&
+ dwim_new_local_branch &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ goto no_reference;
+ opts.new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ }
+ else
+ goto no_reference;
}
/* we can't end up being in (2) anymore, eat the argument */
}
no_reference:
+
+ if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+ opts.track = git_branch_track;
+
if (argc) {
const char **pathspec = get_pathspec(prefix, argv);
author_date = date;
}
+static int ends_rfc2822_footer(struct strbuf *sb)
+{
+ int ch;
+ int hit = 0;
+ int i, j, k;
+ int len = sb->len;
+ int first = 1;
+ const char *buf = sb->buf;
+
+ for (i = len - 1; i > 0; i--) {
+ if (hit && buf[i] == '\n')
+ break;
+ hit = (buf[i] == '\n');
+ }
+
+ while (i < len - 1 && buf[i] == '\n')
+ i++;
+
+ for (; i < len; i = k) {
+ for (k = i; k < len && buf[k] != '\n'; k++)
+ ; /* do nothing */
+ k++;
+
+ if ((buf[k] == ' ' || buf[k] == '\t') && !first)
+ continue;
+
+ first = 0;
+
+ for (j = 0; i + j < len; j++) {
+ ch = buf[i + j];
+ if (ch == ':')
+ break;
+ if (isalnum(ch) ||
+ (ch == '-'))
+ continue;
+ return 0;
+ }
+ }
+ return 1;
+}
+
static int prepare_to_commit(const char *index_file, const char *prefix,
struct wt_status *s)
{
for (i = sb.len - 1; i > 0 && sb.buf[i - 1] != '\n'; i--)
; /* do nothing */
if (prefixcmp(sb.buf + i, sob.buf)) {
- if (prefixcmp(sb.buf + i, sign_off_header))
+ if (!ends_rfc2822_footer(&sb))
strbuf_addch(&sb, '\n');
strbuf_addbuf(&sb, &sob);
}
prepare_revision_walk(&revs);
commit = get_revision(&revs);
if (commit) {
+ struct pretty_print_context ctx = {0};
+ ctx.date_mode = DATE_NORMAL;
strbuf_release(&buf);
- format_commit_message(commit, "%an <%ae>", &buf, DATE_NORMAL);
+ format_commit_message(commit, "%an <%ae>", &buf, &ctx);
return strbuf_detach(&buf, NULL);
}
die("No existing author found with '%s'", name);
initial_commit ? " (root-commit)" : "");
if (!log_tree_commit(&rev, commit)) {
+ struct pretty_print_context ctx = {0};
struct strbuf buf = STRBUF_INIT;
- format_commit_message(commit, format + 7, &buf, DATE_NORMAL);
+ ctx.date_mode = DATE_NORMAL;
+ format_commit_message(commit, format + 7, &buf, &ctx);
printf("%s\n", buf.buf);
strbuf_release(&buf);
}
CMIT_FMT_UNSPECIFIED,
};
+ struct pretty_print_context
+ {
+ int abbrev;
+ const char *subject;
+ const char *after_subject;
+ enum date_mode date_mode;
+ int need_8bit_cte;
+ struct reflog_walk_info *reflog_info;
+ };
+
extern int non_ascii(int);
extern int has_non_ascii(const char *text);
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
const char **encoding_p);
extern void get_commit_format(const char *arg, struct rev_info *);
extern void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb,
+ const char *format, struct strbuf *sb,
- enum date_mode dmode);
- extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
- struct strbuf *,
- int abbrev, const char *subject,
- const char *after_subject, enum date_mode,
- int need_8bit_cte);
+ const struct pretty_print_context *context);
+ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
+ struct strbuf *sb,
+ const struct pretty_print_context *context);
void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
const char *line, enum date_mode dmode,
const char *encoding);
#include "mailmap.h"
#include "log-tree.h"
#include "color.h"
+ #include "reflog-walk.h"
static char *user_format;
struct format_commit_context {
const struct commit *commit;
- enum date_mode dmode;
+ const struct pretty_print_context *pretty_ctx;
unsigned commit_header_parsed:1;
unsigned commit_message_parsed:1;
case 'd':
format_decoration(sb, commit);
return 1;
+ case 'g': /* reflog info */
+ switch(placeholder[1]) {
+ case 'd': /* reflog selector */
+ case 'D':
+ if (c->pretty_ctx->reflog_info)
+ get_reflog_selector(sb,
+ c->pretty_ctx->reflog_info,
+ c->pretty_ctx->date_mode,
+ (placeholder[1] == 'd'));
+ return 2;
+ case 's': /* reflog message */
+ if (c->pretty_ctx->reflog_info)
+ get_reflog_message(sb, c->pretty_ctx->reflog_info);
+ return 2;
+ }
+ return 0; /* unknown %g placeholder */
}
/* For the rest we have to parse the commit header. */
case 'a': /* author ... */
return format_person_part(sb, placeholder[1],
msg + c->author.off, c->author.len,
- c->dmode);
+ c->pretty_ctx->date_mode);
case 'c': /* committer ... */
return format_person_part(sb, placeholder[1],
msg + c->committer.off, c->committer.len,
- c->dmode);
+ c->pretty_ctx->date_mode);
case 'e': /* encoding */
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
return 1;
}
void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb,
+ const char *format, struct strbuf *sb,
- enum date_mode dmode)
+ const struct pretty_print_context *pretty_ctx)
{
struct format_commit_context context;
memset(&context, 0, sizeof(context));
context.commit = commit;
- context.dmode = dmode;
+ context.pretty_ctx = pretty_ctx;
strbuf_expand(sb, format, format_commit_item, &context);
}
}
void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
- struct strbuf *sb, int abbrev,
- const char *subject, const char *after_subject,
- enum date_mode dmode, int need_8bit_cte)
+ struct strbuf *sb,
+ const struct pretty_print_context *context)
{
unsigned long beginning_of_body;
int indent = 4;
const char *msg = commit->buffer;
char *reencoded;
const char *encoding;
+ int need_8bit_cte = context->need_8bit_cte;
if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb, dmode);
+ format_commit_message(commit, user_format, sb, context);
return;
}
}
}
- pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
- if (fmt != CMIT_FMT_ONELINE && !subject) {
+ pp_header(fmt, context->abbrev, context->date_mode, encoding,
+ commit, &msg, sb);
+ if (fmt != CMIT_FMT_ONELINE && !context->subject) {
strbuf_addch(sb, '\n');
}
/* These formats treat the title line specially. */
if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
- pp_title_line(fmt, &msg, sb, subject,
- after_subject, encoding, need_8bit_cte);
+ pp_title_line(fmt, &msg, sb, context->subject,
+ context->after_subject, encoding, need_8bit_cte);
beginning_of_body = sb->len;
if (fmt != CMIT_FMT_ONELINE)
--- /dev/null
- format_commit_message(commit, format, &sb,
- rev.date_mode);
+#include "cache.h"
+#include "submodule.h"
+#include "dir.h"
+#include "diff.h"
+#include "commit.h"
+#include "revision.h"
+
+int add_submodule_odb(const char *path)
+{
+ struct strbuf objects_directory = STRBUF_INIT;
+ struct alternate_object_database *alt_odb;
+
+ strbuf_addf(&objects_directory, "%s/.git/objects/", path);
+ if (!is_directory(objects_directory.buf))
+ return -1;
+
+ /* avoid adding it twice */
+ for (alt_odb = alt_odb_list; alt_odb; alt_odb = alt_odb->next)
+ if (alt_odb->name - alt_odb->base == objects_directory.len &&
+ !strncmp(alt_odb->base, objects_directory.buf,
+ objects_directory.len))
+ return 0;
+
+ alt_odb = xmalloc(objects_directory.len + 42 + sizeof(*alt_odb));
+ alt_odb->next = alt_odb_list;
+ strcpy(alt_odb->base, objects_directory.buf);
+ alt_odb->name = alt_odb->base + objects_directory.len;
+ alt_odb->name[2] = '/';
+ alt_odb->name[40] = '\0';
+ alt_odb->name[41] = '\0';
+ alt_odb_list = alt_odb;
+ prepare_alt_odb();
+ return 0;
+}
+
+void show_submodule_summary(FILE *f, const char *path,
+ unsigned char one[20], unsigned char two[20],
+ const char *del, const char *add, const char *reset)
+{
+ struct rev_info rev;
+ struct commit *commit, *left = left, *right;
+ struct commit_list *merge_bases, *list;
+ const char *message = NULL;
+ struct strbuf sb = STRBUF_INIT;
+ static const char *format = " %m %s";
+ int fast_forward = 0, fast_backward = 0;
+
+ if (is_null_sha1(two))
+ message = "(submodule deleted)";
+ else if (add_submodule_odb(path))
+ message = "(not checked out)";
+ else if (is_null_sha1(one))
+ message = "(new submodule)";
+ else if (!(left = lookup_commit_reference(one)) ||
+ !(right = lookup_commit_reference(two)))
+ message = "(commits not present)";
+
+ if (!message) {
+ init_revisions(&rev, NULL);
+ setup_revisions(0, NULL, &rev, NULL);
+ rev.left_right = 1;
+ rev.first_parent_only = 1;
+ left->object.flags |= SYMMETRIC_LEFT;
+ add_pending_object(&rev, &left->object, path);
+ add_pending_object(&rev, &right->object, path);
+ merge_bases = get_merge_bases(left, right, 1);
+ if (merge_bases) {
+ if (merge_bases->item == left)
+ fast_forward = 1;
+ else if (merge_bases->item == right)
+ fast_backward = 1;
+ }
+ for (list = merge_bases; list; list = list->next) {
+ list->item->object.flags |= UNINTERESTING;
+ add_pending_object(&rev, &list->item->object,
+ sha1_to_hex(list->item->object.sha1));
+ }
+ if (prepare_revision_walk(&rev))
+ message = "(revision walker failed)";
+ }
+
+ strbuf_addf(&sb, "Submodule %s %s..", path,
+ find_unique_abbrev(one, DEFAULT_ABBREV));
+ if (!fast_backward && !fast_forward)
+ strbuf_addch(&sb, '.');
+ strbuf_addf(&sb, "%s", find_unique_abbrev(two, DEFAULT_ABBREV));
+ if (message)
+ strbuf_addf(&sb, " %s\n", message);
+ else
+ strbuf_addf(&sb, "%s:\n", fast_backward ? " (rewind)" : "");
+ fwrite(sb.buf, sb.len, 1, f);
+
+ if (!message) {
+ while ((commit = get_revision(&rev))) {
++ struct pretty_print_context ctx = {0};
++ ctx.date_mode = rev.date_mode;
+ strbuf_setlen(&sb, 0);
+ if (commit->object.flags & SYMMETRIC_LEFT) {
+ if (del)
+ strbuf_addstr(&sb, del);
+ }
+ else if (add)
+ strbuf_addstr(&sb, add);
++ format_commit_message(commit, format, &sb, &ctx);
+ if (reset)
+ strbuf_addstr(&sb, reset);
+ strbuf_addch(&sb, '\n');
+ fprintf(f, "%s", sb.buf);
+ }
+ clear_commit_marks(left, ~0);
+ clear_commit_marks(right, ~0);
+ }
+ strbuf_release(&sb);
+}