@@ -61,6 +61,13 @@ static enum ws_error_action {
6161static int whitespace_error ;
6262static int squelch_whitespace_errors = 5 ;
6363static int applied_after_fixing_ws ;
64+
65+ static enum ws_ignore {
66+ ignore_ws_none ,
67+ ignore_ws_change ,
68+ } ws_ignore_action = ignore_ws_none ;
69+
70+
6471static const char * patch_input_file ;
6572static const char * root ;
6673static int root_len ;
@@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option)
97104 die ("unrecognized whitespace option '%s'" , option );
98105}
99106
107+ static void parse_ignorewhitespace_option (const char * option )
108+ {
109+ if (!option || !strcmp (option , "no" ) ||
110+ !strcmp (option , "false" ) || !strcmp (option , "never" ) ||
111+ !strcmp (option , "none" )) {
112+ ws_ignore_action = ignore_ws_none ;
113+ return ;
114+ }
115+ if (!strcmp (option , "change" )) {
116+ ws_ignore_action = ignore_ws_change ;
117+ return ;
118+ }
119+ die ("unrecognized whitespace ignore option '%s'" , option );
120+ }
121+
100122static void set_default_whitespace_mode (const char * whitespace_option )
101123{
102124 if (!whitespace_option && !apply_default_whitespace )
@@ -214,6 +236,62 @@ static uint32_t hash_line(const char *cp, size_t len)
214236 return h ;
215237}
216238
239+ /*
240+ * Compare lines s1 of length n1 and s2 of length n2, ignoring
241+ * whitespace difference. Returns 1 if they match, 0 otherwise
242+ */
243+ static int fuzzy_matchlines (const char * s1 , size_t n1 ,
244+ const char * s2 , size_t n2 )
245+ {
246+ const char * last1 = s1 + n1 - 1 ;
247+ const char * last2 = s2 + n2 - 1 ;
248+ int result = 0 ;
249+
250+ if (n1 < 0 || n2 < 0 )
251+ return 0 ;
252+
253+ /* ignore line endings */
254+ while ((* last1 == '\r' ) || (* last1 == '\n' ))
255+ last1 -- ;
256+ while ((* last2 == '\r' ) || (* last2 == '\n' ))
257+ last2 -- ;
258+
259+ /* skip leading whitespace */
260+ while (isspace (* s1 ) && (s1 <= last1 ))
261+ s1 ++ ;
262+ while (isspace (* s2 ) && (s2 <= last2 ))
263+ s2 ++ ;
264+ /* early return if both lines are empty */
265+ if ((s1 > last1 ) && (s2 > last2 ))
266+ return 1 ;
267+ while (!result ) {
268+ result = * s1 ++ - * s2 ++ ;
269+ /*
270+ * Skip whitespace inside. We check for whitespace on
271+ * both buffers because we don't want "a b" to match
272+ * "ab"
273+ */
274+ if (isspace (* s1 ) && isspace (* s2 )) {
275+ while (isspace (* s1 ) && s1 <= last1 )
276+ s1 ++ ;
277+ while (isspace (* s2 ) && s2 <= last2 )
278+ s2 ++ ;
279+ }
280+ /*
281+ * If we reached the end on one side only,
282+ * lines don't match
283+ */
284+ if (
285+ ((s2 > last2 ) && (s1 <= last1 )) ||
286+ ((s1 > last1 ) && (s2 <= last2 )))
287+ return 0 ;
288+ if ((s1 > last1 ) && (s2 > last2 ))
289+ break ;
290+ }
291+
292+ return !result ;
293+ }
294+
217295static void add_line_info (struct image * img , const char * bol , size_t len , unsigned flag )
218296{
219297 ALLOC_GROW (img -> line_allocated , img -> nr + 1 , img -> alloc );
@@ -1672,10 +1750,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
16721750 }
16731751}
16741752
1753+ /*
1754+ * Update the preimage, and the common lines in postimage,
1755+ * from buffer buf of length len. If postlen is 0 the postimage
1756+ * is updated in place, otherwise it's updated on a new buffer
1757+ * of length postlen
1758+ */
1759+
16751760static void update_pre_post_images (struct image * preimage ,
16761761 struct image * postimage ,
16771762 char * buf ,
1678- size_t len )
1763+ size_t len , size_t postlen )
16791764{
16801765 int i , ctx ;
16811766 char * new , * old , * fixed ;
@@ -1694,11 +1779,19 @@ static void update_pre_post_images(struct image *preimage,
16941779 * preimage = fixed_preimage ;
16951780
16961781 /*
1697- * Adjust the common context lines in postimage, in place.
1698- * This is possible because whitespace fixing does not make
1699- * the string grow.
1782+ * Adjust the common context lines in postimage. This can be
1783+ * done in-place when we are just doing whitespace fixing,
1784+ * which does not make the string grow, but needs a new buffer
1785+ * when ignoring whitespace causes the update, since in this case
1786+ * we could have e.g. tabs converted to multiple spaces.
1787+ * We trust the caller to tell us if the update can be done
1788+ * in place (postlen==0) or not.
17001789 */
1701- new = old = postimage -> buf ;
1790+ old = postimage -> buf ;
1791+ if (postlen )
1792+ new = postimage -> buf = xmalloc (postlen );
1793+ else
1794+ new = old ;
17021795 fixed = preimage -> buf ;
17031796 for (i = ctx = 0 ; i < postimage -> nr ; i ++ ) {
17041797 size_t len = postimage -> line [i ].len ;
@@ -1773,12 +1866,58 @@ static int match_fragment(struct image *img,
17731866 !memcmp (img -> buf + try , preimage -> buf , preimage -> len ))
17741867 return 1 ;
17751868
1869+ /*
1870+ * No exact match. If we are ignoring whitespace, run a line-by-line
1871+ * fuzzy matching. We collect all the line length information because
1872+ * we need it to adjust whitespace if we match.
1873+ */
1874+ if (ws_ignore_action == ignore_ws_change ) {
1875+ size_t imgoff = 0 ;
1876+ size_t preoff = 0 ;
1877+ size_t postlen = postimage -> len ;
1878+ size_t imglen [preimage -> nr ];
1879+ for (i = 0 ; i < preimage -> nr ; i ++ ) {
1880+ size_t prelen = preimage -> line [i ].len ;
1881+
1882+ imglen [i ] = img -> line [try_lno + i ].len ;
1883+ if (!fuzzy_matchlines (
1884+ img -> buf + try + imgoff , imglen [i ],
1885+ preimage -> buf + preoff , prelen ))
1886+ return 0 ;
1887+ if (preimage -> line [i ].flag & LINE_COMMON )
1888+ postlen += imglen [i ] - prelen ;
1889+ imgoff += imglen [i ];
1890+ preoff += prelen ;
1891+ }
1892+
1893+ /*
1894+ * Ok, the preimage matches with whitespace fuzz. Update it and
1895+ * the common postimage lines to use the same whitespace as the
1896+ * target. imgoff now holds the true length of the target that
1897+ * matches the preimage, and we need to update the line lengths
1898+ * of the preimage to match the target ones.
1899+ */
1900+ fixed_buf = xmalloc (imgoff );
1901+ memcpy (fixed_buf , img -> buf + try , imgoff );
1902+ for (i = 0 ; i < preimage -> nr ; i ++ )
1903+ preimage -> line [i ].len = imglen [i ];
1904+
1905+ /*
1906+ * Update the preimage buffer and the postimage context lines.
1907+ */
1908+ update_pre_post_images (preimage , postimage ,
1909+ fixed_buf , imgoff , postlen );
1910+ return 1 ;
1911+ }
1912+
17761913 if (ws_error_action != correct_ws_error )
17771914 return 0 ;
17781915
17791916 /*
17801917 * The hunk does not apply byte-by-byte, but the hash says
1781- * it might with whitespace fuzz.
1918+ * it might with whitespace fuzz. We haven't been asked to
1919+ * ignore whitespace, we were asked to correct whitespace
1920+ * errors, so let's try matching after whitespace correction.
17821921 */
17831922 fixed_buf = xmalloc (preimage -> len + 1 );
17841923 buf = fixed_buf ;
@@ -1830,7 +1969,7 @@ static int match_fragment(struct image *img,
18301969 * hunk match. Update the context lines in the postimage.
18311970 */
18321971 update_pre_post_images (preimage , postimage ,
1833- fixed_buf , buf - fixed_buf );
1972+ fixed_buf , buf - fixed_buf , 0 );
18341973 return 1 ;
18351974
18361975 unmatch_exit :
@@ -3272,6 +3411,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
32723411{
32733412 if (!strcmp (var , "apply.whitespace" ))
32743413 return git_config_string (& apply_default_whitespace , var , value );
3414+ else if (!strcmp (var , "apply.ignorewhitespace" ))
3415+ return git_config_string (& apply_default_ignorewhitespace , var , value );
32753416 return git_default_config (var , value , cb );
32763417}
32773418
@@ -3308,6 +3449,16 @@ static int option_parse_z(const struct option *opt,
33083449 return 0 ;
33093450}
33103451
3452+ static int option_parse_space_change (const struct option * opt ,
3453+ const char * arg , int unset )
3454+ {
3455+ if (unset )
3456+ ws_ignore_action = ignore_ws_none ;
3457+ else
3458+ ws_ignore_action = ignore_ws_change ;
3459+ return 0 ;
3460+ }
3461+
33113462static int option_parse_whitespace (const struct option * opt ,
33123463 const char * arg , int unset )
33133464{
@@ -3384,6 +3535,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33843535 { OPTION_CALLBACK , 0 , "whitespace" , & whitespace_option , "action" ,
33853536 "detect new or modified lines that have whitespace errors" ,
33863537 0 , option_parse_whitespace },
3538+ { OPTION_CALLBACK , 0 , "ignore-space-change" , NULL , NULL ,
3539+ "ignore changes in whitespace when finding context" ,
3540+ PARSE_OPT_NOARG , option_parse_space_change },
3541+ { OPTION_CALLBACK , 0 , "ignore-whitespace" , NULL , NULL ,
3542+ "ignore changes in whitespace when finding context" ,
3543+ PARSE_OPT_NOARG , option_parse_space_change },
33873544 OPT_BOOLEAN ('R' , "reverse" , & apply_in_reverse ,
33883545 "apply the patch in reverse" ),
33893546 OPT_BOOLEAN (0 , "unidiff-zero" , & unidiff_zero ,
@@ -3408,6 +3565,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
34083565 git_config (git_apply_config , NULL );
34093566 if (apply_default_whitespace )
34103567 parse_whitespace_option (apply_default_whitespace );
3568+ if (apply_default_ignorewhitespace )
3569+ parse_ignorewhitespace_option (apply_default_ignorewhitespace );
34113570
34123571 argc = parse_options (argc , argv , prefix , builtin_apply_options ,
34133572 apply_usage , 0 );
0 commit comments