Merge branch 'sg/fix-versioncmp-with-common-suffix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 23 Jan 2017 23:59:21 +0000 (15:59 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 23 Jan 2017 23:59:21 +0000 (15:59 -0800)
The prereleaseSuffix feature of version comparison that is used in
"git tag -l" did not correctly when two or more prereleases for the
same release were present (e.g. when 2.0, 2.0-beta1, and 2.0-beta2
are there and the code needs to compare 2.0-beta1 and 2.0-beta2).

* sg/fix-versioncmp-with-common-suffix:
versioncmp: generalize version sort suffix reordering
versioncmp: factor out helper for suffix matching
versioncmp: use earliest-longest contained suffix to determine sorting order
versioncmp: cope with common part overlapping with prerelease suffix
versioncmp: pass full tagnames to swap_prereleases()
t7004-tag: add version sort tests to show prerelease reordering issues
t7004-tag: use test_config helper
t7004-tag: delete unnecessary tags with test_when_finished

1  2 
Documentation/config.txt
Documentation/git-tag.txt
t/t7004-tag.sh
diff --combined Documentation/config.txt
index 506431267ed0ef15bce2570d3f1bf69e7268e8f0,0573f1f866570286cbb0a40f16e9bf5b6d0ad48c..af2ae4cc02af75c5cf9395e228ff8bbc6e390181
@@@ -783,11 -783,10 +783,11 @@@ core.sparseCheckout:
        linkgit:git-read-tree[1] for more information.
  
  core.abbrev::
 -      Set the length object names are abbreviated to.  If unspecified,
 -      many commands abbreviate to 7 hexdigits, which may not be enough
 -      for abbreviated object names to stay unique for sufficiently long
 -      time.
 +      Set the length object names are abbreviated to.  If
 +      unspecified or set to "auto", an appropriate value is
 +      computed based on the approximate number of packed objects
 +      in your repository, which hopefully is enough for
 +      abbreviated object names to stay unique for some time.
  
  add.ignoreErrors::
  add.ignore-errors (deprecated)::
@@@ -1410,9 -1409,7 +1410,9 @@@ gc.pruneExpire:
        Override the grace period with this config variable.  The value
        "now" may be used to disable this grace period and always prune
        unreachable objects immediately, or "never" may be used to
 -      suppress pruning.
 +      suppress pruning.  This feature helps prevent corruption when
 +      'git gc' runs concurrently with another process writing to the
 +      repository; see the "NOTES" section of linkgit:git-gc[1].
  
  gc.worktreePruneExpire::
        When 'git gc' is run, it calls
@@@ -1894,16 -1891,6 +1894,16 @@@ http.userAgent:
        of common USER_AGENT strings (but not including those like git/1.7.1).
        Can be overridden by the `GIT_HTTP_USER_AGENT` environment variable.
  
 +http.followRedirects::
 +      Whether git should follow HTTP redirects. If set to `true`, git
 +      will transparently follow any redirect issued by a server it
 +      encounters. If set to `false`, git will treat all redirects as
 +      errors. If set to `initial`, git will follow redirects only for
 +      the initial request to a remote, but not for subsequent
 +      follow-up HTTP requests. Since git uses the redirected URL as
 +      the base for the follow-up requests, this is generally
 +      sufficient. The default is `initial`.
 +
  http.<url>.*::
        Any of the http.* options above can be applied selectively to some URLs.
        For a config key to match a URL, each element of the config key is
@@@ -2321,52 -2308,6 +2321,52 @@@ pretty.<name>:
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
 +protocol.allow::
 +      If set, provide a user defined default policy for all protocols which
 +      don't explicitly have a policy (`protocol.<name>.allow`).  By default,
 +      if unset, known-safe protocols (http, https, git, ssh, file) have a
 +      default policy of `always`, known-dangerous protocols (ext) have a
 +      default policy of `never`, and all other protocols have a default
 +      policy of `user`.  Supported policies:
 ++
 +--
 +
 +* `always` - protocol is always able to be used.
 +
 +* `never` - protocol is never able to be used.
 +
 +* `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is
 +  either unset or has a value of 1.  This policy should be used when you want a
 +  protocol to be directly usable by the user but don't want it used by commands which
 +  execute clone/fetch/push commands without user input, e.g. recursive
 +  submodule initialization.
 +
 +--
 +
 +protocol.<name>.allow::
 +      Set a policy to be used by protocol `<name>` with clone/fetch/push
 +      commands. See `protocol.allow` above for the available policies.
 ++
 +The protocol names currently used by git are:
 ++
 +--
 +  - `file`: any local file-based path (including `file://` URLs,
 +    or local paths)
 +
 +  - `git`: the anonymous git protocol over a direct TCP
 +    connection (or proxy, if configured)
 +
 +  - `ssh`: git over ssh (including `host:path` syntax,
 +    `ssh://`, etc).
 +
 +  - `http`: git over http, both "smart http" and "dumb http".
 +    Note that this does _not_ include `https`; if you want to configure
 +    both, you must do so individually.
 +
 +  - any external helpers are named by their protocol (e.g., use
 +    `hg` to allow the `git-remote-hg` helper)
 +--
 +
  pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
@@@ -2989,11 -2930,6 +2989,11 @@@ is omitted from the advertisements but 
  `refs/namespaces/bar/refs/heads/master` are still advertised as so-called
  "have" lines. In order to match refs before stripping, add a `^` in front of
  the ref name. If you combine `!` and `^`, `!` must be specified first.
 ++
 +Even if you hide refs, a client may still be able to steal the target
 +objects via the techniques described in the "SECURITY" section of the
 +linkgit:gitnamespaces[7] man page; it's best to keep private data in a
 +separate repository.
  
  transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
  uploadarchive.allowUnreachable::
        If true, allow clients to use `git archive --remote` to request
        any tree, whether reachable from the ref tips or not. See the
 -      discussion in the `SECURITY` section of
 +      discussion in the "SECURITY" section of
        linkgit:git-upload-archive[1] for more details. Defaults to
        `false`.
  
@@@ -3017,23 -2953,12 +3017,23 @@@ uploadpack.allowTipSHA1InWant:
        When `uploadpack.hideRefs` is in effect, allow `upload-pack`
        to accept a fetch request that asks for an object at the tip
        of a hidden ref (by default, such a request is rejected).
 -      see also `uploadpack.hideRefs`.
 +      See also `uploadpack.hideRefs`.  Even if this is false, a client
 +      may be able to steal objects via the techniques described in the
 +      "SECURITY" section of the linkgit:gitnamespaces[7] man page; it's
 +      best to keep private data in a separate repository.
  
  uploadpack.allowReachableSHA1InWant::
        Allow `upload-pack` to accept a fetch request that asks for an
        object that is reachable from any ref tip. However, note that
        calculating object reachability is computationally expensive.
 +      Defaults to `false`.  Even if this is false, a client may be able
 +      to steal objects via the techniques described in the "SECURITY"
 +      section of the linkgit:gitnamespaces[7] man page; it's best to
 +      keep private data in a separate repository.
 +
 +uploadpack.allowAnySHA1InWant::
 +      Allow `upload-pack` to accept a fetch request that asks for any
 +      object at all.
        Defaults to `false`.
  
  uploadpack.keepAlive::
@@@ -3113,17 -3038,39 +3113,39 @@@ user.signingKey:
        This option is passed unchanged to gpg's --local-user parameter,
        so you may specify a key using any method that gpg supports.
  
- versionsort.prereleaseSuffix::
-       When version sort is used in linkgit:git-tag[1], prerelease
-       tags (e.g. "1.0-rc1") may appear after the main release
-       "1.0". By specifying the suffix "-rc" in this variable,
-       "1.0-rc1" will appear before "1.0".
- +
- This variable can be specified multiple times, once per suffix. The
- order of suffixes in the config file determines the sorting order
- (e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX
- is sorted before 1.0-rcXX). The sorting order between different
- suffixes is undefined if they are in multiple config files.
+ versionsort.prereleaseSuffix (deprecated)::
+       Deprecated alias for `versionsort.suffix`.  Ignored if
+       `versionsort.suffix` is set.
+ versionsort.suffix::
+       Even when version sort is used in linkgit:git-tag[1], tagnames
+       with the same base version but different suffixes are still sorted
+       lexicographically, resulting e.g. in prerelease tags appearing
+       after the main release (e.g. "1.0-rc1" after "1.0").  This
+       variable can be specified to determine the sorting order of tags
+       with different suffixes.
+ +
+ By specifying a single suffix in this variable, any tagname containing
+ that suffix will appear before the corresponding main release.  E.g. if
+ the variable is set to "-rc", then all "1.0-rcX" tags will appear before
+ "1.0".  If specified multiple times, once per suffix, then the order of
+ suffixes in the configuration will determine the sorting order of tagnames
+ with those suffixes.  E.g. if "-pre" appears before "-rc" in the
+ configuration, then all "1.0-preX" tags will be listed before any
+ "1.0-rcX" tags.  The placement of the main release tag relative to tags
+ with various suffixes can be determined by specifying the empty suffix
+ among those other suffixes.  E.g. if the suffixes "-rc", "", "-ck" and
+ "-bfs" appear in the configuration in this order, then all "v4.8-rcX" tags
+ are listed first, followed by "v4.8", then "v4.8-ckX" and finally
+ "v4.8-bfsX".
+ +
+ If more than one suffixes match the same tagname, then that tagname will
+ be sorted according to the suffix which starts at the earliest position in
+ the tagname.  If more than one different matching suffixes start at
+ that earliest position, then that tagname will be sorted according to the
+ longest of those suffixes.
+ The sorting order between different suffixes is undefined if they are
+ in multiple config files.
  
  web.browser::
        Specify a web browser that may be used by some commands.
index 76cfe40d969bc5f37dce674f43857ec89f25ae67,44c956c67bb490e3ead55ecd7aa9d1475282e08b..5055a9682393409c1ed07e1e417015f46e46a5bc
@@@ -101,17 -101,13 +101,17 @@@ OPTION
        multiple times, in which case the last key becomes the primary
        key. Also supports "version:refname" or "v:refname" (tag
        names are treated as versions). The "version:refname" sort
-       order can also be affected by the
-       "versionsort.prereleaseSuffix" configuration variable.
+       order can also be affected by the "versionsort.suffix"
+       configuration variable.
        The keys supported are the same as those in `git for-each-ref`.
        Sort order defaults to the value configured for the `tag.sort`
        variable if it exists, or lexicographic order otherwise. See
        linkgit:git-config[1].
  
 +-i::
 +--ignore-case::
 +      Sorting and filtering tags are case insensitive.
 +
  --column[=<options>]::
  --no-column::
        Display tag listing in columns. See configuration variable
diff --combined t/t7004-tag.sh
index 07869b0c09da1313dd5564c2318dc5d44818f3a9,bdd28dad14d6f6dd7730e2fff6a82dc9308f3a80..1cfa8a21d23173e51e95906c00342fa13b85d955
@@@ -27,30 -27,6 +27,30 @@@ test_expect_success 'listing all tags i
        test $(git tag | wc -l) -eq 0
  '
  
 +test_expect_success 'sort tags, ignore case' '
 +      (
 +              git init sort &&
 +              cd sort &&
 +              test_commit initial &&
 +              git tag tag-one &&
 +              git tag TAG-two &&
 +              git tag -l >actual &&
 +              cat >expected <<-\EOF &&
 +              TAG-two
 +              initial
 +              tag-one
 +              EOF
 +              test_cmp expected actual &&
 +              git tag -l -i >actual &&
 +              cat >expected <<-\EOF &&
 +              initial
 +              tag-one
 +              TAG-two
 +              EOF
 +              test_cmp expected actual
 +      )
 +'
 +
  test_expect_success 'looking for a tag in an empty tree should fail' \
        '! (tag_exists mytag)'
  
@@@ -105,9 -81,6 +105,9 @@@ test_expect_success 'listing all tags i
  test_expect_success 'listing a tag using a matching pattern should succeed' \
        'git tag -l mytag'
  
 +test_expect_success 'listing a tag with --ignore-case' \
 +      'test $(git tag -l --ignore-case MYTAG) = mytag'
 +
  test_expect_success \
        'listing a tag using a matching pattern should output that tag' \
        'test $(git tag -l mytag) = mytag'
@@@ -149,11 -122,11 +149,11 @@@ test_expect_success '--force can creat
        tag_exists mytag'
  
  test_expect_success '--force is moot with a non-existing tag name' '
+       test_when_finished git tag -d newtag forcetag &&
        git tag newtag >expect &&
        git tag --force forcetag >actual &&
        test_cmp expect actual
  '
- git tag -d newtag forcetag
  
  # deleting tags:
  
@@@ -324,11 -297,9 +324,9 @@@ EO
  '
  
  test_expect_success 'listing tags in column with column.*' '
-       git config column.tag row &&
-       git config column.ui dense &&
+       test_config column.tag row &&
+       test_config column.ui dense &&
        COLUMNS=40 git tag -l >actual &&
-       git config --unset column.ui &&
-       git config --unset column.tag &&
        cat >expected <<\EOF &&
  a1      aa1   cba     t210    t211
  v0.2.1  v1.0  v1.0.1  v1.1.3
@@@ -341,9 -312,8 +339,8 @@@ test_expect_success 'listing tag with -
  '
  
  test_expect_success 'listing tags -n in column with column.ui ignored' '
-       git config column.ui "row dense" &&
+       test_config column.ui "row dense" &&
        COLUMNS=40 git tag -l -n >actual &&
-       git config --unset column.ui &&
        cat >expected <<\EOF &&
  a1              Foo
  aa1             Foo
@@@ -1227,11 -1197,10 +1224,10 @@@ test_expect_success GPG,RFC1991 
  '
  
  # try to sign with bad user.signingkey
- git config user.signingkey BobTheMouse
  test_expect_success GPG \
        'git tag -s fails if gpg is misconfigured (bad key)' \
-       'test_must_fail git tag -s -m tail tag-gpg-failure'
- git config --unset user.signingkey
+       'test_config user.signingkey BobTheMouse &&
+       test_must_fail git tag -s -m tail tag-gpg-failure'
  
  # try to produce invalid signature
  test_expect_success GPG \
@@@ -1511,7 -1480,7 +1507,7 @@@ test_expect_success 'reverse lexical so
  '
  
  test_expect_success 'configured lexical sort' '
-       git config tag.sort "v:refname" &&
+       test_config tag.sort "v:refname" &&
        git tag -l "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.3
  '
  
  test_expect_success 'option override configured sort' '
+       test_config tag.sort "v:refname" &&
        git tag -l --sort=-refname "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.6
@@@ -1536,13 -1506,12 +1533,12 @@@ test_expect_success 'invalid sort param
  '
  
  test_expect_success 'invalid sort parameter in configuratoin' '
-       git config tag.sort "v:notvalid" &&
+       test_config tag.sort "v:notvalid" &&
        test_must_fail git tag -l "foo*"
  '
  
  test_expect_success 'version sort with prerelease reordering' '
-       git config --unset tag.sort &&
-       git config versionsort.prereleaseSuffix -rc &&
+       test_config versionsort.prereleaseSuffix -rc &&
        git tag foo1.6-rc1 &&
        git tag foo1.6-rc2 &&
        git tag -l --sort=version:refname "foo*" >actual &&
  '
  
  test_expect_success 'reverse version sort with prerelease reordering' '
+       test_config versionsort.prereleaseSuffix -rc &&
        git tag -l --sort=-version:refname "foo*" >actual &&
        cat >expect <<-\EOF &&
        foo1.10
        test_cmp expect actual
  '
  
+ test_expect_success 'version sort with prerelease reordering and common leading character' '
+       test_config versionsort.prereleaseSuffix -before &&
+       git tag foo1.7-before1 &&
+       git tag foo1.7 &&
+       git tag foo1.7-after1 &&
+       git tag -l --sort=version:refname "foo1.7*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.7-before1
+       foo1.7
+       foo1.7-after1
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'version sort with prerelease reordering, multiple suffixes and common leading character' '
+       test_config versionsort.prereleaseSuffix -before &&
+       git config --add versionsort.prereleaseSuffix -after &&
+       git tag -l --sort=version:refname "foo1.7*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.7-before1
+       foo1.7-after1
+       foo1.7
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'version sort with prerelease reordering, multiple suffixes match the same tag' '
+       test_config versionsort.prereleaseSuffix -bar &&
+       git config --add versionsort.prereleaseSuffix -foo-baz &&
+       git config --add versionsort.prereleaseSuffix -foo-bar &&
+       git tag foo1.8-foo-bar &&
+       git tag foo1.8-foo-baz &&
+       git tag foo1.8 &&
+       git tag -l --sort=version:refname "foo1.8*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.8-foo-baz
+       foo1.8-foo-bar
+       foo1.8
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'version sort with prerelease reordering, multiple suffixes match starting at the same position' '
+       test_config versionsort.prereleaseSuffix -pre &&
+       git config --add versionsort.prereleaseSuffix -prerelease &&
+       git tag foo1.9-pre1 &&
+       git tag foo1.9-pre2 &&
+       git tag foo1.9-prerelease1 &&
+       git tag -l --sort=version:refname "foo1.9*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.9-pre1
+       foo1.9-pre2
+       foo1.9-prerelease1
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'version sort with general suffix reordering' '
+       test_config versionsort.suffix -alpha &&
+       git config --add versionsort.suffix -beta &&
+       git config --add versionsort.suffix ""  &&
+       git config --add versionsort.suffix -gamma &&
+       git config --add versionsort.suffix -delta &&
+       git tag foo1.10-alpha &&
+       git tag foo1.10-beta &&
+       git tag foo1.10-gamma &&
+       git tag foo1.10-delta &&
+       git tag foo1.10-unlisted-suffix &&
+       git tag -l --sort=version:refname "foo1.10*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.10-alpha
+       foo1.10-beta
+       foo1.10
+       foo1.10-unlisted-suffix
+       foo1.10-gamma
+       foo1.10-delta
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'versionsort.suffix overrides versionsort.prereleaseSuffix' '
+       test_config versionsort.suffix -before &&
+       test_config versionsort.prereleaseSuffix -after &&
+       git tag -l --sort=version:refname "foo1.7*" >actual &&
+       cat >expect <<-\EOF &&
+       foo1.7-before1
+       foo1.7
+       foo1.7-after1
+       EOF
+       test_cmp expect actual
+ '
+ test_expect_success 'version sort with very long prerelease suffix' '
+       test_config versionsort.prereleaseSuffix -very-looooooooooooooooooooooooong-prerelease-suffix &&
+       git tag -l --sort=version:refname
+ '
  run_with_limited_stack () {
        (ulimit -s 128 && "$@")
  }
  
  test_expect_success '--format should list tags as per format given' '
        cat >expect <<-\EOF &&
-       refname : refs/tags/foo1.10
-       refname : refs/tags/foo1.3
-       refname : refs/tags/foo1.6
-       refname : refs/tags/foo1.6-rc1
-       refname : refs/tags/foo1.6-rc2
+       refname : refs/tags/v1.0
+       refname : refs/tags/v1.0.1
+       refname : refs/tags/v1.1.3
        EOF
-       git tag -l --format="refname : %(refname)" "foo*" >actual &&
+       git tag -l --format="refname : %(refname)" "v1*" >actual &&
        test_cmp expect actual
  '