Merge branch 'tr/rev-list-count'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 Jun 2010 18:55:38 +0000 (11:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 Jun 2010 18:55:38 +0000 (11:55 -0700)
* 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

1  2 
Documentation/rev-list-options.txt
contrib/completion/git-completion.bash
revision.c
revision.h
index 73569c073e7806b727e21eeb168814082195a75d,066ade9fce33b70898c1bf411319f1f8b632d44f..cc562a057af3694d3518bc91b52ae322b6b1d9cc
@@@ -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 <paths>.  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
index bc9df12bf10bdcc3942eff4dc674d6d7440b3447,550985daf563ed47fe7eed7faaaf2d9d09c42727..67569901e71e5062199e48304afc424f15b57ba1
  #       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")"
                        }
                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
  }
  
@@@ -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 540358184d7f05e57b183ac0f4e5658e34b2a611,21b133c89bd481b2ac1fa5076c637336b8f57eb1..527ec34e63122807137346c1f8e477d17d675c81
@@@ -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;
        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;
  }
@@@ -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 -<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;
@@@ -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 855464f1441654c1938135d6a99b247c70826146,bafa72808342919485cc0590a66ffc5ef5c96c82..36fdf22b299c4cc7e6f20d767d5c6a6c3f69d952
@@@ -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 */
  
        /* 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