From: Junio C Hamano Date: Wed, 30 Jun 2010 18:55:38 +0000 (-0700) Subject: Merge branch 'tr/rev-list-count' X-Git-Tag: v1.7.2-rc1~9 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/6296062285e07051a5a46cbbd74c67a10bf2ac16?ds=inline;hp=-c Merge branch 'tr/rev-list-count' * tr/rev-list-count: bash completion: Support "divergence from upstream" messages in __git_ps1 rev-list: introduce --count option Conflicts: contrib/completion/git-completion.bash --- 6296062285e07051a5a46cbbd74c67a10bf2ac16 diff --combined Documentation/rev-list-options.txt index 73569c073e,066ade9fce..cc562a057a --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@@ -98,6 -98,15 +98,15 @@@ you would get an output like this 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 ~~~~~~~~~~~~~~~ @@@ -384,14 -393,6 +393,14 @@@ Default mode: 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 . We shall call commits @@@ -448,7 -449,7 +457,7 @@@ This results in + ----------------------------------------------------------------------- .-A---N---O - / / + / / / I---------D ----------------------------------------------------------------------- + @@@ -519,6 -520,8 +528,6 @@@ Note that without '\--full-history', th 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 @@@ -560,46 -563,6 +569,46 @@@ Note the major differences in `N` and ` 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 diff --combined contrib/completion/git-completion.bash index bc9df12bf1,550985daf5..67569901e7 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@@ -42,6 -42,24 +42,24 @@@ # 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 @@@ -78,14 -96,133 +96,133 @@@ __gitdir ( 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")" @@@ -127,11 -264,12 +264,12 @@@ } 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 @@@ -159,10 -297,14 +297,14 @@@ 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 } @@@ -842,7 -984,7 +984,7 @@@ _git_checkout ( --*) __gitcomp " --quiet --ours --theirs --track --no-track --merge - --conflict= --patch + --conflict= --orphan --patch " ;; *) @@@ -1052,7 -1194,7 +1194,7 @@@ _git_format_patch ( --numbered --start-number --numbered-files --keep-subject - --signoff + --signoff --signature --no-signature --in-reply-to= --cc= --full-index --binary --not --all @@@ -1726,7 -1868,6 +1868,7 @@@ _git_config ( format.headers format.numbered format.pretty + format.signature format.signoff format.subjectprefix format.suffix diff --combined revision.c index 540358184d,21b133c89b..527ec34e63 --- a/revision.c +++ b/revision.c @@@ -646,93 -646,6 +646,93 @@@ static int still_interesting(struct com 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; @@@ -740,13 -653,6 +740,13 @@@ 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; @@@ -788,11 -694,6 +788,11 @@@ 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; } @@@ -1162,22 -1063,18 +1162,22 @@@ static int handle_revision_opt(struct r 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 -, 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=")) { @@@ -1192,10 -1089,6 +1192,10 @@@ 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")) { @@@ -1253,6 -1146,8 +1253,8 @@@ 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; @@@ -1888,7 -1783,7 +1890,7 @@@ int prepare_revision_walk(struct rev_in 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) diff --combined revision.h index 855464f144,bafa728083..36fdf22b29 --- a/revision.h +++ b/revision.h @@@ -57,6 -57,7 +57,7 @@@ struct rev_info limited:1, unpacked:1, boundary:2, + count:1, left_right:1, rewrite_parents:1, print_parents:1, @@@ -66,7 -67,6 +67,7 @@@ reverse_output_stage:1, cherry_pick:1, bisect:1, + ancestry_path:1, first_parent_only:1; /* Diff flags */ @@@ -132,6 -132,10 +133,10 @@@ /* 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