This implies the '--topo-order' option by default, but the
'--date-order' option may also be specified.
+ ifdef::git-rev-list[]
+ --count::
+ Print a number stating how many commits would have been
+ listed, and suppress all other output. When used together
+ with '--left-right', instead print the counts for left and
+ right commits, separated by a tab.
+ endif::git-rev-list[]
+
+
ifndef::git-rev-list[]
Diff Formatting
~~~~~~~~~~~~~~~
merges from the resulting history, as there are no selected
commits contributing to this merge.
+--ancestry-path::
+
+ When given a range of commits to display (e.g. 'commit1..commit2'
+ or 'commit2 {caret}commit1'), only display commits that exist
+ directly on the ancestry chain between the 'commit1' and
+ 'commit2', i.e. commits that are both descendants of 'commit1',
+ and ancestors of 'commit2'.
+
A more detailed explanation follows.
Suppose you specified `foo` as the <paths>. We shall call commits
+
-----------------------------------------------------------------------
.-A---N---O
- / /
+ / / /
I---------D
-----------------------------------------------------------------------
+
one of the parents is TREESAME, we follow only that one, so the other
sides of the merge are never walked.
-Finally, there is a fourth simplification mode available:
-
--simplify-merges::
First, build a history graph in the same way that
removed completely, because it had one parent and is TREESAME.
--
+Finally, there is a fifth simplification mode available:
+
+--ancestry-path::
+
+ Limit the displayed commits to those directly on the ancestry
+ chain between the "from" and "to" commits in the given commit
+ range. I.e. only display commits that are ancestor of the "to"
+ commit, and descendants of the "from" commit.
++
+As an example use case, consider the following commit history:
++
+-----------------------------------------------------------------------
+ D---E-------F
+ / \ \
+ B---C---G---H---I---J
+ / \
+ A-------K---------------L--M
+-----------------------------------------------------------------------
++
+A regular 'D..M' computes the set of commits that are ancestors of `M`,
+but excludes the ones that are ancestors of `D`. This is useful to see
+what happened to the history leading to `M` since `D`, in the sense
+that "what does `M` have that did not exist in `D`". The result in this
+example would be all the commits, except `A` and `B` (and `D` itself,
+of course).
++
+When we want to find out what commits in `M` are contaminated with the
+bug introduced by `D` and need fixing, however, we might want to view
+only the subset of 'D..M' that are actually descendants of `D`, i.e.
+excluding `C` and `K`. This is exactly what the '\--ancestry-path'
+option does. Applied to the 'D..M' range, it results in:
++
+-----------------------------------------------------------------------
+ E-------F
+ \ \
+ G---H---I---J
+ \
+ L--M
+-----------------------------------------------------------------------
+
The '\--simplify-by-decoration' option allows you to view only the
big picture of the topology of the history, by omitting commits
that are not referenced by tags. Commits are marked as !TREESAME
# set GIT_PS1_SHOWUNTRACKEDFILES to a nonempty value. If there're
# untracked files, then a '%' will be shown next to the branch name.
#
+ # If you would like to see the difference between HEAD and its
+ # upstream, set GIT_PS1_SHOWUPSTREAM="auto". A "<" indicates
+ # you are behind, ">" indicates you are ahead, and "<>"
+ # indicates you have diverged. You can further control
+ # behaviour by setting GIT_PS1_SHOWUPSTREAM to a space-separated
+ # list of values:
+ # verbose show number of commits ahead/behind (+/-) upstream
+ # legacy don't use the '--count' option available in recent
+ # versions of git-rev-list
+ # git always compare HEAD to @{upstream}
+ # svn always compare HEAD to your SVN upstream
+ # By default, __git_ps1 will compare HEAD to your SVN upstream
+ # if it can find one, or @{upstream} otherwise. Once you have
+ # set GIT_PS1_SHOWUPSTREAM, you can override it on a
+ # per-repository basis by setting the bash.showUpstream config
+ # variable.
+ #
+ #
# To submit patches:
#
# *) Read Documentation/SubmittingPatches
fi
}
+ # stores the divergence from upstream in $p
+ # used by GIT_PS1_SHOWUPSTREAM
+ __git_ps1_show_upstream ()
+ {
+ local key value
+ local svn_remote=() svn_url_pattern count n
+ local upstream=git legacy="" verbose=""
+
+ # get some config options from git-config
+ while read key value; do
+ case "$key" in
+ bash.showupstream)
+ GIT_PS1_SHOWUPSTREAM="$value"
+ if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+ p=""
+ return
+ fi
+ ;;
+ svn-remote.*.url)
+ svn_remote[ $((${#svn_remote[@]} + 1)) ]="$value"
+ svn_url_pattern+="\\|$value"
+ upstream=svn+git # default upstream is SVN if available, else git
+ ;;
+ esac
+ done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+
+ # parse configuration values
+ for option in ${GIT_PS1_SHOWUPSTREAM}; do
+ case "$option" in
+ git|svn) upstream="$option" ;;
+ verbose) verbose=1 ;;
+ legacy) legacy=1 ;;
+ esac
+ done
+
+ # Find our upstream
+ case "$upstream" in
+ git) upstream="@{upstream}" ;;
+ svn*)
+ # get the upstream from the "git-svn-id: ..." in a commit message
+ # (git-svn uses essentially the same procedure internally)
+ local svn_upstream=($(git log --first-parent -1 \
+ --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+ if [[ 0 -ne ${#svn_upstream[@]} ]]; then
+ svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
+ svn_upstream=${svn_upstream%@*}
+ for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+ svn_upstream=${svn_upstream#${svn_remote[$n]}}
+ done
+
+ if [[ -z "$svn_upstream" ]]; then
+ # default branch name for checkouts with no layout:
+ upstream=${GIT_SVN_ID:-git-svn}
+ else
+ upstream=${svn_upstream#/}
+ fi
+ elif [[ "svn+git" = "$upstream" ]]; then
+ upstream="@{upstream}"
+ fi
+ ;;
+ esac
+
+ # Find how many commits we are ahead/behind our upstream
+ if [[ -z "$legacy" ]]; then
+ count="$(git rev-list --count --left-right \
+ "$upstream"...HEAD 2>/dev/null)"
+ else
+ # produce equivalent output to --count for older versions of git
+ local commits
+ if commits="$(git rev-list --left-right "$upstream"...HEAD 2>/dev/null)"
+ then
+ local commit behind=0 ahead=0
+ for commit in $commits
+ do
+ case "$commit" in
+ "<"*) let ++behind
+ ;;
+ *) let ++ahead
+ ;;
+ esac
+ done
+ count="$behind $ahead"
+ else
+ count=""
+ fi
+ fi
+
+ # calculate the result
+ if [[ -z "$verbose" ]]; then
+ case "$count" in
+ "") # no upstream
+ p="" ;;
+ "0 0") # equal to upstream
+ p="=" ;;
+ "0 "*) # ahead of upstream
+ p=">" ;;
+ *" 0") # behind upstream
+ p="<" ;;
+ *) # diverged from upstream
+ p="<>" ;;
+ esac
+ else
+ case "$count" in
+ "") # no upstream
+ p="" ;;
+ "0 0") # equal to upstream
+ p=" u=" ;;
+ "0 "*) # ahead of upstream
+ p=" u+${count#0 }" ;;
+ *" 0") # behind upstream
+ p=" u-${count% 0}" ;;
+ *) # diverged from upstream
+ p=" u+${count#* }-${count% *}" ;;
+ esac
+ fi
+
+ }
+
+
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
# returns text to add to bash PS1 prompt (includes branch name)
__git_ps1 ()
{
local g="$(__gitdir)"
if [ -n "$g" ]; then
- local r
- local b
+ local r=""
+ local b=""
if [ -f "$g/rebase-merge/interactive" ]; then
r="|REBASE-i"
b="$(cat "$g/rebase-merge/head-name")"
}
fi
- local w
- local i
- local s
- local u
- local c
+ local w=""
+ local i=""
+ local s=""
+ local u=""
+ local c=""
+ local p=""
if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
u="%"
fi
fi
+
+ if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+ __git_ps1_show_upstream
+ fi
fi
local f="$w$i$s$u"
- printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
+ printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
fi
}
--*)
__gitcomp "
--quiet --ours --theirs --track --no-track --merge
- --conflict= --patch
+ --conflict= --orphan --patch
"
;;
*)
--numbered --start-number
--numbered-files
--keep-subject
- --signoff
+ --signoff --signature --no-signature
--in-reply-to= --cc=
--full-index --binary
--not --all
format.headers
format.numbered
format.pretty
+ format.signature
format.signoff
format.subjectprefix
format.suffix
return slop-1;
}
+/*
+ * "rev-list --ancestry-path A..B" computes commits that are ancestors
+ * of B but not ancestors of A but further limits the result to those
+ * that are descendants of A. This takes the list of bottom commits and
+ * the result of "A..B" without --ancestry-path, and limits the latter
+ * further to the ones that can reach one of the commits in "bottom".
+ */
+static void limit_to_ancestry(struct commit_list *bottom, struct commit_list *list)
+{
+ struct commit_list *p;
+ struct commit_list *rlist = NULL;
+ int made_progress;
+
+ /*
+ * Reverse the list so that it will be likely that we would
+ * process parents before children.
+ */
+ for (p = list; p; p = p->next)
+ commit_list_insert(p->item, &rlist);
+
+ for (p = bottom; p; p = p->next)
+ p->item->object.flags |= TMP_MARK;
+
+ /*
+ * Mark the ones that can reach bottom commits in "list",
+ * in a bottom-up fashion.
+ */
+ do {
+ made_progress = 0;
+ for (p = rlist; p; p = p->next) {
+ struct commit *c = p->item;
+ struct commit_list *parents;
+ if (c->object.flags & (TMP_MARK | UNINTERESTING))
+ continue;
+ for (parents = c->parents;
+ parents;
+ parents = parents->next) {
+ if (!(parents->item->object.flags & TMP_MARK))
+ continue;
+ c->object.flags |= TMP_MARK;
+ made_progress = 1;
+ break;
+ }
+ }
+ } while (made_progress);
+
+ /*
+ * NEEDSWORK: decide if we want to remove parents that are
+ * not marked with TMP_MARK from commit->parents for commits
+ * in the resulting list. We may not want to do that, though.
+ */
+
+ /*
+ * The ones that are not marked with TMP_MARK are uninteresting
+ */
+ for (p = list; p; p = p->next) {
+ struct commit *c = p->item;
+ if (c->object.flags & TMP_MARK)
+ continue;
+ c->object.flags |= UNINTERESTING;
+ }
+
+ /* We are done with the TMP_MARK */
+ for (p = list; p; p = p->next)
+ p->item->object.flags &= ~TMP_MARK;
+ for (p = bottom; p; p = p->next)
+ p->item->object.flags &= ~TMP_MARK;
+ free_commit_list(rlist);
+}
+
+/*
+ * Before walking the history, keep the set of "negative" refs the
+ * caller has asked to exclude.
+ *
+ * This is used to compute "rev-list --ancestry-path A..B", as we need
+ * to filter the result of "A..B" further to the ones that can actually
+ * reach A.
+ */
+static struct commit_list *collect_bottom_commits(struct commit_list *list)
+{
+ struct commit_list *elem, *bottom = NULL;
+ for (elem = list; elem; elem = elem->next)
+ if (elem->item->object.flags & UNINTERESTING)
+ commit_list_insert(elem->item, &bottom);
+ return bottom;
+}
+
static int limit_list(struct rev_info *revs)
{
int slop = SLOP;
struct commit_list *list = revs->commits;
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
+ struct commit_list *bottom = NULL;
+
+ if (revs->ancestry_path) {
+ bottom = collect_bottom_commits(list);
+ if (!bottom)
+ die("--ancestry-path given but there are no bottom commits");
+ }
while (list) {
struct commit_list *entry = list;
if (revs->cherry_pick)
cherry_pick_list(newlist, revs);
+ if (bottom) {
+ limit_to_ancestry(bottom, newlist);
+ free_commit_list(bottom);
+ }
+
revs->commits = newlist;
return 0;
}
if (!prefixcmp(arg, "--max-count=")) {
revs->max_count = atoi(arg + 12);
+ revs->no_walk = 0;
} else if (!prefixcmp(arg, "--skip=")) {
revs->skip_count = atoi(arg + 7);
} else if ((*arg == '-') && isdigit(arg[1])) {
/* accept -<digit>, like traditional "head" */
revs->max_count = atoi(arg + 1);
+ revs->no_walk = 0;
} else if (!strcmp(arg, "-n")) {
if (argc <= 1)
return error("-n requires an argument");
revs->max_count = atoi(argv[1]);
+ revs->no_walk = 0;
return 2;
} else if (!prefixcmp(arg, "-n")) {
revs->max_count = atoi(arg + 2);
+ revs->no_walk = 0;
} else if (!prefixcmp(arg, "--max-age=")) {
revs->max_age = atoi(arg + 10);
} else if (!prefixcmp(arg, "--since=")) {
revs->min_age = approxidate(arg + 8);
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
+ } else if (!strcmp(arg, "--ancestry-path")) {
+ revs->ancestry_path = 1;
+ revs->simplify_history = 0;
+ revs->limited = 1;
} else if (!strcmp(arg, "-g") || !strcmp(arg, "--walk-reflogs")) {
init_reflog_walk(&revs->reflog_info);
} else if (!strcmp(arg, "--default")) {
revs->boundary = 1;
} else if (!strcmp(arg, "--left-right")) {
revs->left_right = 1;
+ } else if (!strcmp(arg, "--count")) {
+ revs->count = 1;
} else if (!strcmp(arg, "--cherry-pick")) {
revs->cherry_pick = 1;
revs->limited = 1;
enum rewrite_result {
rewrite_one_ok,
rewrite_one_noparents,
- rewrite_one_error,
+ rewrite_one_error
};
static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp)
limited:1,
unpacked:1,
boundary:2,
+ count:1,
left_right:1,
rewrite_parents:1,
print_parents:1,
reverse_output_stage:1,
cherry_pick:1,
bisect:1,
+ ancestry_path:1,
first_parent_only:1;
/* Diff flags */
/* notes-specific options: which refs to show */
struct display_notes_opt notes_opt;
+
+ /* commit counts */
+ int count_left;
+ int count_right;
};
#define REV_TREE_SAME 0