/* * The git protocol is described at https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt * Fetching consists of several stages: * - The client opens an SSH connection to the server. * Call open_transport(FETCH, ...) to initiate this SSH connection. * - The server sends a list of "refs" it knows about, * i.e. branches and tags and their corresponding commit hashes. * Call receive_refs() to handle each ref received from the server. * - The client chooses zero or more of these commit hashes that it wants to fetch. * Call send_want() on each commit hash you want to receive. * Call finish_wants() once the commit hashes have been sent. * If no commit hashes were requested, STOP and call close_transport()! * - The client tells the server which commits hashes it already has, * so the server can minimize the amount of data sent. * There are different ways the server can decide the oldest commit it needs to send; * we are using the basic strategy: the server chooses * the first commit hash sent by the client that the server also has. * This means that if you want to fetch the newest `master`, for example, * the client should send the commit hash of `master` locally, then its parent, * and so on, until it reaches a commit hash that the server also has. * Call send_have() to send each local commit hash to the server. * Call check_have_acks() periodically to check whether the server * knows any of the commit hashes sent since the last check_have_acks(). * But don't call it after every commit hash, since it waits for the server to respond. * Once the server returns that it is ready (or the client knows no more of its commits * are common with the server), call finish_haves(). * - The server figures out what needs to be sent to the client and sends a PACK file. * This format is documented at https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt * The file contains all the new objects (commits, trees, etc.) that the client needs. * Call receive_pack() to handle each object received from the server. * * Pushing is a bit simpler since the client must already know the state of the remote: * - The client opens an SSH connection using open_transport(PUSH, ...) * - The server sends its current refs, just like at the start of a fetch. * Call receive_refs() to receive these remote refs. * If any ref that is going to be pushed points to a different commit on the remote * than what the client thinks is on the remote, the push should be aborted. * The client will need to run a fetch and merge in the remote's changes. * - The client sends a list of refs it wants the server to update. * An update can create, delete, or change a branch. * Call send_update() for each update and finish_updates() once all updates are sent. * If no updates were requested, STOP and call close_transport()! * - The client sends all the new objects that the server needs in a PACK file. * If no branches were requested to be created or changed, do not send a PACK file! * Call start_pack() to send the header of the PACK file. * Call send_pack_object() for each object (commit, tree, or blob) in the PACK file. * Call finish_pack() once all objects have been sent. * - The server tells the client which updates were successful. * Call check_updates() to receive each successfully updated ref. */ #ifndef TRANSPORT_H #define TRANSPORT_H #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include "constants.h" extern const char BRANCH_REF_PREFIX[]; typedef enum { FETCH, PUSH } transport_direction_t; typedef struct transport transport_t; /** * Opens a git connection over SSH * * @param direction whether performing a fetch or a push * @param url the SSH login and project, e.g. "git@gitlab.caltech.edu:cs24-19fa/project07" * @return a handle to the SSH process that can be used by the functions below */ transport_t *open_transport(transport_direction_t direction, char *url); /** Closes a transport_t obtained from open_transport() */ void close_transport(transport_t *); /** * A callback function called for each ref received from the server. * Both `ref` and `hash` may not be used after the callback returns. * * @param ref the name of the reference, e.g. "refs/heads/master" or "refs/tags/v1.0" * @param hash the commit hash, e.g. "00ef4ec3fd981d4e5020634ead3349633dd8ec5a" * @param aux an auxiliary value */ typedef void (*ref_receiver_t)(char *ref, object_hash_t hash, void *aux); /** * Discovers all refs in the remote repository. * Calls the callback on each discovered ref. * This should be the first operation performed on a RECEIVE transport. */ void receive_refs(transport_t *, ref_receiver_t receiver, void *aux); // FETCH OPERATIONS void send_want(transport_t *, const object_hash_t hash); void finish_wants(transport_t *); /** * A callback function called on a commit that the client and server have in common. * `hash` may not be used after the callback returns. * * @param hash the commit hash * @param aux an auxiliary value */ typedef void (*ack_receiver_t)(object_hash_t hash, void *aux); void send_have(transport_t *, const object_hash_t hash); /** * Checks with the server which of the haves sent * since the last check_have_acks() are also on the server. * Calls `receiver` on each common commit hash. * Returns whether the server is ready to send the PACK file. * Once the server says it is ready, or the client knows no more commits * can be common with the server, finish_haves() must be called. */ bool check_have_acks(transport_t *, ack_receiver_t receiver, void *aux); void finish_haves(transport_t *); /** * The types of objects which can be received in a PACK file. * See https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt * * `OBJ_REF_DELTA` is a special type indicating an object that is constructed * from sections of another object. See the link above for the format. */ typedef enum { COMMIT = 1, TREE = 2, BLOB = 3, OBJ_REF_DELTA = 7 } object_type_t; /** * A callback function called when an object is received from the server * * @param type the type of object that was received * @param base_hash if `type == OBJ_REF_DELTA`, the hash of the base object * this object will be constructed from (see "Deltified representation"). * Otherwise, NULL. This may not be used after the callback returns. * @param contents the bytes that make up this object, or in the `OBJ_REF_DELTA` case, * the instructions to construct the object from the base object. * This is heap-allocated and should be free()d by the callback. * @param length the number of bytes in `contents` * @param aux an auxiliary value */ typedef void (*object_receiver_t)( object_type_t type, object_hash_t base_hash, uint8_t *contents, size_t length, void *aux ); void receive_pack(transport_t *, object_receiver_t receiver, void *aux); // PUSH OPERATIONS /** * Requests that the server update the given ref (e.g. "refs/heads/master") * from an old commit hash to a new one. * Set `old_hash` to NULL to create a new ref; set `new_hash` to NULL to delete the ref. */ void send_update( transport_t *, const char *ref, const object_hash_t old_hash, const object_hash_t new_hash ); void finish_updates(transport_t *); void start_pack(transport_t *, size_t object_count); void send_pack_object( transport_t *, object_type_t type, const uint8_t *contents, size_t length ); void finish_pack(transport_t *); /** * A callback function called when a remote ref is updated successfully. * `ref` may not be used after the callback returns. * * @param ref the ref that was updated, e.g. "refs/heads/master" * @param aux an auxiliary value */ typedef void (*updated_ref_receiver_t)(char *ref, void *aux); void check_updates(transport_t *, updated_ref_receiver_t receiver, void *aux); #endif // #ifndef TRANSPORT_H