return slash;
}
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is of the form @{-<n>}.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+ struct strbuf buf = STRBUF_INIT;
+ int ret = interpret_branch_name(*string, &buf);
+
+ if (ret == *len) {
+ size_t size;
+ *string = strbuf_detach(&buf, &size);
+ *len = size;
+ return (char *)*string;
+ }
+
+ return NULL;
+}
+
int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
{
+ char *last_branch = substitute_branch_name(&str, &len);
const char **p, *r;
int refs_found = 0;
char fullref[PATH_MAX];
unsigned char sha1_from_ref[20];
unsigned char *this_result;
+ int flag;
this_result = refs_found ? sha1_from_ref : sha1;
mksnpath(fullref, sizeof(fullref), *p, len, str);
- r = resolve_ref(fullref, this_result, 1, NULL);
+ r = resolve_ref(fullref, this_result, 1, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
if (!warn_ambiguous_refs)
break;
- }
+ } else if ((flag & REF_ISSYMREF) &&
+ (len != 4 || strcmp(str, "HEAD")))
+ warning("ignoring dangling symref %s.", fullref);
}
+ free(last_branch);
return refs_found;
}
int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
{
+ char *last_branch = substitute_branch_name(&str, &len);
const char **p;
int logs_found = 0;
if (!warn_ambiguous_refs)
break;
}
+ free(last_branch);
return logs_found;
}
+static int get_sha1_1(const char *name, int len, unsigned char *sha1);
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
- /* basic@{time or number} format to query ref-log */
+ /* basic@{time or number or -number} format to query ref-log */
reflog_len = at = 0;
if (len && str[len-1] == '}') {
- for (at = 0; at < len - 1; at++) {
+ for (at = len-2; at >= 0; at--) {
if (str[at] == '@' && str[at+1] == '{') {
reflog_len = (len-1) - (at+2);
len = at;
return -1;
if (!len && reflog_len) {
+ struct strbuf buf = STRBUF_INIT;
+ int ret;
+ /* try the @{-N} syntax for n-th checkout */
+ ret = interpret_branch_name(str+at, &buf);
+ if (ret > 0) {
+ /* substitute this branch name and restart */
+ return get_sha1_1(buf.buf, buf.len, sha1);
+ } else if (ret == 0) {
+ return -1;
+ }
/* allow "@{...}" to mean the current branch reflog */
refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
} else if (reflog_len)
return 0;
}
-static int get_sha1_1(const char *name, int len, unsigned char *sha1);
-
static int get_parent(const char *name, int len,
unsigned char *result, int idx)
{
return retval;
}
+struct grab_nth_branch_switch_cbdata {
+ long cnt, alloc;
+ struct strbuf *buf;
+};
+
+static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
+{
+ struct grab_nth_branch_switch_cbdata *cb = cb_data;
+ const char *match = NULL, *target = NULL;
+ size_t len;
+ int nth;
+
+ if (!prefixcmp(message, "checkout: moving from ")) {
+ match = message + strlen("checkout: moving from ");
+ target = strstr(match, " to ");
+ }
+
+ if (!match || !target)
+ return 0;
+
+ len = target - match;
+ nth = cb->cnt++ % cb->alloc;
+ strbuf_reset(&cb->buf[nth]);
+ strbuf_add(&cb->buf[nth], match, len);
+ return 0;
+}
+
+/*
+ * This reads "@{-N}" syntax, finds the name of the Nth previous
+ * branch we were on, and places the name of the branch in the given
+ * buf and returns the number of characters parsed if successful.
+ *
+ * If the input is not of the accepted format, it returns a negative
+ * number to signal an error.
+ *
+ * If the input was ok but there are not N branch switches in the
+ * reflog, it returns 0.
+ */
+int interpret_branch_name(const char *name, struct strbuf *buf)
+{
+ long nth;
+ int i, retval;
+ struct grab_nth_branch_switch_cbdata cb;
+ const char *brace;
+ char *num_end;
+
+ if (name[0] != '@' || name[1] != '{' || name[2] != '-')
+ return -1;
+ brace = strchr(name, '}');
+ if (!brace)
+ return -1;
+ nth = strtol(name+3, &num_end, 10);
+ if (num_end != brace)
+ return -1;
+ if (nth <= 0)
+ return -1;
+ cb.alloc = nth;
+ cb.buf = xmalloc(nth * sizeof(struct strbuf));
+ for (i = 0; i < nth; i++)
+ strbuf_init(&cb.buf[i], 20);
+ cb.cnt = 0;
+ retval = 0;
+ for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
+ if (cb.cnt < nth) {
+ cb.cnt = 0;
+ for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ }
+ if (cb.cnt < nth)
+ goto release_return;
+ i = cb.cnt % nth;
+ strbuf_reset(buf);
+ strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
+ retval = brace-name+1;
+
+release_return:
+ for (i = 0; i < nth; i++)
+ strbuf_release(&cb.buf[i]);
+ free(cb.buf);
+
+ return retval;
+}
+
+int get_sha1_mb(const char *name, unsigned char *sha1)
+{
+ struct commit *one, *two;
+ struct commit_list *mbs;
+ unsigned char sha1_tmp[20];
+ const char *dots;
+ int st;
+
+ dots = strstr(name, "...");
+ if (!dots)
+ return get_sha1(name, sha1);
+ if (dots == name)
+ st = get_sha1("HEAD", sha1_tmp);
+ else {
+ struct strbuf sb;
+ strbuf_init(&sb, dots - name);
+ strbuf_add(&sb, name, dots - name);
+ st = get_sha1(sb.buf, sha1_tmp);
+ strbuf_release(&sb);
+ }
+ if (st)
+ return st;
+ one = lookup_commit_reference_gently(sha1_tmp, 0);
+ if (!one)
+ return -1;
+
+ if (get_sha1(dots[3] ? (dots + 3) : "HEAD", sha1_tmp))
+ return -1;
+ two = lookup_commit_reference_gently(sha1_tmp, 0);
+ if (!two)
+ return -1;
+ mbs = get_merge_bases(one, two, 1);
+ if (!mbs || mbs->next)
+ st = -1;
+ else {
+ st = 0;
+ hashcpy(sha1, mbs->item->object.sha1);
+ }
+ free_commit_list(mbs);
+ return st;
+}
+
/*
* This is like "get_sha1_basic()", except it allows "sha1 expressions",
* notably "xyz^" for "parent of xyz"
return get_sha1_with_mode(name, sha1, &unused);
}
-int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
+/* Must be called only when object_name:filename doesn't exist. */
+static void diagnose_invalid_sha1_path(const char *prefix,
+ const char *filename,
+ const unsigned char *tree_sha1,
+ const char *object_name)
+{
+ struct stat st;
+ unsigned char sha1[20];
+ unsigned mode;
+
+ if (!prefix)
+ prefix = "";
+
+ if (!lstat(filename, &st))
+ die("Path '%s' exists on disk, but not in '%s'.",
+ filename, object_name);
+ if (errno == ENOENT || errno == ENOTDIR) {
+ char *fullname = xmalloc(strlen(filename)
+ + strlen(prefix) + 1);
+ strcpy(fullname, prefix);
+ strcat(fullname, filename);
+
+ if (!get_tree_entry(tree_sha1, fullname,
+ sha1, &mode)) {
+ die("Path '%s' exists, but not '%s'.\n"
+ "Did you mean '%s:%s'?",
+ fullname,
+ filename,
+ object_name,
+ fullname);
+ }
+ die("Path '%s' does not exist in '%s'",
+ filename, object_name);
+ }
+}
+
+/* Must be called only when :stage:filename doesn't exist. */
+static void diagnose_invalid_index_path(int stage,
+ const char *prefix,
+ const char *filename)
+{
+ struct stat st;
+ struct cache_entry *ce;
+ int pos;
+ unsigned namelen = strlen(filename);
+ unsigned fullnamelen;
+ char *fullname;
+
+ if (!prefix)
+ prefix = "";
+
+ /* Wrong stage number? */
+ pos = cache_name_pos(filename, namelen);
+ if (pos < 0)
+ pos = -pos - 1;
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == namelen &&
+ !memcmp(ce->name, filename, namelen))
+ die("Path '%s' is in the index, but not at stage %d.\n"
+ "Did you mean ':%d:%s'?",
+ filename, stage,
+ ce_stage(ce), filename);
+
+ /* Confusion between relative and absolute filenames? */
+ fullnamelen = namelen + strlen(prefix);
+ fullname = xmalloc(fullnamelen + 1);
+ strcpy(fullname, prefix);
+ strcat(fullname, filename);
+ pos = cache_name_pos(fullname, fullnamelen);
+ if (pos < 0)
+ pos = -pos - 1;
+ ce = active_cache[pos];
+ if (ce_namelen(ce) == fullnamelen &&
+ !memcmp(ce->name, fullname, fullnamelen))
+ die("Path '%s' is in the index, but not '%s'.\n"
+ "Did you mean ':%d:%s'?",
+ fullname, filename,
+ ce_stage(ce), fullname);
+
+ if (!lstat(filename, &st))
+ die("Path '%s' exists on disk, but not in the index.", filename);
+ if (errno == ENOENT || errno == ENOTDIR)
+ die("Path '%s' does not exist (neither on disk nor in the index).",
+ filename);
+
+ free(fullname);
+}
+
+
+int get_sha1_with_mode_1(const char *name, unsigned char *sha1, unsigned *mode, int gently, const char *prefix)
{
int ret, bracket_depth;
int namelen = strlen(name);
}
pos++;
}
+ if (!gently)
+ diagnose_invalid_index_path(stage, prefix, cp);
return -1;
}
for (cp = name, bracket_depth = 0; *cp; cp++) {
}
if (*cp == ':') {
unsigned char tree_sha1[20];
- if (!get_sha1_1(name, cp-name, tree_sha1))
- return get_tree_entry(tree_sha1, cp+1, sha1,
- mode);
+ char *object_name = NULL;
+ if (!gently) {
+ object_name = xmalloc(cp-name+1);
+ strncpy(object_name, name, cp-name);
+ object_name[cp-name] = '\0';
+ }
+ if (!get_sha1_1(name, cp-name, tree_sha1)) {
+ const char *filename = cp+1;
+ ret = get_tree_entry(tree_sha1, filename, sha1, mode);
+ if (!gently) {
+ diagnose_invalid_sha1_path(prefix, filename,
+ tree_sha1, object_name);
+ free(object_name);
+ }
+ return ret;
+ } else {
+ if (!gently)
+ die("Invalid object name '%s'.", object_name);
+ }
}
return ret;
}