#include "object.h"
#include "tree.h"
+#include <sys/time.h>
+#include <signal.h>
+static int reset = 0;
static int merge = 0;
static int update = 0;
static int index_only = 0;
+static int nontrivial_merge = 0;
+static int trivial_merges_only = 0;
+static int aggressive = 0;
+static int verbose_update = 0;
+static volatile int progress_update = 0;
static int head_idx = -1;
static int merge_size = 0;
pathlen = strlen(first);
ce_size = cache_entry_size(baselen + pathlen);
- src = xmalloc(sizeof(struct cache_entry *) * src_size);
- memset(src, 0, sizeof(struct cache_entry *) * src_size);
+ src = xcalloc(src_size, sizeof(struct cache_entry *));
- subposns = xmalloc(sizeof(struct tree_list_entry *) * len);
- memset(subposns, 0, sizeof(struct tree_list_entry *) * len);
+ subposns = xcalloc(len, sizeof(struct tree_list_entry *));
if (cache_name && !strcmp(cache_name, first)) {
any_files = 1;
else
ce_stage = 2;
- ce = xmalloc(ce_size);
- memset(ce, 0, ce_size);
+ ce = xcalloc(1, ce_size);
ce->ce_mode = create_ce_mode(posns[i]->mode);
ce->ce_flags = create_ce_flags(baselen + pathlen,
ce_stage);
ce->name);
}
+/* Unlink the last component and attempt to remove leading
+ * directories, in case this unlink is the removal of the
+ * last entry in the directory -- empty directories are removed.
+ */
+static void unlink_entry(char *name)
+{
+ char *cp, *prev;
+
+ if (unlink(name))
+ return;
+ prev = NULL;
+ while (1) {
+ int status;
+ cp = strrchr(name, '/');
+ if (prev)
+ *prev = '/';
+ if (!cp)
+ break;
+
+ *cp = 0;
+ status = rmdir(name);
+ if (status) {
+ *cp = '/';
+ break;
+ }
+ prev = cp;
+ }
+}
+
+static void progress_interval(int signum)
+{
+ progress_update = 1;
+}
+
+static void setup_progress_signal(void)
+{
+ struct sigaction sa;
+ struct itimerval v;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = progress_interval;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = SA_RESTART;
+ sigaction(SIGALRM, &sa, NULL);
+
+ v.it_interval.tv_sec = 1;
+ v.it_interval.tv_usec = 0;
+ v.it_value = v.it_interval;
+ setitimer(ITIMER_REAL, &v, NULL);
+}
+
static void check_updates(struct cache_entry **src, int nr)
{
static struct checkout state = {
.refresh_cache = 1,
};
unsigned short mask = htons(CE_UPDATE);
+ unsigned last_percent = 200, cnt = 0, total = 0;
+
+ if (update && verbose_update) {
+ for (total = cnt = 0; cnt < nr; cnt++) {
+ struct cache_entry *ce = src[cnt];
+ if (!ce->ce_mode || ce->ce_flags & mask)
+ total++;
+ }
+
+ /* Don't bother doing this for very small updates */
+ if (total < 250)
+ total = 0;
+
+ if (total) {
+ fprintf(stderr, "Checking files out...\n");
+ setup_progress_signal();
+ progress_update = 1;
+ }
+ cnt = 0;
+ }
+
while (nr--) {
struct cache_entry *ce = *src++;
+
+ if (total) {
+ if (!ce->ce_mode || ce->ce_flags & mask) {
+ unsigned percent;
+ cnt++;
+ percent = (cnt * 100) / total;
+ if (percent != last_percent ||
+ progress_update) {
+ fprintf(stderr, "%4u%% (%u/%u) done\r",
+ percent, cnt, total);
+ last_percent = percent;
+ }
+ }
+ }
if (!ce->ce_mode) {
if (update)
- unlink(ce->name);
+ unlink_entry(ce->name);
continue;
}
if (ce->ce_flags & mask) {
ce->ce_flags &= ~mask;
if (update)
- checkout_entry(ce, &state);
+ checkout_entry(ce, &state, NULL);
}
}
+ if (total) {
+ signal(SIGALRM, SIG_IGN);
+ fputc('\n', stderr);
+ }
}
static int unpack_trees(merge_fn_t fn)
{
int indpos = 0;
unsigned len = object_list_length(trees);
- struct tree_entry_list **posns =
- xmalloc(len * sizeof(struct tree_entry_list *));
+ struct tree_entry_list **posns;
int i;
struct object_list *posn = trees;
merge_size = len;
- for (i = 0; i < len; i++) {
- posns[i] = ((struct tree *) posn->item)->entries;
- posn = posn->next;
+
+ if (len) {
+ posns = xmalloc(len * sizeof(struct tree_entry_list *));
+ for (i = 0; i < len; i++) {
+ posns[i] = ((struct tree *) posn->item)->entries;
+ posn = posn->next;
+ }
+ if (unpack_trees_rec(posns, len, "", fn, &indpos))
+ return -1;
}
- if (unpack_trees_rec(posns, len, "", fn, &indpos))
- return -1;
+
+ if (trivial_merges_only && nontrivial_merge)
+ die("Merge requires file-level merging");
check_updates(active_cache, active_nr);
return 0;
{
struct stat st;
- if (index_only)
+ if (index_only || reset)
return;
if (!lstat(ce->name, &st)) {
- unsigned changed = ce_match_stat(ce, &st);
+ unsigned changed = ce_match_stat(ce, &st, 1);
if (!changed)
return;
errno = 0;
}
+ if (reset) {
+ ce->ce_flags |= htons(CE_UPDATE);
+ return;
+ }
if (errno == ENOENT)
return;
die("Entry '%s' not uptodate. Cannot merge.", ce->name);
}
+/*
+ * We do not want to remove or overwrite a working tree file that
+ * is not tracked.
+ */
+static void verify_absent(const char *path, const char *action)
+{
+ struct stat st;
+
+ if (index_only || reset || !update)
+ return;
+ if (!lstat(path, &st))
+ die("Untracked working tree file '%s' "
+ "would be %s by merge.", path, action);
+}
+
static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
{
merge->ce_flags |= htons(CE_UPDATE);
verify_uptodate(old);
}
}
+ else
+ verify_absent(merge->name, "overwritten");
+
merge->ce_flags &= ~htons(CE_STAGEMASK);
add_cache_entry(merge, ADD_CACHE_OK_TO_ADD);
return 1;
{
if (old)
verify_uptodate(old);
+ else
+ verify_absent(ce->name, "removed");
ce->ce_mode = 0;
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
return 1;
int count;
int head_match = 0;
int remote_match = 0;
+ const char *path = NULL;
int df_conflict_head = 0;
int df_conflict_remote = 0;
int any_anc_missing = 0;
+ int no_anc_exists = 1;
int i;
for (i = 1; i < head_idx; i++) {
if (!stages[i])
any_anc_missing = 1;
+ else {
+ if (!path)
+ path = stages[i]->name;
+ no_anc_exists = 0;
+ }
}
index = stages[0];
remote = NULL;
}
+ if (!path && index)
+ path = index->name;
+ if (!path && head)
+ path = head->name;
+ if (!path && remote)
+ path = remote->name;
+
/* First, if there's a #16 situation, note that to prevent #13
- * and #14.
+ * and #14.
*/
if (!same(remote, head)) {
for (i = 1; i < head_idx; i++) {
if (!head && !remote && any_anc_missing)
return 0;
+ /* Under the new "aggressive" rule, we resolve mostly trivial
+ * cases that we historically had git-merge-one-file resolve.
+ */
+ if (aggressive) {
+ int head_deleted = !head && !df_conflict_head;
+ int remote_deleted = !remote && !df_conflict_remote;
+ /*
+ * Deleted in both.
+ * Deleted in one and unchanged in the other.
+ */
+ if ((head_deleted && remote_deleted) ||
+ (head_deleted && remote && remote_match) ||
+ (remote_deleted && head && head_match)) {
+ if (index)
+ return deleted_entry(index, index);
+ else if (path)
+ verify_absent(path, "removed");
+ return 0;
+ }
+ /*
+ * Added in both, identically.
+ */
+ if (no_anc_exists && head && remote && same(head, remote))
+ return merged_entry(head, index);
+
+ }
+
/* Below are "no merge" cases, which require that the index be
* up-to-date to avoid the files getting overwritten with
* conflict resolution files.
if (index) {
verify_uptodate(index);
}
+ else if (path)
+ verify_absent(path, "overwritten");
+
+ nontrivial_merge = 1;
/* #2, #3, #4, #6, #7, #9, #11. */
count = 0;
struct cache_entry *oldtree = src[1], *newtree = src[2];
if (merge_size != 2)
- return error("Cannot do a twoway merge of %d trees\n",
+ return error("Cannot do a twoway merge of %d trees",
merge_size);
if (current) {
struct cache_entry *a = src[1];
if (merge_size != 1)
- return error("Cannot do a oneway merge of %d trees\n",
+ return error("Cannot do a oneway merge of %d trees",
merge_size);
if (!a)
- return 0;
+ return deleted_entry(old, old);
if (old && same(old, a)) {
+ if (reset) {
+ struct stat st;
+ if (lstat(old->name, &st) ||
+ ce_match_stat(old, &st, 1))
+ old->ce_flags |= htons(CE_UPDATE);
+ }
return keep_entry(old);
}
- return merged_entry(a, NULL);
+ return merged_entry(a, old);
}
static int read_cache_unmerged(void)
return deleted;
}
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
static struct cache_file cache_file;
int main(int argc, char **argv)
{
- int i, newfd, reset, stage = 0;
+ int i, newfd, stage = 0;
unsigned char sha1[20];
merge_fn_t fn = NULL;
+ setup_git_directory();
+ git_config(git_default_config);
+
newfd = hold_index_file_for_update(&cache_file, get_index_file());
if (newfd < 0)
die("unable to create new cachefile");
+ git_config(git_default_config);
+
merge = 0;
reset = 0;
for (i = 1; i < argc; i++) {
continue;
}
+ if (!strcmp(arg, "-v")) {
+ verbose_update = 1;
+ continue;
+ }
+
/* "-i" means "index only", meaning that a merge will
* not even look at the working tree.
*/
continue;
}
+ if (!strcmp(arg, "--trivial")) {
+ trivial_merges_only = 1;
+ continue;
+ }
+
+ if (!strcmp(arg, "--aggressive")) {
+ aggressive = 1;
+ continue;
+ }
+
/* "-m" stands for "merge", meaning we start in stage 1 */
if (!strcmp(arg, "-m")) {
if (stage || merge)
if (1 < index_only + update)
usage(read_tree_usage);
- if (get_sha1(arg, sha1) < 0)
- usage(read_tree_usage);
+ if (get_sha1(arg, sha1))
+ die("Not a valid object name %s", arg);
if (list_tree(sha1) < 0)
die("failed to unpack tree object %s", arg);
stage++;