int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, struct extra_have_objects *extra_have) { int in = fd[0]; int out = fd[1]; struct ref *ref; int new_refs; int ask_for_status_report = 0; int allow_deleting_refs = 0; int expect_status_report = 0; int ret; /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" "Perhaps you should specify a branch such as 'master'.\n"); return 0; } /* * Finally, tell the other end! */ new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { if (ref->peer_ref) hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); else if (!args->send_mirror) continue; ref->deletion = is_null_sha1(ref->new_sha1); if (ref->deletion && !allow_deleting_refs) { ref->status = REF_STATUS_REJECT_NODELETE; continue; } if (!ref->deletion && !hashcmp(ref->old_sha1, ref->new_sha1)) { ref->status = REF_STATUS_UPTODATE; continue; } /* This part determines what can overwrite what. * The rules are: * * (0) you can always use --force or +A:B notation to * selectively force individual ref pairs. * * (1) if the old thing does not exist, it is OK. * * (2) if you do not have the old thing, you are not allowed * to overwrite it; you would not know what you are losing * otherwise. * * (3) if both new and old are commit-ish, and new is a * descendant of old, it is OK. * * (4) regardless of all of the above, removing :B is * always allowed. */ ref->nonfastforward = !ref->deletion && !is_null_sha1(ref->old_sha1) && (!has_sha1_file(ref->old_sha1) || !ref_newer(ref->new_sha1, ref->old_sha1)); if (ref->nonfastforward && !ref->force && !args->force_update) { ref->status = REF_STATUS_REJECT_NONFASTFORWARD; continue; } if (!ref->deletion) new_refs++; if (!args->dry_run) { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); if (ask_for_status_report) { packet_write(out, "%s %s %s%c%s", old_hex, new_hex, ref->name, 0, "report-status"); ask_for_status_report = 0; expect_status_report = 1; } else packet_write(out, "%s %s %s", old_hex, new_hex, ref->name); } ref->status = expect_status_report ? REF_STATUS_EXPECTING_REPORT : REF_STATUS_OK; } packet_flush(out); if (new_refs && !args->dry_run) { if (pack_objects(out, remote_refs, extra_have, args) < 0) { for (ref = remote_refs; ref; ref = ref->next) ref->status = REF_STATUS_NONE; return -1; } } if (expect_status_report) ret = receive_status(in, remote_refs); else ret = 0; if (ret < 0) return ret; for (ref = remote_refs; ref; ref = ref->next) { switch (ref->status) { case REF_STATUS_NONE: case REF_STATUS_UPTODATE: case REF_STATUS_OK: break; default: return -1; } } return 0; }
int cmd_main(int argc, const char **argv) { struct transfer_request *request; struct transfer_request *next_request; int nr_refspec = 0; const char **refspec = NULL; struct remote_lock *ref_lock = NULL; struct remote_lock *info_ref_lock = NULL; struct rev_info revs; int delete_branch = 0; int force_delete = 0; int objects_to_send; int rc = 0; int i; int new_refs; struct ref *ref, *local_refs; repo = xcalloc(1, sizeof(*repo)); argv++; for (i = 1; i < argc; i++, argv++) { const char *arg = *argv; if (*arg == '-') { if (!strcmp(arg, "--all")) { push_all = MATCH_REFS_ALL; continue; } if (!strcmp(arg, "--force")) { force_all = 1; continue; } if (!strcmp(arg, "--dry-run")) { dry_run = 1; continue; } if (!strcmp(arg, "--helper-status")) { helper_status = 1; continue; } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; http_is_verbose = 1; continue; } if (!strcmp(arg, "-d")) { delete_branch = 1; continue; } if (!strcmp(arg, "-D")) { delete_branch = 1; force_delete = 1; continue; } if (!strcmp(arg, "-h")) usage(http_push_usage); } if (!repo->url) { char *path = strstr(arg, "//"); str_end_url_with_slash(arg, &repo->url); repo->path_len = strlen(repo->url); if (path) { repo->path = strchr(path+2, '/'); if (repo->path) repo->path_len = strlen(repo->path); } continue; } refspec = argv; nr_refspec = argc - i; break; } #ifndef USE_CURL_MULTI die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI"); #endif if (!repo->url) usage(http_push_usage); if (delete_branch && nr_refspec != 1) die("You must specify only one branch name when deleting a remote branch"); setup_git_directory(); memset(remote_dir_exists, -1, 256); http_init(NULL, repo->url, 1); #ifdef USE_CURL_MULTI is_running_queue = 0; #endif /* Verify DAV compliance/lock support */ if (!locking_available()) { rc = 1; goto cleanup; } sigchain_push_common(remove_locks_on_signal); /* Check whether the remote has server info files */ repo->can_update_info_refs = 0; repo->has_info_refs = remote_exists("info/refs"); repo->has_info_packs = remote_exists("objects/info/packs"); if (repo->has_info_refs) { info_ref_lock = lock_remote("info/refs", LOCK_TIME); if (info_ref_lock) repo->can_update_info_refs = 1; else { error("cannot lock existing info/refs"); rc = 1; goto cleanup; } } if (repo->has_info_packs) fetch_indices(); /* Get a list of all local and remote heads to validate refspecs */ local_refs = get_local_heads(); fprintf(stderr, "Fetching remote heads...\n"); get_dav_remote_heads(); run_request_queue(); /* Remove a remote branch if -d or -D was specified */ if (delete_branch) { if (delete_remote_branch(refspec[0], force_delete) == -1) { fprintf(stderr, "Unable to delete remote branch %s\n", refspec[0]); if (helper_status) printf("error %s cannot remove\n", refspec[0]); } goto cleanup; } /* match them up */ if (match_push_refs(local_refs, &remote_refs, nr_refspec, (const char **) refspec, push_all)) { rc = -1; goto cleanup; } if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n"); if (helper_status) printf("error null no match\n"); rc = 0; goto cleanup; } new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { struct argv_array commit_argv = ARGV_ARRAY_INIT; if (!ref->peer_ref) continue; if (is_null_oid(&ref->peer_ref->new_oid)) { if (delete_remote_branch(ref->name, 1) == -1) { error("Could not remove %s", ref->name); if (helper_status) printf("error %s cannot remove\n", ref->name); rc = -4; } else if (helper_status) printf("ok %s\n", ref->name); new_refs++; continue; } if (!oidcmp(&ref->old_oid, &ref->peer_ref->new_oid)) { if (push_verbosely) fprintf(stderr, "'%s': up-to-date\n", ref->name); if (helper_status) printf("ok %s up to date\n", ref->name); continue; } if (!force_all && !is_null_oid(&ref->old_oid) && !ref->force) { if (!has_object_file(&ref->old_oid) || !ref_newer(&ref->peer_ref->new_oid, &ref->old_oid)) { /* * We do not have the remote ref, or * we know that the remote ref is not * an ancestor of what we are trying to * push. Either way this can be losing * commits at the remote end and likely * we were not up to date to begin with. */ error("remote '%s' is not an ancestor of\n" "local '%s'.\n" "Maybe you are not up-to-date and " "need to pull first?", ref->name, ref->peer_ref->name); if (helper_status) printf("error %s non-fast forward\n", ref->name); rc = -2; continue; } } oidcpy(&ref->new_oid, &ref->peer_ref->new_oid); new_refs++; fprintf(stderr, "updating '%s'", ref->name); if (strcmp(ref->name, ref->peer_ref->name)) fprintf(stderr, " using '%s'", ref->peer_ref->name); fprintf(stderr, "\n from %s\n to %s\n", oid_to_hex(&ref->old_oid), oid_to_hex(&ref->new_oid)); if (dry_run) { if (helper_status) printf("ok %s\n", ref->name); continue; } /* Lock remote branch ref */ ref_lock = lock_remote(ref->name, LOCK_TIME); if (ref_lock == NULL) { fprintf(stderr, "Unable to lock remote branch %s\n", ref->name); if (helper_status) printf("error %s lock error\n", ref->name); rc = 1; continue; } /* Set up revision info for this refspec */ argv_array_push(&commit_argv, ""); /* ignored */ argv_array_push(&commit_argv, "--objects"); argv_array_push(&commit_argv, oid_to_hex(&ref->new_oid)); if (!push_all && !is_null_oid(&ref->old_oid)) argv_array_pushf(&commit_argv, "^%s", oid_to_hex(&ref->old_oid)); init_revisions(&revs, setup_git_directory()); setup_revisions(commit_argv.argc, commit_argv.argv, &revs, NULL); revs.edge_hint = 0; /* just in case */ /* Generate a list of objects that need to be pushed */ pushing = 0; if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(&revs, NULL); objects_to_send = get_delta(&revs, ref_lock); finish_all_active_slots(); /* Push missing objects to remote, this would be a convenient time to pack them first if appropriate. */ pushing = 1; if (objects_to_send) fprintf(stderr, " sending %d objects\n", objects_to_send); run_request_queue(); /* Update the remote branch if all went well */ if (aborted || !update_remote(ref->new_oid.hash, ref_lock)) rc = 1; if (!rc) fprintf(stderr, " done\n"); if (helper_status) printf("%s %s\n", !rc ? "ok" : "error", ref->name); unlock_remote(ref_lock); check_locks(); argv_array_clear(&commit_argv); } /* Update remote server info if appropriate */ if (repo->has_info_refs && new_refs) { if (info_ref_lock && repo->can_update_info_refs) { fprintf(stderr, "Updating remote server info\n"); if (!dry_run) update_remote_info_refs(info_ref_lock); } else { fprintf(stderr, "Unable to update server info\n"); } } cleanup: if (info_ref_lock) unlock_remote(info_ref_lock); free(repo); http_cleanup(); request = request_queue_head; while (request != NULL) { next_request = request->next; release_request(request); request = next_request; } return rc; }