Skip to content

Allow classifier to run on symlinks as usual - #3948

Merged
kivikakk merged 2 commits into
masterfrom
kivikakk/no-traverse-symlink-fix
Jan 7, 2018
Merged

kivikakk merged 2 commits into
masterfrom
kivikakk/no-traverse-symlink-fix

Conversation

@kivikakk

Copy link
Copy Markdown
Contributor

When deciding between "GCC Machine Description" and "Markdown" for .md, and a symlink is involved, we always choose "GCC Machine Description".

In general, if we've gotten to the end of all strategies and we're choosing between these two — i.e. we've deemed it to be "equal probability" that it's either one or the other — however we've determined this, we should choose Markdown as the more likely of the two.

/cc @lildude Definitely open to other ways of doing this.

@lildude

lildude commented Dec 13, 2017 •

Copy link
Copy Markdown
Member

I've thought of this quite recently and came up with a similar idea, however my reluctance with going this route is the one question: who decides on the weighting?

We might think Markdown is more important than "GCC Machine Description", however a "GCC Machine Description" dev without a single markdown file in his repo is going to argue the other way.

I picture this becoming quite a battle when it comes to C/C++/Objective-C and their constant battle with misclassification of header files.

I'm not completely averse to the idea of a weighting, I just want to be sure we've got a good strategy to deal with how we determine this weighting.

A quick thought (without much other thought 😉 )... with regards to following the symlinks which is causing this problem, why not bail really early with no results when we encounter a symlink? Effectively completely ignoring the symlink. The destination, if in the repo, should be assessed in its own right and if the destination isn't in the repo, it doesn't count anyway.

@kivikakk

Copy link
Copy Markdown
Contributor Author

who decides the weighting?

Yes, that's a very good point. However;

I picture this becoming quite a battle when it comes to C/C++/Objective-C and their constant battle with misclassification of header files.

In this case the Bayesian classifier will always choose a winner for us, and we can just let that do its thing. I envision this as a way to resolve ties when there aren't other good means.

In this case, we're not following symlink contents because we're accepting that the symlink is a way people use to render a given file as a different filename.

The example here is: there's a blah.pm Perl module, but they want its embedded POD to be the README, so they symlink it to README.pod. We have to avoid using the file content because that would choose Perl. I think this answers the last question re: bailing early when encountering the symlink.

I'm not completely averse to the idea of a weighting, I just want to be sure we've got a good strategy to deal with how we determine this weighting.

I definitely agree, and I'm not exactly sure what that would look like, and am definitely open to other methods of doing this. We could just do something similar to the "popular" list and bless certain filetypes such as Markdown.

@pchaigno

Copy link
Copy Markdown
Contributor

When deciding between "GCC Machine Description" and "Markdown" for .md, and a symlink is involved, we always choose "GCC Machine Description".

If we want to choose Markdown at the end, why even go through the Bayesian classifier? We could bail early at the heuristic strategy with Markdown as a default result for .md files... Am I missing something?

@kivikakk

Copy link
Copy Markdown
Contributor Author

If we want to choose Markdown at the end, why even go through the Bayesian classifier? We could bail early at the heuristic strategy with Markdown as a default result for .md files... Am I missing something?

Well, for non-symlink files, we do want to consider the content, and there are some legitimate GCC Machine Description files which the classifier will correctly identify. We just want to resolve a tie in case of not considering the content, such as with symlinks. We don't go through the classifier at all with symlinks presently.

@pchaigno

Copy link
Copy Markdown
Contributor

As far as I understand, since the Bayesian classifier always returns the complete list of candidate languages ordered by scores (i.e., it doesn't exclude any candidate language), the following will always return the language with the highest weight, regardless of the score assigned by the classifier:

ls = languages.each_with_index.sort_by { |l, idx| [-l.weight, idx] }.first
ls[0] if ls

@kivikakk

Copy link
Copy Markdown
Contributor Author

Ah, of course, you're quite right. I'll have to think of a better solution here.

@kivikakk

Copy link
Copy Markdown
Contributor Author

Clone of 'https://github.com/httpspec/sublime-highlighting' into submodule path 'vendor/grammars/Sublime-HTTP' failed

User httpspec has disappeared.

@kivikakk

Copy link
Copy Markdown
Contributor Author

Since the classifier runs last, I've tried having it simply return its first candidate only. This feels like a hack, but I'm not sure there's a strict downside.

@lildude

lildude commented Jan 3, 2018

Copy link
Copy Markdown
Member

Sorry to leave this hanging around for so long. I've been playing around with the changes you introduced in #3946 and one thing I'm not clear on is why we're exiting early in the classifier if it's a symlink?

I've done some testing with master as it stands after #3946 has been merged, and by removing this line I can get Linguist to always correctly identify the file through a symlink.

For example, symlinking samples/Markdown/README.mdown -> symlink.md as in this PR (I've added a bit of extra crude debugging to see what strategy is being used and the languages passed down):

$ ls -l samples/Markdown/symlink.md
lrwxr-xr-x  1 lildude  staff  12  3 Jan 09:39 samples/Markdown/symlink.md -> README.mdown
$ LINGUIST_DEBUG=2 bin/linguist samples/Markdown/symlink.md
symlink.md: 20 lines (13 sloc)
  type:      Text
  mime type: text/plain
Using Linguist::Strategy::Modeline...
[]
Using Linguist::Strategy::Filename...
[]
Using Linguist::Shebang...
[]
Using Linguist::Strategy::Extension...
[#<Linguist::Language name=GCC Machine Description>, #<Linguist::Language name=Markdown>]
Using Linguist::Heuristics...
[#<Linguist::Language name=GCC Machine Description>, #<Linguist::Language name=Markdown>]
Using Linguist::Classifier...
                 #GCC Machine Description  Markdown
            (    5     6.321         -
            )    5     6.172         -
[... truncated for brevity ...]
          you    3         -    27.157
         you.    1         -     7.443
GCC Machine Description =  -1871.359 +  -7.575 =  -1878.934
  Markdown =  -1012.544 +  -6.189 =  -1018.733
[#<Linguist::Language name=Markdown>, #<Linguist::Language name=GCC Machine Description>]
  language:  Markdown   <--- Correct language, without the need for weighting.
$

Another example, using the Perl/POD file from github/markup#1133 as readme.pl and symlinking foo.pod to it:

$ ls -l samples/Pod/foo.pod
lrwxr-xr-x  1 lildude  staff  9  3 Jan 10:17 samples/Pod/foo.pod -> readme.pl
$ $ LINGUIST_DEBUG=2 bin/linguist samples/Pod/foo.pod
foo.pod: 91 lines (58 sloc)
  type:      Text
  mime type: text/plain
Using Linguist::Strategy::Modeline...
[]
Using Linguist::Strategy::Filename...
[]
Using Linguist::Shebang...
[]
Using Linguist::Strategy::Extension...
  language:  Pod
$

... and checking the original file:

$ LINGUIST_DEBUG=2 bin/linguist samples/Pod/readme.pl
readme.pl: 91 lines (58 sloc)
  type:      Text
  mime type: application/x-perl
Using Linguist::Strategy::Modeline...
  language:  Perl
$

So as I understand it, things are operating as expected if we don't exit the classifier early if it's a symlink, however I suspect I'm missing something somewhere.

@kivikakk

kivikakk commented Jan 3, 2018

Copy link
Copy Markdown
Contributor Author

I suspect I'm missing something somewhere, but your results check out, so let's try running with that. I'll cook up a PR.

@kivikakk
kivikakk force-pushed the kivikakk/no-traverse-symlink-fix branch from 067b8cc to 0a040fa Compare January 3, 2018 22:28
@kivikakk

kivikakk commented Jan 3, 2018

Copy link
Copy Markdown
Contributor Author

PR force-pushed per your suggestion. This looks like it'll check out. 👍

@kivikakk kivikakk changed the title Weight results when heuristics alone aren't enough Allow classifier to run on symlinks as usual Jan 4, 2018
Comment thread test/test_heuristics.rb
["#{samples_path}/#{language_name}/filenames"]
fixs = Dir.glob("#{samples_path}/#{language_name}/#{file}") -
["#{samples_path}/#{language_name}/filenames"]
fixs.reject { |f| File.symlink?(f) }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to filter out the symlinks during testing? Wouldn't doing so effectively mean we never test a symlink with the heuristics?

Additionally, I think we need a specific heuristics test for symlinks to ensure the heuristics are being skipped for them.

@kivikakk kivikakk Jan 4, 2018 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is the test symlink's placement in the Markdown directory means we assume it should return Markdown as the result, whereas we skip heuristics instead and return nothing. Before this change to the test, we get this output:

  1) Failure:
TestHeuristics#test_md_by_heuristics [/Users/kivikakk/github/linguist/test/test_heuristics.rb:191]:
Failed for /Users/kivikakk/github/linguist/samples/Markdown/symlink.md.
--- expected
+++ actual
@@ -1 +1 @@
-[#<Linguist::Language name=Markdown>]
+[]

I'll add an explicit test too.

@lildude lildude left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks for the explanation about the test too.

@pchaigno

pchaigno commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

I might have missed something, but why don't we want heuristic rules to run on symlinks?

@kivikakk

kivikakk commented Jan 7, 2018

Copy link
Copy Markdown
Contributor Author

Thanks @lildude!

@pchaigno, symlinks are used reasonably often in GitHub to e.g. reuse a .pl file as README.pod. We want to ignore the content of the symlink target, in this case, and only honour the filename.

@kivikakk
kivikakk merged commit 5fbe9c0 into master Jan 7, 2018
@kivikakk
kivikakk deleted the kivikakk/no-traverse-symlink-fix branch January 7, 2018 22:02
@lildude lildude mentioned this pull request Jan 26, 2018
@github-linguist github-linguist locked as resolved and limited conversation to collaborators Jun 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants