#include "diff.h"
#include "refs.h"
#include "revision.h"
+#include <regex.h>
+#include "grep.h"
static char *path_name(struct name_path *path, const char *name)
{
/*
* Tag object? Look what it points to..
*/
- while (object->type == TYPE_TAG) {
+ while (object->type == OBJ_TAG) {
struct tag *tag = (struct tag *) object;
if (revs->tag_objects && !(flags & UNINTERESTING))
add_pending_object(revs, object, tag->tag);
* Commit object? Just return it, we'll do all the complex
* reachability crud.
*/
- if (object->type == TYPE_COMMIT) {
+ if (object->type == OBJ_COMMIT) {
struct commit *commit = (struct commit *)object;
if (parse_commit(commit) < 0)
die("unable to parse commit %s", name);
* Tree object? Either mark it uniniteresting, or add it
* to the list of objects to look at later..
*/
- if (object->type == TYPE_TREE) {
+ if (object->type == OBJ_TREE) {
struct tree *tree = (struct tree *)object;
if (!revs->tree_objects)
return NULL;
/*
* Blob object? You know the drill by now..
*/
- if (object->type == TYPE_BLOB) {
+ if (object->type == OBJ_BLOB) {
struct blob *blob = (struct blob *)object;
if (!revs->blob_objects)
return NULL;
static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
{
struct commit_list **pp, *parent;
- int tree_changed = 0;
+ int tree_changed = 0, tree_same = 0;
if (!commit->tree)
return;
parse_commit(p);
switch (rev_compare_tree(revs, p->tree, commit->tree)) {
case REV_TREE_SAME:
+ tree_same = 1;
if (!revs->simplify_history || (p->object.flags & UNINTERESTING)) {
/* Even if a merge with an uninteresting
* side branch brought the entire change
}
die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
}
- if (tree_changed)
+ if (tree_changed && !tree_same)
commit->object.flags |= TREECHANGE;
}
if (revs->max_age != -1 && (commit->date < revs->max_age))
obj->flags |= UNINTERESTING;
- if (revs->unpacked && has_sha1_pack(obj->sha1))
+ if (revs->unpacked &&
+ has_sha1_pack(obj->sha1, revs->ignore_packed))
obj->flags |= UNINTERESTING;
add_parents_to_list(revs, commit, &list);
if (obj->flags & UNINTERESTING) {
return 0;
while (1) {
it = get_reference(revs, arg, sha1, 0);
- if (it->type != TYPE_TAG)
+ if (it->type != OBJ_TAG)
break;
- memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+ hashcpy(sha1, ((struct tag*)it)->tagged->sha1);
}
- if (it->type != TYPE_COMMIT)
+ if (it->type != OBJ_COMMIT)
return 0;
commit = (struct commit *)it;
for (parents = commit->parents; parents; parents = parents->next) {
return 1;
}
-void init_revisions(struct rev_info *revs)
+void init_revisions(struct rev_info *revs, const char *prefix)
{
memset(revs, 0, sizeof(*revs));
revs->pruning.change = file_change;
revs->lifo = 1;
revs->dense = 1;
- revs->prefix = setup_git_directory();
+ revs->prefix = prefix;
revs->max_age = -1;
revs->min_age = -1;
revs->max_count = -1;
diff_setup(&revs->diffopt);
}
+static void add_pending_commit_list(struct rev_info *revs,
+ struct commit_list *commit_list,
+ unsigned int flags)
+{
+ while (commit_list) {
+ struct object *object = &commit_list->item->object;
+ object->flags |= flags;
+ add_pending_object(revs, object, sha1_to_hex(object->sha1));
+ commit_list = commit_list->next;
+ }
+}
+
+static void prepare_show_merge(struct rev_info *revs)
+{
+ struct commit_list *bases;
+ struct commit *head, *other;
+ unsigned char sha1[20];
+ const char **prune = NULL;
+ int i, prune_num = 1; /* counting terminating NULL */
+
+ if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+ die("--merge without HEAD?");
+ if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+ die("--merge without MERGE_HEAD?");
+ add_pending_object(revs, &head->object, "HEAD");
+ add_pending_object(revs, &other->object, "MERGE_HEAD");
+ bases = get_merge_bases(head, other, 1);
+ while (bases) {
+ struct commit *it = bases->item;
+ struct commit_list *n = bases->next;
+ free(bases);
+ bases = n;
+ it->object.flags |= UNINTERESTING;
+ add_pending_object(revs, &it->object, "(merge-base)");
+ }
+
+ if (!active_nr)
+ read_cache();
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
+ if (!ce_stage(ce))
+ continue;
+ if (ce_path_match(ce, revs->prune_data)) {
+ prune_num++;
+ prune = xrealloc(prune, sizeof(*prune) * prune_num);
+ prune[prune_num-2] = ce->name;
+ prune[prune_num-1] = NULL;
+ }
+ while ((i+1 < active_nr) &&
+ ce_same_name(ce, active_cache[i+1]))
+ i++;
+ }
+ revs->prune_data = prune;
+}
+
+int handle_revision_arg(const char *arg, struct rev_info *revs,
+ int flags,
+ int cant_be_filename)
+{
+ char *dotdot;
+ struct object *object;
+ unsigned char sha1[20];
+ int local_flags;
+
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char from_sha1[20];
+ const char *next = dotdot + 2;
+ const char *this = arg;
+ int symmetric = *next == '.';
+ unsigned int flags_exclude = flags ^ UNINTERESTING;
+
+ *dotdot = 0;
+ next += symmetric;
+
+ if (!*next)
+ next = "HEAD";
+ if (dotdot == arg)
+ this = "HEAD";
+ if (!get_sha1(this, from_sha1) &&
+ !get_sha1(next, sha1)) {
+ struct commit *a, *b;
+ struct commit_list *exclude;
+
+ a = lookup_commit_reference(from_sha1);
+ b = lookup_commit_reference(sha1);
+ if (!a || !b) {
+ die(symmetric ?
+ "Invalid symmetric difference expression %s...%s" :
+ "Invalid revision range %s..%s",
+ arg, next);
+ }
+
+ if (!cant_be_filename) {
+ *dotdot = '.';
+ verify_non_filename(revs->prefix, arg);
+ }
+
+ if (symmetric) {
+ exclude = get_merge_bases(a, b, 1);
+ add_pending_commit_list(revs, exclude,
+ flags_exclude);
+ free_commit_list(exclude);
+ a->object.flags |= flags;
+ } else
+ a->object.flags |= flags_exclude;
+ b->object.flags |= flags;
+ add_pending_object(revs, &a->object, this);
+ add_pending_object(revs, &b->object, next);
+ return 0;
+ }
+ *dotdot = '.';
+ }
+ dotdot = strstr(arg, "^@");
+ if (dotdot && !dotdot[2]) {
+ *dotdot = 0;
+ if (add_parents_only(revs, arg, flags))
+ return 0;
+ *dotdot = '^';
+ }
+ local_flags = 0;
+ if (*arg == '^') {
+ local_flags = UNINTERESTING;
+ arg++;
+ }
+ if (get_sha1(arg, sha1))
+ return -1;
+ if (!cant_be_filename)
+ verify_non_filename(revs->prefix, arg);
+ object = get_reference(revs, arg, sha1, flags ^ local_flags);
+ add_pending_object(revs, object, arg);
+ return 0;
+}
+
+static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
+{
+ if (!revs->grep_filter) {
+ struct grep_opt *opt = xcalloc(1, sizeof(*opt));
+ opt->status_only = 1;
+ opt->pattern_tail = &(opt->pattern_list);
+ opt->regflags = REG_NEWLINE;
+ revs->grep_filter = opt;
+ }
+ append_grep_pattern(revs->grep_filter, ptn,
+ "command line", 0, what);
+}
+
+static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+{
+ char *pat;
+ const char *prefix;
+ int patlen, fldlen;
+
+ fldlen = strlen(field);
+ patlen = strlen(pattern);
+ pat = xmalloc(patlen + fldlen + 10);
+ prefix = ".*";
+ if (*pattern == '^') {
+ prefix = "";
+ pattern++;
+ }
+ sprintf(pat, "^%s %s%s", field, prefix, pattern);
+ add_grep(revs, pat, GREP_PATTERN_HEAD);
+}
+
+static void add_message_grep(struct rev_info *revs, const char *pattern)
+{
+ add_grep(revs, pattern, GREP_PATTERN_BODY);
+}
+
+static void add_ignore_packed(struct rev_info *revs, const char *name)
+{
+ int num = ++revs->num_ignore_packed;
+
+ revs->ignore_packed = xrealloc(revs->ignore_packed,
+ sizeof(const char **) * (num + 1));
+ revs->ignore_packed[num-1] = name;
+ revs->ignore_packed[num] = NULL;
+}
+
/*
* Parse revision information, filling in the "rev_info" structure,
* and removing the used arguments from the argument list.
*/
int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
{
- int i, flags, seen_dashdash;
+ int i, flags, seen_dashdash, show_merge;
const char **unrecognized = argv + 1;
int left = 1;
break;
}
- flags = 0;
+ flags = show_merge = 0;
for (i = 1; i < argc; i++) {
- struct object *object;
const char *arg = argv[i];
- unsigned char sha1[20];
- char *dotdot;
- int local_flags;
-
if (*arg == '-') {
int opts;
if (!strncmp(arg, "--max-count=", 12)) {
def = argv[i];
continue;
}
+ if (!strcmp(arg, "--merge")) {
+ show_merge = 1;
+ continue;
+ }
if (!strcmp(arg, "--topo-order")) {
revs->topo_order = 1;
continue;
}
if (!strcmp(arg, "--unpacked")) {
revs->unpacked = 1;
+ free(revs->ignore_packed);
+ revs->ignore_packed = NULL;
+ revs->num_ignore_packed = 0;
+ continue;
+ }
+ if (!strncmp(arg, "--unpacked=", 11)) {
+ revs->unpacked = 1;
+ add_ignore_packed(revs, arg+11);
continue;
}
if (!strcmp(arg, "-r")) {
revs->simplify_history = 0;
continue;
}
+ if (!strcmp(arg, "--relative-date")) {
+ revs->relative_date = 1;
+ continue;
+ }
+
+ /*
+ * Grepping the commit log
+ */
+ if (!strncmp(arg, "--author=", 9)) {
+ add_header_grep(revs, "author", arg+9);
+ continue;
+ }
+ if (!strncmp(arg, "--committer=", 12)) {
+ add_header_grep(revs, "committer", arg+12);
+ continue;
+ }
+ if (!strncmp(arg, "--grep=", 7)) {
+ add_message_grep(revs, arg+7);
+ continue;
+ }
+
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
revs->diff = 1;
left++;
continue;
}
- dotdot = strstr(arg, "..");
- if (dotdot) {
- unsigned char from_sha1[20];
- const char *next = dotdot + 2;
- const char *this = arg;
- *dotdot = 0;
- if (!*next)
- next = "HEAD";
- if (dotdot == arg)
- this = "HEAD";
- if (!get_sha1(this, from_sha1) &&
- !get_sha1(next, sha1)) {
- struct object *exclude;
- struct object *include;
-
- exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
- include = get_reference(revs, next, sha1, flags);
- if (!exclude || !include)
- die("Invalid revision range %s..%s", arg, next);
-
- if (!seen_dashdash) {
- *dotdot = '.';
- verify_non_filename(revs->prefix, arg);
- }
- add_pending_object(revs, exclude, this);
- add_pending_object(revs, include, next);
- continue;
- }
- *dotdot = '.';
- }
- dotdot = strstr(arg, "^@");
- if (dotdot && !dotdot[2]) {
- *dotdot = 0;
- if (add_parents_only(revs, arg, flags))
- continue;
- *dotdot = '^';
- }
- local_flags = 0;
- if (*arg == '^') {
- local_flags = UNINTERESTING;
- arg++;
- }
- if (get_sha1(arg, sha1)) {
- int j;
- if (seen_dashdash || local_flags)
+ if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
+ int j;
+ if (seen_dashdash || *arg == '^')
die("bad revision '%s'", arg);
/* If we didn't have a "--":
for (j = i; j < argc; j++)
verify_filename(revs->prefix, argv[j]);
- revs->prune_data = get_pathspec(revs->prefix, argv + i);
+ revs->prune_data = get_pathspec(revs->prefix,
+ argv + i);
break;
}
- if (!seen_dashdash)
- verify_non_filename(revs->prefix, arg);
- object = get_reference(revs, arg, sha1, flags ^ local_flags);
- add_pending_object(revs, object, arg);
}
+
+ if (show_merge)
+ prepare_show_merge(revs);
if (def && !revs->pending.nr) {
unsigned char sha1[20];
struct object *object;
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
}
revs->diffopt.abbrev = revs->abbrev;
- diff_setup_done(&revs->diffopt);
+ if (diff_setup_done(&revs->diffopt) < 0)
+ die("diff_setup_done failed");
+
+ if (revs->grep_filter)
+ compile_grep_patterns(revs->grep_filter);
return left;
}
struct commit *p = *pp;
if (!revs->limited)
add_parents_to_list(revs, p, &revs->commits);
+ if (p->parents && p->parents->next)
+ return 0;
if (p->object.flags & (TREECHANGE | UNINTERESTING))
return 0;
if (!p->parents)
}
}
+static int commit_match(struct commit *commit, struct rev_info *opt)
+{
+ if (!opt->grep_filter)
+ return 1;
+ return grep_buffer(opt->grep_filter,
+ NULL, /* we say nothing, not even filename */
+ commit->buffer, strlen(commit->buffer));
+}
+
struct commit *get_revision(struct rev_info *revs)
{
struct commit_list *list = revs->commits;
*/
if (!revs->limited) {
if ((revs->unpacked &&
- has_sha1_pack(commit->object.sha1)) ||
+ has_sha1_pack(commit->object.sha1,
+ revs->ignore_packed)) ||
(revs->max_age != -1 &&
(commit->date < revs->max_age)))
continue;
if (revs->no_merges &&
commit->parents && commit->parents->next)
continue;
+ if (!commit_match(commit, revs))
+ continue;
if (revs->prune_fn && revs->dense) {
- if (!(commit->object.flags & TREECHANGE))
- continue;
+ /* Commit without changes? */
+ if (!(commit->object.flags & TREECHANGE)) {
+ /* drop merges unless we want parenthood */
+ if (!revs->parents)
+ continue;
+ /* non-merge - always ignore it */
+ if (!commit->parents || !commit->parents->next)
+ continue;
+ }
if (revs->parents)
rewrite_parents(revs, commit);
}