line_ending.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. # line_ending.py -- Line ending conversion functions
  2. # Copyright (C) 2018-2018 Boris Feld <boris.feld@comet.ml>
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """ All line-ending related functions, from conversions to config processing
  21. Line-ending normalization is a complex beast. Here is some notes and details
  22. about how it seems to work.
  23. The normalization is a two-fold process that happens at two moments:
  24. - When reading a file from the index and to the working directory. For example
  25. when doing a `git clone` or `git checkout` call. We call this process the
  26. read filter in this module.
  27. - When writing a file to the index from the working directory. For example
  28. when doing a `git add` call. We call this process the write filter in this
  29. module.
  30. One thing to know is that Git does line-ending normalization only on text
  31. files. How does Git know that a file is text? We can either mark a file as a
  32. text file, a binary file or ask Git to automatically decides. Git has an
  33. heuristic to detect if a file is a text file or a binary file. It seems based
  34. on the percentage of non-printable characters in files.
  35. The code for this heuristic is here:
  36. https://git.kernel.org/pub/scm/git/git.git/tree/convert.c#n46
  37. Dulwich have an implementation with a slightly different heuristic, the
  38. `is_binary` function in `dulwich.patch`.
  39. The binary detection heuristic implementation is close to the one in JGit:
  40. https://github.com/eclipse/jgit/blob/f6873ffe522bbc3536969a3a3546bf9a819b92bf/org.eclipse.jgit/src/org/eclipse/jgit/diff/RawText.java#L300
  41. There is multiple variables that impact the normalization.
  42. First, a repository can contains a `.gitattributes` file (or more than one...)
  43. that can further customize the operation on some file patterns, for example:
  44. *.txt text
  45. Force all `.txt` files to be treated as text files and to have their lines
  46. endings normalized.
  47. *.jpg -text
  48. Force all `.jpg` files to be treated as binary files and to not have their
  49. lines endings converted.
  50. *.vcproj text eol=crlf
  51. Force all `.vcproj` files to be treated as text files and to have their lines
  52. endings converted into `CRLF` in working directory no matter the native EOL of
  53. the platform.
  54. *.sh text eol=lf
  55. Force all `.sh` files to be treated as text files and to have their lines
  56. endings converted into `LF` in working directory no matter the native EOL of
  57. the platform.
  58. If the `eol` attribute is not defined, Git uses the `core.eol` configuration
  59. value described later.
  60. * text=auto
  61. Force all files to be scanned by the text file heuristic detection and to have
  62. their line endings normalized in case they are detected as text files.
  63. Git also have a obsolete attribute named `crlf` that can be translated to the
  64. corresponding text attribute value.
  65. Then there are some configuration option (that can be defined at the
  66. repository or user level):
  67. - core.autocrlf
  68. - core.eol
  69. `core.autocrlf` is taken into account for all files that doesn't have a `text`
  70. attribute defined in `.gitattributes`; it takes three possible values:
  71. - `true`: This forces all files on the working directory to have CRLF
  72. line-endings in the working directory and convert line-endings to LF
  73. when writing to the index. When autocrlf is set to true, eol value is
  74. ignored.
  75. - `input`: Quite similar to the `true` value but only force the write
  76. filter, ie line-ending of new files added to the index will get their
  77. line-endings converted to LF.
  78. - `false` (default): No normalization is done.
  79. `core.eol` is the top-level configuration to define the line-ending to use
  80. when applying the read_filer. It takes three possible values:
  81. - `lf`: When normalization is done, force line-endings to be `LF` in the
  82. working directory.
  83. - `crlf`: When normalization is done, force line-endings to be `CRLF` in
  84. the working directory.
  85. - `native` (default): When normalization is done, force line-endings to be
  86. the platform's native line ending.
  87. One thing to remember is when line-ending normalization is done on a file, Git
  88. always normalize line-ending to `LF` when writing to the index.
  89. There are sources that seems to indicate that Git won't do line-ending
  90. normalization when a file contains mixed line-endings. I think this logic
  91. might be in text / binary detection heuristic but couldn't find it yet.
  92. Sources:
  93. - https://git-scm.com/docs/git-config#git-config-coreeol
  94. - https://git-scm.com/docs/git-config#git-config-coreautocrlf
  95. - https://git-scm.com/docs/gitattributes#_checking_out_and_checking_in
  96. - https://adaptivepatchwork.com/2012/03/01/mind-the-end-of-your-line/
  97. """
  98. CRLF = b"\r\n"
  99. LF = b"\n"
  100. def convert_crlf_to_lf(text_hunk):
  101. """Convert CRLF in text hunk into LF
  102. :param text_hunk: A bytes string representing a text hunk
  103. :return: The text hunk with the same type, with CRLF replaced into LF
  104. """
  105. return text_hunk.replace(CRLF, LF)
  106. def convert_lf_to_crlf(text_hunk):
  107. """Convert LF in text hunk into CRLF
  108. :param text_hunk: A bytes string representing a text hunk
  109. :return: The text hunk with the same type, with LF replaced into CRLF
  110. """
  111. # TODO find a more efficient way of doing it
  112. intermediary = text_hunk.replace(CRLF, LF)
  113. return intermediary.replace(LF, CRLF)
  114. def get_checkout_filter_autocrlf(core_autocrlf):
  115. """ Returns the correct checkout filter base on autocrlf value
  116. :param core_autocrlf: The bytes configuration value of core.autocrlf.
  117. Valid values are: b'true', b'false' or b'input'.
  118. :return: Either None if no filter has to be applied or a function
  119. accepting a single argument, a binary text hunk
  120. """
  121. if core_autocrlf == b"true":
  122. return convert_lf_to_crlf
  123. return None
  124. def get_checkin_filter_autocrlf(core_autocrlf):
  125. """ Returns the correct checkin filter base on autocrlf value
  126. :param core_autocrlf: The bytes configuration value of core.autocrlf.
  127. Valid values are: b'true', b'false' or b'input'.
  128. :return: Either None if no filter has to be applied or a function
  129. accepting a single argument, a binary text hunk
  130. """
  131. if core_autocrlf == b"true" or core_autocrlf == b"input":
  132. return convert_crlf_to_lf
  133. # Checking filter should never be `convert_lf_to_crlf`
  134. return None