1/*2* "git push"3*/4#include "cache.h"5#include "refs.h"6#include "run-command.h"7#include "builtin.h"89#define MAX_URI (16)1011static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";1213static int all = 0, tags = 0, force = 0, thin = 1;14static const char *execute = NULL;1516#define BUF_SIZE (2084)17static char buffer[BUF_SIZE];1819static const char **refspec = NULL;20static int refspec_nr = 0;2122static void add_refspec(const char *ref)23{24int nr = refspec_nr + 1;25refspec = xrealloc(refspec, nr * sizeof(char *));26refspec[nr-1] = ref;27refspec_nr = nr;28}2930static int expand_one_ref(const char *ref, const unsigned char *sha1)31{32/* Ignore the "refs/" at the beginning of the refname */33ref += 5;3435if (strncmp(ref, "tags/", 5))36return 0;3738add_refspec(strdup(ref));39return 0;40}4142static void expand_refspecs(void)43{44if (all) {45if (refspec_nr)46die("cannot mix '--all' and a refspec");4748/*49* No need to expand "--all" - we'll just use50* the "--all" flag to send-pack51*/52return;53}54if (!tags)55return;56for_each_ref(expand_one_ref);57}5859static void set_refspecs(const char **refs, int nr)60{61if (nr) {62size_t bytes = nr * sizeof(char *);6364refspec = xrealloc(refspec, bytes);65memcpy(refspec, refs, bytes);66refspec_nr = nr;67}68expand_refspecs();69}7071static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])72{73int n = 0;74FILE *f = fopen(git_path("remotes/%s", repo), "r");75int has_explicit_refspec = refspec_nr;7677if (!f)78return -1;79while (fgets(buffer, BUF_SIZE, f)) {80int is_refspec;81char *s, *p;8283if (!strncmp("URL: ", buffer, 5)) {84is_refspec = 0;85s = buffer + 5;86} else if (!strncmp("Push: ", buffer, 6)) {87is_refspec = 1;88s = buffer + 6;89} else90continue;9192/* Remove whitespace at the head.. */93while (isspace(*s))94s++;95if (!*s)96continue;9798/* ..and at the end */99p = s + strlen(s);100while (isspace(p[-1]))101*--p = 0;102103if (!is_refspec) {104if (n < MAX_URI)105uri[n++] = strdup(s);106else107error("more than %d URL's specified, ignoreing the rest", MAX_URI);108}109else if (is_refspec && !has_explicit_refspec)110add_refspec(strdup(s));111}112fclose(f);113if (!n)114die("remote '%s' has no URL", repo);115return n;116}117118static int get_branches_uri(const char *repo, const char *uri[MAX_URI])119{120const char *slash = strchr(repo, '/');121int n = slash ? slash - repo : 1000;122FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");123char *s, *p;124int len;125126if (!f)127return 0;128s = fgets(buffer, BUF_SIZE, f);129fclose(f);130if (!s)131return 0;132while (isspace(*s))133s++;134if (!*s)135return 0;136p = s + strlen(s);137while (isspace(p[-1]))138*--p = 0;139len = p - s;140if (slash)141len += strlen(slash);142p = xmalloc(len + 1);143strcpy(p, s);144if (slash)145strcat(p, slash);146uri[0] = p;147return 1;148}149150/*151* Read remotes and branches file, fill the push target URI152* list. If there is no command line refspecs, read Push: lines153* to set up the *refspec list as well.154* return the number of push target URIs155*/156static int read_config(const char *repo, const char *uri[MAX_URI])157{158int n;159160if (*repo != '/') {161n = get_remotes_uri(repo, uri);162if (n > 0)163return n;164165n = get_branches_uri(repo, uri);166if (n > 0)167return n;168}169170uri[0] = repo;171return 1;172}173174static int do_push(const char *repo)175{176const char *uri[MAX_URI];177int i, n;178int remote;179const char **argv;180int argc;181182n = read_config(repo, uri);183if (n <= 0)184die("bad repository '%s'", repo);185186argv = xmalloc((refspec_nr + 10) * sizeof(char *));187argv[0] = "dummy-send-pack";188argc = 1;189if (all)190argv[argc++] = "--all";191if (force)192argv[argc++] = "--force";193if (execute)194argv[argc++] = execute;195if (thin)196argv[argc++] = "--thin";197remote = argc;198argv[argc++] = "dummy-remote";199while (refspec_nr--)200argv[argc++] = *refspec++;201argv[argc] = NULL;202203for (i = 0; i < n; i++) {204int error;205const char *dest = uri[i];206const char *sender = "git-send-pack";207if (!strncmp(dest, "http://", 7) ||208!strncmp(dest, "https://", 8))209sender = "git-http-push";210argv[0] = sender;211argv[remote] = dest;212error = run_command_v(argc, argv);213if (!error)214continue;215switch (error) {216case -ERR_RUN_COMMAND_FORK:217die("unable to fork for %s", sender);218case -ERR_RUN_COMMAND_EXEC:219die("unable to exec %s", sender);220case -ERR_RUN_COMMAND_WAITPID:221case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:222case -ERR_RUN_COMMAND_WAITPID_SIGNAL:223case -ERR_RUN_COMMAND_WAITPID_NOEXIT:224die("%s died with strange error", sender);225default:226return -error;227}228}229return 0;230}231232int cmd_push(int argc, const char **argv, char **envp)233{234int i;235const char *repo = "origin"; // default repository236237for (i = 1; i < argc; i++) {238const char *arg = argv[i];239240if (arg[0] != '-') {241repo = arg;242i++;243break;244}245if (!strcmp(arg, "--all")) {246all = 1;247continue;248}249if (!strcmp(arg, "--tags")) {250tags = 1;251continue;252}253if (!strcmp(arg, "--force")) {254force = 1;255continue;256}257if (!strcmp(arg, "--thin")) {258thin = 1;259continue;260}261if (!strcmp(arg, "--no-thin")) {262thin = 0;263continue;264}265if (!strncmp(arg, "--exec=", 7)) {266execute = arg;267continue;268}269usage(push_usage);270}271set_refspecs(argv + i, argc - i);272return do_push(repo);273}