{"size": 11147, "ext": "scrbl", "max_stars_repo_path": "pkgs/racket-doc/pkg/scribblings/git-workflow.scrbl", "max_stars_repo_name": "lkh01/racket", "max_stars_repo_stars_event_min_datetime": "2018-07-05T21:18:24.000Z", "max_stars_repo_stars_event_max_datetime": "2021-06-04T14:48:37.000Z", "max_issues_repo_path": "pkgs/racket-doc/pkg/scribblings/git-workflow.scrbl", "max_issues_repo_name": "lkh01/racket", "max_issues_repo_issues_event_min_datetime": "2017-05-10T07:33:20.000Z", "max_issues_repo_issues_event_max_datetime": "2018-07-19T15:49:46.000Z", "max_forks_repo_path": "pkgs/racket-doc/pkg/scribblings/git-workflow.scrbl", "max_forks_repo_name": "lkh01/racket", "max_forks_repo_forks_event_min_datetime": "2016-04-25T11:47:57.000Z", "max_forks_repo_forks_event_max_datetime": "2017-05-09T14:52:48.000Z"}
{"max_stars_count": 4, "max_issues_count": 9, "max_forks_count": 3, "avg_line_length": 48.0474137931, "max_line_length": 114, "alphanum_fraction": 0.7489907598}
#lang scribble/manual
@(require "common.rkt"
          scribble/bnf)

@title[#:tag "git-workflow"]{Developing Packages with Git}

When a Git repository is specified as a package source, then a copy of
the repository content is installed as the package
implementation. That installation mode is designed for package
consumers, who normally use a package without modifying it. The
installed copy of the package is unsuitable for development by the
package author, however, since the installation is not a full clone of
the Git repository. The Racket package manager provides different
installation modes to support package authors who work with Git
repository clones.


@section{Linking a Git Checkout as a Directory}

Since a Git repository checkout is a directory, it can be linked as a
package as described in @secref["working-new-pkgs"]. In that case, any
modifications made locally take effect immediately for the package
installation, including any updates from a @exec{git pull}. The
developer must explicitly pull any remote updates to the repository,
however, including when the updates are needed to satisfy the
requirements of dependent packages.

In the following section, we describe an alternative that makes
@command-ref{update} aware of the checkout directory's status as a
repository clone. Furthermore, a directory-linked package can be
promoted to a clone-linked package with @command-ref{update}.


@section[#:tag "clone-link"]{Linking a Git Checkout as a Clone}

When a package is installed with

@commandline{@command{install} --clone @nonterm{dir} @nonterm{git-pkg-source}}

then instead of installing the package as a mere copy of the
repository source, the package is installed by creating a Git clone of
@nonterm{git-pkg-source} as @nonterm{dir}. The clone's checkout is
linked in the same way as a directory, but unlike a plain directory
link, the Racket package manager keeps track of the repository
connection. The @nonterm{git-pkg-source} must be a Git or GitHub
package source, or it must be a package name that the catalog maps to
a Git or GitHub package source; if the source URL includes a fragment,
it must name a branch or tag (as opposed to a raw commit).

When the repository at @nonterm{git-pkg-source} is changed so that the
source has a new checksum, then @command-ref{update} for the package pulls
commits from the repository to the local clone. In other words,
@command-ref{update} works as an alternative to @exec{git pull --ff-only}
to pull updates for the package. Furthermore, @command-ref{update} can
pull updates to local package repositories when checking dependencies.
For example, @exec{@command{update} --all} pulls updates for all
linked package repositories.

A @tech{package source} provided with @DFlag{clone} can include a
branch and/or path into the repository. The branch specification
affects the branch used for the initial checkout, while a non-empty
path causes a subdirectory of the checkout to be linked for the
package.

Suppose that a developer works with a large number of packages and
develops only a few of them. The intended workflow is as follows:

@itemlist[

 @item{Install all the relevant packages with @command-ref{install}.}

 @item{For each package to be developed out of a particular Git
  repository named by @nonterm{pkg-name}, update the installation with

  @commandline{@command{update} --clone @nonterm{dir} @nonterm{pkg-name}}

  which discards the original installation of the package and replaces
  it with a local clone as @nonterm{dir}.

  As a convenience, when @nonterm{git-pkg-source} and the last element
  of @nonterm{dir} are the same, then @nonterm{pkg-name} can be
  omitted. Put another way, the argument to @DFlag{clone} can be a
  path to @nonterm{pkg-name}:

  @commandline{@command{update} --clone @nonterm{path-to}/@nonterm{pkg-name}}

  @margin-note{As a further convenience, when building from scratch
  from the main Racket source repository, the Git configuration
  ignores a top-level @filepath{extra-pkgs} directory. The directory
  is intended to be used as a target for @DFlag{clone}:

  @commandline{@command{update} --clone extra-pkgs/@nonterm{pkg-name}}

  which creates the @filepath{extra-pkgs} subdirectory if it doesn't exist.}}


 @item{If a package's current installation is not drawn from a Git
  repository (e.g., it's drawn from a catalog of built packages for a
  distribution or snapshot), but @nonterm{catalog} maps the package
  name to the right Git repository, then combine @DFlag{clone} with
  @DFlag{lookup} and @DFlag{catalog}:

  @commandline{@command{update} --lookup --catalog @nonterm{catalog} --clone @nonterm{path-to}/@nonterm{pkg-name}}

  A suitable @nonterm{catalog} might be @url{https://pkgs.racket-lang.org}.}

 @item{A newly cloned package will have the specified (or existing
  installation's) repository as its Git @exec{origin}. If you want to
  push and pull updates from a different repository---for instance,
  your own fork of the package source---then use @exec{git} commands
  to add or change the @exec{origin} of your clone to the other
  repository. For example, the command

  @commandline{git remote set-url origin @nonterm{url-of-other-repo}}

  in the clone's directory causes @exec{git pull} and @exec{git push}
  to pull and push to the given @nonterm{url-of-other-repo}.

  @margin-note{You can preserve the clone's connection to its central
  repository by setting an @exec{upstream} remote, e.g. @exec{git
  remote add upstream @nonterm{url-of-central-repo}}. This gives you
  the option to periodically pull in commits from the central
  repository with @exec{git pull --ff-only upstream master}.}

  Alternatively, use @exec{git} to clone the target @nonterm{url}
  first, and then supply the local clone's path as @nonterm{dir} in

  @commandline{@command{update} --clone @nonterm{dir} @nonterm{pkg-name}}

  Either way, when @command{update} pulls updates to the clone, it
  will still pull them from the repository corresponding to
  @nonterm{pkg-name}'s old source, and not from the @exec{git remote}
  @nonterm{url}. Usually, that's what package developers want; when
  they're not actively modifying a package, other developers' updates
  should be pulled from the package's main repository. In case where
  @nonterm{url} is the preferred source of updates for
  @command{update}, use @nonterm{url} in

  @commandline{@command{update} --clone @nonterm{dir} @nonterm{url}}

  Beware, however, that @command{update} may be less able to detect
  repository sharing among multiple packages (and keep the package
  installations consistently associated with a particular clone)
  when an alternative @nonterm{url} is provided.}

 @item{Manage changes to each of the developed packages in the usual
  way with @exec{git} tools, but @command-ref{update} is also available
  for updates, including mass updates.}

]

@section{Interactions Between @exec{git} and @exec{raco pkg}}

The @exec{git} and @exec{raco pkg} tools interact in specific
ways:

@itemlist[

 @item{With the link-establishing

       @commandline{@command{install} --clone @nonterm{dir} @nonterm{git-pkg-source}}

       or the same for @command-ref{update}, if a local repository exists
       already as @nonterm{dir}, then it is left in place and any new
       commits are fetched from @nonterm{git-pkg-source}. The package
       manager does not attempt to check whether a pre-existing
       @nonterm{dir} is consistent with @nonterm{git-pkg-source}; it
       simply starts fetching new commits to @nonterm{dir}, and a
       later @exec{git pull --ff-only} will detect any mismatch.

       Multiple @nonterm{git-pkg-source}s can be provided to
       @command-ref{install}, which makes sense when multiple packages
       are sourced from the same repository and can therefore share
       @nonterm{dir}.  Whether through a single @exec{raco pkg} use or
       multiple uses with the same @exec{--clone @nonterm{dir}},
       packages from the same repository should be linked from the
       same local clone (assuming that they are in the same repository
       because they should be modified together). The package system
       does not inherently require clone sharing among the packages,
       but since non-sharing or inconsistent installation modes could
       be confusing, @command-ref{install} and @command-ref{update}
       report non-sharing or inconsistent installations. In typical cases,
       the default @exec{@DFlag{multi-clone} ask} mode can automatically
       fix inconsistencies.}

 @item{When pulling changes to repositories that have local copies,
       @command-ref{update} pulls changes with the equivalent of @exec{git
       pull --ff-only} by default. Supplying @exec{@DFlag{pull} rebase}
       pulls changes with the equivalent of @exec{git pull --rebase}, instead.
       Supplying @exec{@DFlag{pull} try} attempts to pull with @exec{git
       pull --ff-only}, but failure is ignored.}

 @item{When @command-ref{update} is given a specific commit as the target
       of the update, it uses the equivalent of @exec{git merge --ff-only
       @nonterm{checksum}} or @exec{git merge --rebase
       @nonterm{checksum}}. This approach is intended to preserve any
       changes to the package made locally, but it implies that the
       package cannot be ``downgraded'' to a older commit simply by
       specifying the commit for @command-ref{update}; any newer commits
       that are already in the local repository will be preserved.}

 @item{The installed-package database records the most recent commit
       pulled from the source repository after each installation or
       update. The current commit in the repository checkout is
       consulted only for the purposes of merging onto pulled
       commits. Thus, after pushing repository changes with @exec{git
       push}, a @command-ref{update} makes sense to synchronize the
       package-installation database with the remote repository state
       (which is then the same as the local repository state).}

 @item{When checking a @command-ref{install} or @command-ref{update}
       request for dependencies and collisions, the clone directory's
       content is used directly only if the current checkout includes
       the target commit.

       Otherwise, commits are first fetched with @exec{git fetch}, and
       an additional local clone is created in a temporary directory.
       If the overall installation or update is deemed to be
       successful with respect to remote commits (not necessarily the
       current commit in each local repository) in that copy, then an
       update to the linked repository checkout proceeds. Finally,
       after all checkouts succeed, other package installations and
       updates are completed and recorded. If a checkout fails (e.g.,
       due to a conflict or uncommitted change), then the repository
       checkout is left in a failed state, but all package actions are
       otherwise canceled.}

 @item{Removing a package with @command-ref{remove} leaves the
       repository checkout intact while removing the package link.}

]
