-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is an attempt to address the issue raised on #git channel recently by Carl Worth. After a conflicted automerge, "git diff" shows a combined diff to give you how the tentative automerge result differs from what came from each branch. During a complex merge, it is tempting to be able to resolve a few paths at a time, mark them "I've dealt with them" with git-update-index to unclutter the next "git diff" output, and keep going. However, when the final result does not compile or otherwise found to be a mismerge, the workflow to fix the mismerged paths suddenly changes to "git diff HEAD -- path" (to get a diff from our HEAD before merging) and "git diff MERGE_HEAD -- path" (to get a diff from theirs), and it cannot show the combined anymore. With git-unresolve <paths>..., the versions from our branch and their branch for specified blobs are placed in stage #2 and stage #3, without touching the working tree files. This gives you the combined diff back for easier review, along with "diff --ours" and "diff --theirs". One thing it does not do is to place the base in stage #1; this means "diff --base" would behave differently between the run immediately after a conflicted three-way merge, and the run after an update-index by mistake followed by a git-unresolve. We could theoretically run merge-base between HEAD and MERGE_HEAD to find which tree to place in stage #1, but reviewing "diff --base" is not that useful so.... Signed-off-by: Junio C Hamano <junkio@cox.net>
- Loading branch information
Junio C Hamano
committed
Apr 19, 2006
1 parent
a4d0cce
commit ec16779
Showing
3 changed files
with
149 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
#include "cache.h" | ||
#include "tree-walk.h" | ||
|
||
static const char unresolve_usage[] = | ||
"git-unresolve <paths>..."; | ||
|
||
static struct cache_file cache_file; | ||
static unsigned char head_sha1[20]; | ||
static unsigned char merge_head_sha1[20]; | ||
|
||
static struct cache_entry *read_one_ent(const char *which, | ||
unsigned char *ent, const char *path, | ||
int namelen, int stage) | ||
{ | ||
unsigned mode; | ||
unsigned char sha1[20]; | ||
int size; | ||
struct cache_entry *ce; | ||
|
||
if (get_tree_entry(ent, path, sha1, &mode)) { | ||
error("%s: not in %s branch.", path, which); | ||
return NULL; | ||
} | ||
if (mode == S_IFDIR) { | ||
error("%s: not a blob in %s branch.", path, which); | ||
return NULL; | ||
} | ||
size = cache_entry_size(namelen); | ||
ce = xcalloc(1, size); | ||
|
||
memcpy(ce->sha1, sha1, 20); | ||
memcpy(ce->name, path, namelen); | ||
ce->ce_flags = create_ce_flags(namelen, stage); | ||
ce->ce_mode = create_ce_mode(mode); | ||
return ce; | ||
} | ||
|
||
static int unresolve_one(const char *path) | ||
{ | ||
int namelen = strlen(path); | ||
int pos; | ||
int ret = 0; | ||
struct cache_entry *ce_2 = NULL, *ce_3 = NULL; | ||
|
||
/* See if there is such entry in the index. */ | ||
pos = cache_name_pos(path, namelen); | ||
if (pos < 0) { | ||
/* If there isn't, either it is unmerged, or | ||
* resolved as "removed" by mistake. We do not | ||
* want to do anything in the former case. | ||
*/ | ||
pos = -pos-1; | ||
if (pos < active_nr) { | ||
struct cache_entry *ce = active_cache[pos]; | ||
if (ce_namelen(ce) == namelen && | ||
!memcmp(ce->name, path, namelen)) { | ||
fprintf(stderr, | ||
"%s: skipping still unmerged path.\n", | ||
path); | ||
goto free_return; | ||
} | ||
} | ||
} | ||
|
||
/* Grab blobs from given path from HEAD and MERGE_HEAD, | ||
* stuff HEAD version in stage #2, | ||
* stuff MERGE_HEAD version in stage #3. | ||
*/ | ||
ce_2 = read_one_ent("our", head_sha1, path, namelen, 2); | ||
ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3); | ||
|
||
if (!ce_2 || !ce_3) { | ||
ret = -1; | ||
goto free_return; | ||
} | ||
if (!memcmp(ce_2->sha1, ce_3->sha1, 20) && | ||
ce_2->ce_mode == ce_3->ce_mode) { | ||
fprintf(stderr, "%s: identical in both, skipping.\n", | ||
path); | ||
goto free_return; | ||
} | ||
|
||
remove_file_from_cache(path); | ||
if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) { | ||
error("%s: cannot add our version to the index.", path); | ||
ret = -1; | ||
goto free_return; | ||
} | ||
if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD)) | ||
return 0; | ||
error("%s: cannot add their version to the index.", path); | ||
ret = -1; | ||
free_return: | ||
free(ce_2); | ||
free(ce_3); | ||
return ret; | ||
} | ||
|
||
static void read_head_pointers(void) | ||
{ | ||
if (read_ref(git_path("HEAD"), head_sha1)) | ||
die("Cannot read HEAD -- no initial commit yet?"); | ||
if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) { | ||
fprintf(stderr, "Not in the middle of a merge.\n"); | ||
exit(0); | ||
} | ||
} | ||
|
||
int main(int ac, char **av) | ||
{ | ||
int i; | ||
int err = 0; | ||
int newfd; | ||
|
||
if (ac < 2) | ||
usage(unresolve_usage); | ||
|
||
git_config(git_default_config); | ||
|
||
/* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we | ||
* are not doing a merge, so exit with success status. | ||
*/ | ||
read_head_pointers(); | ||
|
||
/* Otherwise we would need to update the cache. */ | ||
newfd= hold_index_file_for_update(&cache_file, get_index_file()); | ||
if (newfd < 0) | ||
die("unable to create new cachefile"); | ||
|
||
if (read_cache() < 0) | ||
die("cache corrupted"); | ||
|
||
for (i = 1; i < ac; i++) { | ||
char *arg = av[i]; | ||
err |= unresolve_one(arg); | ||
} | ||
if (err) | ||
die("Error encountered; index not updated."); | ||
|
||
if (active_cache_changed) { | ||
if (write_cache(newfd, active_cache, active_nr) || | ||
commit_index_file(&cache_file)) | ||
die("Unable to write new cachefile"); | ||
} | ||
return 0; | ||
} |