wt-status.c: handle worktree renames
Before 425a28e0a4 (diff-lib: allow ita entries treated as "not yet exist
in index" - 2016-10-24) there are never "new files" in the index, which
essentially disables rename detection because we only detect renames
when a new file appears in a diff pair.
After that commit, an i-t-a entry can appear as a new file in "git
diff-files". But the diff callback function in wt-status.c does not
handle this case and produces incorrect status output.
PS. The reader may notice that this patch adds a new xstrdup() but not
a free(). Yes we leak memory (the same for head_path). But wt_status
so far has been short lived, this leak should not matter in
practice.
Noticed-by: Alex Vandiver <alexmv@dropbox.com>
Helped-by: Igor Djordjevic <igor.d.djordjevic@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
			
			
This commit is contained in:
		 Nguyễn Thái Ngọc Duy
					Nguyễn Thái Ngọc Duy
				
			
				
					committed by
					
						 Junio C Hamano
						Junio C Hamano
					
				
			
			
				
	
			
			
			 Junio C Hamano
						Junio C Hamano
					
				
			
						parent
						
							5134ccde64
						
					
				
				
					commit
					176ea74793
				
			| @ -125,14 +125,15 @@ the status.relativePaths config option below. | ||||
| Short Format | ||||
| ~~~~~~~~~~~~ | ||||
|  | ||||
| In the short-format, the status of each path is shown as | ||||
| In the short-format, the status of each path is shown as one of these | ||||
| forms | ||||
|  | ||||
| 	XY PATH1 -> PATH2 | ||||
| 	XY PATH | ||||
| 	XY ORIG_PATH -> PATH | ||||
|  | ||||
| where `PATH1` is the path in the `HEAD`, and the " `-> PATH2`" part is | ||||
| shown only when `PATH1` corresponds to a different path in the | ||||
| index/worktree (i.e. the file is renamed). The `XY` is a two-letter | ||||
| status code. | ||||
| where `ORIG_PATH` is where the renamed/copied contents came | ||||
| from. `ORIG_PATH` is only shown when the entry is renamed or | ||||
| copied. The `XY` is a two-letter status code. | ||||
|  | ||||
| The fields (including the `->`) are separated from each other by a | ||||
| single space. If a filename contains whitespace or other nonprintable | ||||
| @ -168,6 +169,8 @@ in which case `XY` are `!!`. | ||||
|     [MARC]           index and work tree matches | ||||
|     [ MARC]     M    work tree changed since index | ||||
|     [ MARC]     D    deleted in work tree | ||||
|     [ D]        R    renamed in work tree | ||||
|     [ D]        C    copied in work tree | ||||
|     ------------------------------------------------- | ||||
|     D           D    unmerged, both deleted | ||||
|     A           U    unmerged, added by us | ||||
| @ -285,13 +288,13 @@ Renamed or copied entries have the following format: | ||||
| 		of similarity between the source and target of the | ||||
| 		move or copy). For example "R100" or "C75". | ||||
|     <path>      The pathname.  In a renamed/copied entry, this | ||||
| 		is the path in the index and in the working tree. | ||||
| 		is the target path. | ||||
|     <sep>       When the `-z` option is used, the 2 pathnames are separated | ||||
| 		with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) | ||||
| 		byte separates them. | ||||
|     <origPath>  The pathname in the commit at HEAD.  This is only | ||||
| 		present in a renamed/copied entry, and tells | ||||
| 		where the renamed/copied contents came from. | ||||
|     <origPath>  The pathname in the commit at HEAD or in the index. | ||||
| 		This is only present in a renamed/copied entry, and | ||||
| 		tells where the renamed/copied contents came from. | ||||
|     -------------------------------------------------------- | ||||
|  | ||||
| Unmerged entries have the following format; the first character is | ||||
|  | ||||
| @ -162,5 +162,65 @@ test_expect_success 'commit: ita entries ignored in empty commit check' ' | ||||
| 	) | ||||
| ' | ||||
|  | ||||
| test_expect_success 'rename detection finds the right names' ' | ||||
| 	git init rename-detection && | ||||
| 	( | ||||
| 		cd rename-detection && | ||||
| 		echo contents >first && | ||||
| 		git add first && | ||||
| 		git commit -m first && | ||||
| 		mv first third && | ||||
| 		git add -N third && | ||||
|  | ||||
| 		git status | grep -v "^?" >actual.1 && | ||||
| 		test_i18ngrep "renamed: *first -> third" actual.1 && | ||||
|  | ||||
| 		git status --porcelain | grep -v "^?" >actual.2 && | ||||
| 		cat >expected.2 <<-\EOF && | ||||
| 		 R first -> third | ||||
| 		EOF | ||||
| 		test_cmp expected.2 actual.2 && | ||||
|  | ||||
| 		hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && | ||||
| 		git status --porcelain=v2 | grep -v "^?" >actual.3 && | ||||
| 		cat >expected.3 <<-EOF && | ||||
| 		2 .R N... 100644 100644 100644 $hash $hash R100 third	first | ||||
| 		EOF | ||||
| 		test_cmp expected.3 actual.3 | ||||
| 	) | ||||
| ' | ||||
|  | ||||
| test_expect_success 'double rename detection in status' ' | ||||
| 	git init rename-detection-2 && | ||||
| 	( | ||||
| 		cd rename-detection-2 && | ||||
| 		echo contents >first && | ||||
| 		git add first && | ||||
| 		git commit -m first && | ||||
| 		git mv first second && | ||||
| 		mv second third && | ||||
| 		git add -N third && | ||||
|  | ||||
| 		git status | grep -v "^?" >actual.1 && | ||||
| 		test_i18ngrep "renamed: *first -> second" actual.1 && | ||||
| 		test_i18ngrep "renamed: *second -> third" actual.1 && | ||||
|  | ||||
| 		git status --porcelain | grep -v "^?" >actual.2 && | ||||
| 		cat >expected.2 <<-\EOF && | ||||
| 		R  first -> second | ||||
| 		 R second -> third | ||||
| 		EOF | ||||
| 		test_cmp expected.2 actual.2 && | ||||
|  | ||||
| 		hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && | ||||
| 		git status --porcelain=v2 | grep -v "^?" >actual.3 && | ||||
| 		cat >expected.3 <<-EOF && | ||||
| 		2 R. N... 100644 100644 100644 $hash $hash R100 second	first | ||||
| 		2 .R N... 100644 100644 100644 $hash $hash R100 third	second | ||||
| 		EOF | ||||
| 		test_cmp expected.3 actual.3 | ||||
| 	) | ||||
| ' | ||||
|  | ||||
| test_done | ||||
|  | ||||
|  | ||||
							
								
								
									
										22
									
								
								wt-status.c
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								wt-status.c
									
									
									
									
									
								
							| @ -361,8 +361,6 @@ static void wt_longstatus_print_change_data(struct wt_status *s, | ||||
| 	switch (change_type) { | ||||
| 	case WT_STATUS_UPDATED: | ||||
| 		status = d->index_status; | ||||
| 		if (d->rename_source) | ||||
| 			one_name = d->rename_source; | ||||
| 		break; | ||||
| 	case WT_STATUS_CHANGED: | ||||
| 		if (d->new_submodule_commits || d->dirty_submodule) { | ||||
| @ -383,6 +381,14 @@ static void wt_longstatus_print_change_data(struct wt_status *s, | ||||
| 		    change_type); | ||||
| 	} | ||||
|  | ||||
| 	/* | ||||
| 	 * Only pick up the rename it's relevant. If the rename is for | ||||
| 	 * the changed section and we're printing the updated section, | ||||
| 	 * ignore it. | ||||
| 	 */ | ||||
| 	if (d->rename_status == status) | ||||
| 		one_name = d->rename_source; | ||||
|  | ||||
| 	one = quote_path(one_name, s->prefix, &onebuf); | ||||
| 	two = quote_path(two_name, s->prefix, &twobuf); | ||||
|  | ||||
| @ -434,7 +440,7 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, | ||||
| 		struct wt_status_change_data *d; | ||||
|  | ||||
| 		p = q->queue[i]; | ||||
| 		it = string_list_insert(&s->change, p->one->path); | ||||
| 		it = string_list_insert(&s->change, p->two->path); | ||||
| 		d = it->util; | ||||
| 		if (!d) { | ||||
| 			d = xcalloc(1, sizeof(*d)); | ||||
| @ -461,6 +467,14 @@ static void wt_status_collect_changed_cb(struct diff_queue_struct *q, | ||||
| 			/* mode_worktree is zero for a delete. */ | ||||
| 			break; | ||||
|  | ||||
| 		case DIFF_STATUS_COPIED: | ||||
| 		case DIFF_STATUS_RENAMED: | ||||
| 			if (d->rename_status) | ||||
| 				die("BUG: multiple renames on the same target? how?"); | ||||
| 			d->rename_source = xstrdup(p->one->path); | ||||
| 			d->rename_score = p->score * 100 / MAX_SCORE; | ||||
| 			d->rename_status = p->status; | ||||
| 			/* fallthru */ | ||||
| 		case DIFF_STATUS_MODIFIED: | ||||
| 		case DIFF_STATUS_TYPE_CHANGED: | ||||
| 		case DIFF_STATUS_UNMERGED: | ||||
| @ -532,6 +546,8 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, | ||||
|  | ||||
| 		case DIFF_STATUS_COPIED: | ||||
| 		case DIFF_STATUS_RENAMED: | ||||
| 			if (d->rename_status) | ||||
| 				die("BUG: multiple renames on the same target? how?"); | ||||
| 			d->rename_source = xstrdup(p->one->path); | ||||
| 			d->rename_score = p->score * 100 / MAX_SCORE; | ||||
| 			d->rename_status = p->status; | ||||
|  | ||||
		Reference in New Issue
	
	Block a user