@@ -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 );
@@ -1592,10 +1670,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
15921670 }
15931671}
15941672
1673+ /*
1674+ * Update the preimage, and the common lines in postimage,
1675+ * from buffer buf of length len. If postlen is 0 the postimage
1676+ * is updated in place, otherwise it's updated on a new buffer
1677+ * of length postlen
1678+ */
1679+
15951680static void update_pre_post_images (struct image * preimage ,
15961681 struct image * postimage ,
15971682 char * buf ,
1598- size_t len )
1683+ size_t len , size_t postlen )
15991684{
16001685 int i , ctx ;
16011686 char * new , * old , * fixed ;
@@ -1614,11 +1699,19 @@ static void update_pre_post_images(struct image *preimage,
16141699 * preimage = fixed_preimage ;
16151700
16161701 /*
1617- * Adjust the common context lines in postimage, in place.
1618- * This is possible because whitespace fixing does not make
1619- * the string grow.
1702+ * Adjust the common context lines in postimage. This can be
1703+ * done in-place when we are just doing whitespace fixing,
1704+ * which does not make the string grow, but needs a new buffer
1705+ * when ignoring whitespace causes the update, since in this case
1706+ * we could have e.g. tabs converted to multiple spaces.
1707+ * We trust the caller to tell us if the update can be done
1708+ * in place (postlen==0) or not.
16201709 */
1621- new = old = postimage -> buf ;
1710+ old = postimage -> buf ;
1711+ if (postlen )
1712+ new = postimage -> buf = xmalloc (postlen );
1713+ else
1714+ new = old ;
16221715 fixed = preimage -> buf ;
16231716 for (i = ctx = 0 ; i < postimage -> nr ; i ++ ) {
16241717 size_t len = postimage -> line [i ].len ;
@@ -1693,12 +1786,58 @@ static int match_fragment(struct image *img,
16931786 !memcmp (img -> buf + try , preimage -> buf , preimage -> len ))
16941787 return 1 ;
16951788
1789+ /*
1790+ * No exact match. If we are ignoring whitespace, run a line-by-line
1791+ * fuzzy matching. We collect all the line length information because
1792+ * we need it to adjust whitespace if we match.
1793+ */
1794+ if (ws_ignore_action == ignore_ws_change ) {
1795+ size_t imgoff = 0 ;
1796+ size_t preoff = 0 ;
1797+ size_t postlen = postimage -> len ;
1798+ size_t imglen [preimage -> nr ];
1799+ for (i = 0 ; i < preimage -> nr ; i ++ ) {
1800+ size_t prelen = preimage -> line [i ].len ;
1801+
1802+ imglen [i ] = img -> line [try_lno + i ].len ;
1803+ if (!fuzzy_matchlines (
1804+ img -> buf + try + imgoff , imglen [i ],
1805+ preimage -> buf + preoff , prelen ))
1806+ return 0 ;
1807+ if (preimage -> line [i ].flag & LINE_COMMON )
1808+ postlen += imglen [i ] - prelen ;
1809+ imgoff += imglen [i ];
1810+ preoff += prelen ;
1811+ }
1812+
1813+ /*
1814+ * Ok, the preimage matches with whitespace fuzz. Update it and
1815+ * the common postimage lines to use the same whitespace as the
1816+ * target. imgoff now holds the true length of the target that
1817+ * matches the preimage, and we need to update the line lengths
1818+ * of the preimage to match the target ones.
1819+ */
1820+ fixed_buf = xmalloc (imgoff );
1821+ memcpy (fixed_buf , img -> buf + try , imgoff );
1822+ for (i = 0 ; i < preimage -> nr ; i ++ )
1823+ preimage -> line [i ].len = imglen [i ];
1824+
1825+ /*
1826+ * Update the preimage buffer and the postimage context lines.
1827+ */
1828+ update_pre_post_images (preimage , postimage ,
1829+ fixed_buf , imgoff , postlen );
1830+ return 1 ;
1831+ }
1832+
16961833 if (ws_error_action != correct_ws_error )
16971834 return 0 ;
16981835
16991836 /*
17001837 * The hunk does not apply byte-by-byte, but the hash says
1701- * it might with whitespace fuzz.
1838+ * it might with whitespace fuzz. We haven't been asked to
1839+ * ignore whitespace, we were asked to correct whitespace
1840+ * errors, so let's try matching after whitespace correction.
17021841 */
17031842 fixed_buf = xmalloc (preimage -> len + 1 );
17041843 buf = fixed_buf ;
@@ -1750,7 +1889,7 @@ static int match_fragment(struct image *img,
17501889 * hunk match. Update the context lines in the postimage.
17511890 */
17521891 update_pre_post_images (preimage , postimage ,
1753- fixed_buf , buf - fixed_buf );
1892+ fixed_buf , buf - fixed_buf , 0 );
17541893 return 1 ;
17551894
17561895 unmatch_exit :
@@ -3192,6 +3331,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
31923331{
31933332 if (!strcmp (var , "apply.whitespace" ))
31943333 return git_config_string (& apply_default_whitespace , var , value );
3334+ else if (!strcmp (var , "apply.ignorewhitespace" ))
3335+ return git_config_string (& apply_default_ignorewhitespace , var , value );
31953336 return git_default_config (var , value , cb );
31963337}
31973338
@@ -3228,6 +3369,16 @@ static int option_parse_z(const struct option *opt,
32283369 return 0 ;
32293370}
32303371
3372+ static int option_parse_space_change (const struct option * opt ,
3373+ const char * arg , int unset )
3374+ {
3375+ if (unset )
3376+ ws_ignore_action = ignore_ws_none ;
3377+ else
3378+ ws_ignore_action = ignore_ws_change ;
3379+ return 0 ;
3380+ }
3381+
32313382static int option_parse_whitespace (const struct option * opt ,
32323383 const char * arg , int unset )
32333384{
@@ -3304,6 +3455,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33043455 { OPTION_CALLBACK , 0 , "whitespace" , & whitespace_option , "action" ,
33053456 "detect new or modified lines that have whitespace errors" ,
33063457 0 , option_parse_whitespace },
3458+ { OPTION_CALLBACK , 0 , "ignore-space-change" , NULL , NULL ,
3459+ "ignore changes in whitespace when finding context" ,
3460+ PARSE_OPT_NOARG , option_parse_space_change },
3461+ { OPTION_CALLBACK , 0 , "ignore-whitespace" , NULL , NULL ,
3462+ "ignore changes in whitespace when finding context" ,
3463+ PARSE_OPT_NOARG , option_parse_space_change },
33073464 OPT_BOOLEAN ('R' , "reverse" , & apply_in_reverse ,
33083465 "apply the patch in reverse" ),
33093466 OPT_BOOLEAN (0 , "unidiff-zero" , & unidiff_zero ,
@@ -3328,6 +3485,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33283485 git_config (git_apply_config , NULL );
33293486 if (apply_default_whitespace )
33303487 parse_whitespace_option (apply_default_whitespace );
3488+ if (apply_default_ignorewhitespace )
3489+ parse_ignorewhitespace_option (apply_default_ignorewhitespace );
33313490
33323491 argc = parse_options (argc , argv , prefix , builtin_apply_options ,
33333492 apply_usage , 0 );
0 commit comments