Add Git introduction

This commit is contained in:
Aaron Rainbolt 2025-12-28 23:11:59 -06:00
parent 3465376d9a
commit f11bc5f603
Signed by: arraybolt3
GPG Key ID: A709160D73C79109

View File

@ -35,7 +35,7 @@ files that we'll describe as we go.
To get an idea of how source packages are structured, we'll start with a
fairly simple package, ``lxqt-about``::
aaron@pkg-builder:~/lubuntu-dev/lxqt-about$ tree
aaron@pkg-builder:~/vmshare/pkg/lxqt-about$ tree
.
├── aboutdialog
│   ...
@ -707,5 +707,833 @@ from there and build it.
``lxqt-about`` deb file, alongside several other files.
That's it! If the above worked, you now have a working packaging environment!
We're now ready to move past system setup, and start learning how packaging
works.
We're now ready to move past system setup, and start learning how to do Debian
packaging.
Git essentials
--------------
.. NOTE::
If you already have a decent working knowledge of Git, you can skim this
section and only focus on the parts that have to do with Debian packages.
If you're working with Debian packages, you are almost certainly going to have
to work with Git. Git is, in essence, a tool for keeping track of changes that
are made to files in a directory. As changes are made to the directory,
developers use Git to take snapshots of what they changed and how. This is
very useful in both software development and packaging, because:
* Keeping track of changes in this way allows you to look at how things
changed in the past if needed.
* If something important is lost in the course of changes, you can get it
back out of Git's history.
* If some changes break everything, you can revert them and go back to a
previous commit.
* Each time someone changes something, their identity is attached to that
change, meaning you can determine who changed what, and when.
* If multiple people are changing the same file at the same time, Git can keep
track of those changes independently, then merge them together later so that
nothing is lost.
Because of the large number of uses Git has, it can be a tricky tool to get
used to. It has far too many features to fully explain in this guide, so we're
only going to go over the basics here. If you want to learn more about Git
than this guide covers, you should read the free
`Pro Git e-book <https://git-scm.com/book/en/v2>`__. Git also has a number of
manual pages you can browse through, see ``man git``.
Repos, commits, branches, tags, and remotes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The primary purpose of Git is manipulating Git *repositories*. A repository
(or "repo" for short) is nothing more than a directory full of files, with a
hidden ``.git`` subdirectory in it. The files in this directory are just
regular files, like you're already used to working with. All of Git's state
(the repo history and whatnot) is stored in the ``.git`` directory. Git is
intended to be used from the command line, and provides a number of commands
for repo manipulation. These commands usually affect whatever repo you are
"in". For instance, if ``$HOME/my-project`` is a Git repo, you are "in" the
``my-project`` repo if your current working directory is ``$HOME/my-project``
or any subdirectory underneath it.
As you make changes to a Git repository, you will want to *commit* your
changes from time to time. A Git commit is a bit of data that records what
files changed, what lines in those files changed, how those lines changed, and
who changed them. Each commit has a unique ID bound to it, so you can
unambiguously refer to any commit in the repository. When you create a new
commit, that's called "committing" a change. Commits can be *checked out*,
meaning that the files in the repository will be changed to reflect what the
repo looked like at the time that commit was made.
.. NOTE::
Git commits can track both large and small changes, but generally it's most
useful if you try to make each Git commit correspond to one logical change
in a project. For instance, if you need to change the version number of a
package, fix an issue that prevents the package from building, make the
README clearer, and introduce a patch to change default settings, each of
those changes should go into separate commits. On the other hand, if you
need to fix a common typo that occur in ten files, it's fine to put all of
those fixes in one commit.
Being able to keep track of changes as they're made is useful, but it isn't
enough for most projects. You might have multiple people making changes to a
project at the same time. You might want to make some experimental changes
without having to do a bunch of messy reverts if they don't work out. You
might even need to maintain different versions of the same project as if they
were separate sub-projects. To handle these scenarios, Git allows you to
create *branches* within a repo. Each branch is essentially a separate
timeline, tracking one or more commits entirely separate from other commits.
If one commit is made to one branch and another commit is made to another
branch, each branch will only "see" the commit that was made on them. If you
want to take all of the commits from one branch and integrate them into
another, you can *merge* branches. Each branch has a *tip*, which is the
commit most recently made to that branch. Similar to commits, branches can be
checked out. Whatever commit is on the tip of the checked-out branch will end
up checked out when you do this. (Note that checking out a branch is *not*
exactly the same as checking out the commit at the tip of a branch, we'll
cover the differences further later.)
Every so often, you'll create a commit that is, for one reason or another,
special. You may want to quickly look at this commit later, without having to
analyze the repo to figure out where it's at. Git provides *tags* for marking
these special commits. One common use of tags is to mark the last commit made
before releasing a new version of a project. The tagged commit can then be
used to look at the state the repo was in when that version of the project was
released. Tags can be checked out too; checking out a tag is exactly identical
to checking out the commit a tag is associated with.
All of the above features work locally, on your system. Again, a Git repo is
nothing more than a directory with a hidden ``.git`` subdirectory. So what
happens when you want to publish your changes for others to see? How do you
receive changes from others? This is where Git remotes come into play. A Git
remote is a server that has a copy of a Git repo on it. As you make changes to
your copy of the repo, you can *push* a branch to the remote; this will send
commits that you've created to the remote, which will then integrate those
commits into a branch of its copy of the repo. If other people have made
changes to a branch on the remote, and you want to download them, you can
*pull* that branch. Last, but not least, if you want a copy of a Git repo you
don't have yet, you can *clone* the repo from the remote.
Armed with an understanding of these concepts, you are now ready to work with
Git.
Analyzing a Git repo
^^^^^^^^^^^^^^^^^^^^
Remember running ``git clone`` to download the ``lxqt-about-packaging``
project earlier? (See "Testing the packaging environment" above if you don't
remember this.) The ``git clone`` command *clones* a repository. That means we
have a fully functional Git repo already downloaded, so let's take a closer
look at it and see what's in there!
Ensure the ``lubuntu-dev`` window is open and the VM is running. Then open
QTerminal, and change to the ``lxqt-about-packaging`` directory::
cd $HOME/vmshare/pkg/lxqt-about-packaging
Now that we're here, let's look at some basic info about the repo, and see if
any changes have been made to it that haven't been comitted yet. To do this,
run ``git status``. Assuming you haven't changed the repo since we ran the
``sbuild`` command previously, you should see something like this::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
nothing to commit, working tree clean
Let's break down what this means:
* One of the branches in this repo is called ``ubuntu/resolute``. This is the
branch we are actively "on", meaning that all the files in the project's
directory reflect Git's idea of the files on the ``ubuntu/resolute`` branch.
If we make any changes to these files and commit them, our new commit will
end up added to the ``ubuntu/resolute`` branch.
* The ``ubuntu/resolute`` branch does not just exist on our machine, it also
exists on the Git remote named ``origin``. To Git's awareness, our copy of
``ubuntu/resolute`` matches ``origin``'s copy of ``ubuntu/resolute``.
* All of the files in the repo match exactly what's been committed to the
``ubuntu/resolute`` branch (well, all of the files Git cares about anyway,
we'll look at that closer in a bit). We don't have any changes that need
committed. (What we've been calling the "files in the repo", Git calls the
"working tree".)
Most of this is straightforward, except... what is this remote named
``origin``? Let's see what remotes we have::
git remote
Running this command should print the following::
origin
Well that's not particularly useful. Let's look at what the ``origin`` remote
actually points to::
git remote get-url origin
This will output the following::
https://git.lubuntu.me/Lubuntu/lxqt-about-packaging.git
That's much more useful. The ``origin`` remote points to the
``lxqt-about-packaging`` repo on Lubuntu's Gitea instance.
.. NOTE::
When you clone a repository, Git automatically takes the URL you cloned the
repo from, and turns it into the ``origin`` remote. You can have more than
one remote in a repository, and if you spend enough time packaging you'll
probably run into situations where being able to do this is useful, but for
now we'll focus on managing a repo with just one remote.
Git repos generally are worked on by multiple people. Lubuntu's packaging
repos are no exception; each packager needs to be able to upload their changes
to a packaging repo after preparing the packaging for a Lubuntu component.
For this reason, it's generally a good idea to download the latest changes to
a repo before beginning work on it. Let's do that now, just in case someone's
modified ``lxqt-about-packaging``::
git pull
If there were changes, you'll see some info about the download, that starts
with something like this::
remote: Enumerating objects: 54, done.
remote: Counting objects: 100% (54/54), done.
remote: Compressing objects: 100% (29/29), done.
remote: Total 54 (delta 26), reused 51 (delta 23), pack-reused 0 (from 0)
Unpacking objects: 100% (54/54), 331.53 KiB | 1.03 MiB/s, done.
From git.lubuntu.me:Lubuntu/lxqt-about-packaging
Updating 98c3794..fe38a66
Fast-forward
debian/changelog | 1 +
debian/control | 2 +-
debian/copyright | 2 +-
debian/watch | 11 ++++++-----
4 files changed, 9 insertions(+), 7 deletions(-)
Exactly what Git shows here may vary, since it's showing a summary of the
download process, the changes that were downloaded, whether they apply to the
active branch or not, etc. For now we don't have to worry about most of this,
we'll cover how to deal with the edge cases later.
Alternatively, if there weren't any changes, you'll see::
Already up to date.
Now that we have our repo up-to-date, let's look at some of the most recent
commits and see what people have been doing lately::
git log
This will output something like this::
commit fe38a662e5c7e0c87d31a25097e9dc29dfe23554 (HEAD -> ubuntu/resolute, origin/ubuntu/resolute)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Tue Nov 18 13:16:56 2025 -0600
Use debian/watch version 5 (taken from Debian's packages)
commit f90b89bffb8c04c1a0a9979cc02bd077a1d2deff (tag: ubuntu/2.2.0-0ubuntu1, origin/ubuntu/questing)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:16:32 2025 -0500
Update build deps
commit 98c37947c4abdcda7487893cf15a3b73a26d3572
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:16:00 2025 -0500
Bump Standards-Version
commit 65c030aacf544aa46ca0c9159853e3da05edc3e2
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:15:30 2025 -0500
Update copyright file
commit 9f9ab07b6c8483ad890dda9b16f9883f6815a49d
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Thu Jul 31 16:14:31 2025 -0500
Bump version for new upstream release
You'll also notice your Bash prompt is gone, and instead your cursor is
sitting directly after a ``:`` character. That's because you're in a *pager*,
allowing you to scroll the Git log. You can scroll in any direction (including
left and right) using the arrow keys, and can scroll up and down quickly using
the Page Up and Page Down keys. To exit the pager and get back to a normal
Bash prompt, press ``q``.
Now that we're out of the pager, let's look closer at what all this info
means. You don't have to fully understand all of this yet, you can come back
and reference this later.
* Each "section" (separated from adjacent sections by newlines) corresponds to
a single commit.
* Each commit has a unique ID (this is that long hexadecimal number you see
after ``commit`` in each commit entry).
* Each commit has an author, the person who actually wrote the changes that
went into the commit. Authors are identified by name and email address.
(This is why we configured your name and email into Git when we set up the
packaging environment; this info will end up attached to commits you
create.)
* Each commit has a date. This is the exact date and time at which the commit
was created.
* Each commit has a *commit message*. Generally when you make changes to a
repository, you want to document *what* you changed, and *why*. These
records should be put into commit messages. Many repositories (especially
Lubuntu's packaging repositories) just have one-line commit messages briefly
summarizing what was done.
* Some commits have one or more branches displayed next to them. These commits
are on the tip of each respective branch. For instance, if we were to check
out ``ubuntu/resolute``, we'd end up on commit
``fe38a662e5c7e0c87d31a25097e9dc29dfe23554``.
.. NOTE::
Commit IDs are ridiculously long, hard to type, and just about impossible
to remember. To work around this, you can use the first several characters
of a commit ID in place of a full commit ID in most instances. It's
traditional to use the first seven or eight characters, since it's rare for
two commits in the same repo to ever have the same first seven characters,
and it's still short enough to be temporarily memorized and typed without
too much trouble.
* Some commits also have *remote* branches displayed next to them. Remember
when we saw ``origin/ubuntu/resolute`` earlier? That was referring to
``origin``'s copy of ``ubuntu/resolute``. Well, we see it again here. We
also see ``origin/ubuntu/questing``, which is ``origin``'s copy of
the ``ubuntu/questing`` branch.
* We can also see a tag, ``ubuntu/2.2.0-0ubuntu1``. This means that someone
decided that commit ``f90b89b`` should have this "name" associated with it.
In Lubuntu's packaging repos, we use tags to keep track of which commits
correspond to which package version numbers. Whoever packaged ``lxqt-about``
version ``2.2.0-0ubuntu1``, made this tag to say "if you check out
``f90b89b``, you will see the packaging that I uploaded to Ubuntu when I
created version 2.2.0-0ubuntu1 of this package."
* Finally, what's this ``HEAD`` thing? In Git, the "head" is a pointer that
tracks whatever commit you are currently "on". In this instance, HEAD is on
commit ``fe38a66``. The ``->`` in ``HEAD -> ubuntu/resolute`` means that
HEAD is currently *attached* to the branch ``ubuntu/resolute``. That means
that if you make a new commit, not only will ``HEAD`` move to the new
commit, the commit will also be added to the ``ubuntu/resolute`` branch,
thus changing the tip of the branch.
Now that we see what's going on with the log, let's take a look at the
repository's branches. We saw earlier that ``origin`` has an
``ubuntu/questing`` branch, so let's list off all the branches in the
repository and see what all we have::
git branch
This will output::
* ubuntu/resolute
Well that can't be right! There's an ``ubuntu/questing`` branch, so where is
it? To answer that question, we need to think a bit more about how Git works.
* Every developer has their own copy of the repo, and the remote has a copy
too.
* Every developer can modify their repo as they see fit. They have to manually
upload and download changes that have been made to the repo on the remote.
* Developers can create and upload new branches.
* Therefore, **the remote repo might have branches your repo doesn't have.**
Every time you download changes from the remote using ``git pull``, Git finds
out what branches the remote has available, downloads any commits associated
with them, and keeps track of those for you. However, it doesn't change the
branches in your repo. This makes it so that if you and someone else both make
a branch with the same name, you don't end up with serious problems the next
time both of you pull changes from the remote. Instead, Git keeps track of
your repo's branches and the remote repo's branches *separately*.
When you see ``origin/ubuntu/resolute``, that's because there's actually a
branch named ``origin/ubuntu/resolute``. You also have a branch named
``ubuntu/resolute``, and that branch happens to be *tracking*
``origin/ubuntu/resolute``. That means that when changes to
``origin/ubuntu/resolute`` are downloaded, ``ubuntu/resolute`` gets updated
with those changes. It also means if you change ``ubuntu/resolute``, and
upload your changes, those changes will end up in ``origin/ubuntu/resolute``.
It's a two-way sync.
With this in mind, we can better understand why ``ubuntu/questing`` is
missing; we don't have a branch named ``ubuntu/questing`` yet. We do know that
the ``origin`` remote has such a branch though. So let's see what branches
``origin`` has::
git branch -r
This will output quite a bit more information::
origin/HEAD -> origin/ubuntu/resolute
origin/backports/focal
origin/backports/jammy
origin/backports/mantic
origin/ci/stable
origin/ci/unstable
origin/ubuntu/cosmic
origin/ubuntu/disco
origin/ubuntu/eoan
origin/ubuntu/focal
origin/ubuntu/groovy
origin/ubuntu/hirsute
origin/ubuntu/impish
origin/ubuntu/kinetic
origin/ubuntu/lunar
origin/ubuntu/mantic
origin/ubuntu/noble
origin/ubuntu/oracular
origin/ubuntu/plucky
origin/ubuntu/questing
origin/ubuntu/resolute
We see that ``origin`` has a whole lot of branches, including the elusive
``origin/ubuntu/questing`` one we're looking for. There's also
``origin/HEAD``, which can be safely ignored.
.. NOTE::
If you're wondering what ``origin/HEAD`` is, see
`<this answer on Stack Exchange https://superuser.com/a/1192881/1734781>`__.
Now, the question is, how do we add one of these remote branches to our repo?
To do this, we'll simply *check out* the branch::
git checkout ubuntu/questing
This will output the following::
branch 'ubuntu/questing' set up to track 'origin/ubuntu/questing'.
Switched to a new branch 'ubuntu/questing'
And now if we run ``git branch`` again, we'll see::
* ubuntu/questing
ubuntu/resolute
Perfect! Now we have ``ubuntu/questing`` in our repo. We've also switched to
the ``ubuntu/questing`` branch, meaning our repo's working tree matches the
commit at the tip of ``ubuntu/questing``. To illustrate, let's look at a file
in the repository that will be different in ``ubuntu/questing`` and
``ubuntu/resolute``::
cat debian/watch
This will output the following::
version=4
opts="searchmode=plain, \
pgpsigurlmangle=s/$/.asc/, \
uversionmangle=s/(\d+\.\d+\.\d+).*/$1/" \
https://api.github.com/repos/lxqt/@PACKAGE@/releases https:\/\/github.com\/lxqt\/@PACKAGE@\/releases\/download\/@ANY_VERSION@\/@PACKAGE@-@ANY_VERSION@.tar.xz
We'll cover what all of this means once we get into Debian packaging itself.
For the time being though, let's switch back to ``ubuntu/resolute`` and see
what happens::
git checkout ubuntu/resolute
This will output the following::
Switched to branch 'ubuntu/resolute'
Your branch is up to date with 'origin/ubuntu/resolute'.
Now let's look at the ``debian/watch`` file again::
cat debian/watch
This will output the following::
Version: 5
Template: GitHub
Owner: lxqt
Project: @PACKAGE@
Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
Pgp-Mode: auto
That looks entirely different than what we saw a bit ago! What happened there?
We can find out what happened, by taking a look at the Git history. First,
let's find out who made the changes to this file::
git blame debian/watch
This will show us a line-by-line breakdown of the file, indicating what
commits changed each line in the file, who made them, and when they made
them::
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 1) Version: 5
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 2) Template: GitHub
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 3) Owner: lxqt
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 4) Project: @PACKAGE@
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 5) Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
fe38a662 (Aaron Rainbolt 2025-11-18 13:16:56 -0600 6) Pgp-Mode: auto
See the ``fe38a662``? That's a partial commit ID! If we look back at the
output of ``git log`` from earlier, we can see the full commit ID, and
corresponding commit message::
commit fe38a662e5c7e0c87d31a25097e9dc29dfe23554 (HEAD -> ubuntu/resolute, origin/ubuntu/resolute)
Author: Aaron Rainbolt <arraybolt3@ubuntu.com>
Date: Tue Nov 18 13:16:56 2025 -0600
Use debian/watch version 5 (taken from Debian's packages)
Now that we know who changed the file and when, let's see what their changes
looked like. Each Git commit corresponds to a *state* the repo was in when the
commit was made, so we can't simply say "show me commit XYZ". What we *can*
say is "show me everything that changed between commit ABC and commit XYZ".
Furthermore, Git allows us to refer to the commit *before* a particular commit
by adding a caret (``^``) after a commit ID. That is, ``fe38a662^`` refers to
the commit immediately *before* ``fe38a662``. Now that we have two commits to
compare to each other, we can tell Git to show us what changed between those
commits::
git diff fe38a662^ fe38a662
This will output the following::
diff --git a/debian/copyright b/debian/copyright
index 7b74a60..6ee271d 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -11,10 +11,10 @@ License: LGPL-2.1+
Files: debian/*
Copyright: 2021-2025 Lubuntu Developers <lubuntu-devel@lists.ubuntu.com>
- 2022 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
2014-2019 Alf Gaida <agaida@siduction.org>
2015-2021 Andrew Lee (李健秋) <ajqlee@debian.org>
2025 Aaron Rainbolt <arraybolt3@ubuntu.com>
+ 2022-2025 ChangZhuo Chen (陳昌倬) <czchen@debian.org>
License: LGPL-2.1+
License: LGPL-2.1+
diff --git a/debian/watch b/debian/watch
index a544020..4070de7 100644
--- a/debian/watch
+++ b/debian/watch
@@ -1,5 +1,6 @@
-version=4
-opts="searchmode=plain, \
-pgpsigurlmangle=s/$/.asc/, \
-uversionmangle=s/(\d+\.\d+\.\d+).*/$1/" \
- https://api.github.com/repos/lxqt/@PACKAGE@/releases https:\/\/github.com\/lxqt\/@PACKAGE@\/releases\/download\/@ANY_VERSION@\/@PACKAGE@-@ANY_VERSION@.tar.xz
+Version: 5
+Template: GitHub
+Owner: lxqt
+Project: @PACKAGE@
+Download-Url-Mangle: s%https://api.github.com/repos/([^/]+)/@PACKAGE@/git/refs/tags/@ANY_VERSION@%https://github.com/$1/@PACKAGE@/releases/download/$2/@PACKAGE@-$2.tar.xz%g
+Pgp-Mode: auto
We won't go into all the intricacies of the diff format used here. Suffice to
say, lines prefixed with a ``-`` are lines that were *removed*, and lines
prefixed with a ``+`` are lines that were *added*. In this case we can see
that ``debian/watch`` got completely rewritten. We also see that in
``debian/copyright``, ChangZhuo Chen was moved to the bottom of the
``Files: debian/*`` section, and the ``2022`` copyright reference was changed
to ``2022-2025``. Why exactly this was done, only the committer knows, but
that's what was done. The commit log conveniently lists Aaron's email, so now
you can now email Aaron Rainbolt and ask him why he did this.
.. NOTE::
The reason Aaron (me) made this change was because the ``debian/watch``
file in this package was previously broken, and the Debian LXQt Team's
``debian/watch`` file was working. I copied their file, and plugged it into
Lubuntu's packaging to get things to work right. The Git repo for Debian's
``lxqt-about`` package listed ChangZhuo Chen as the author of the new watch
file contents, so I moved their copyright info to the bottom and updated
their copyright date accordingly.
.. WARNING::
If you're security-conscious, you might be asking yourself, "what's to stop
me from pretending to be Aaron or anyone else when I make a change to the
repo?" The answer is, **nothing prevents this by default.** Impersonating
others in Git logs is very easy. This is part of why projects don't hand
out Git commit access to just anyone; it would be too easy to do something
malicious and make someone else look like they were at fault. Even this
isn't bullet-proof though, a contributor might become malicious, or someone
might hack the server that hosts the remote Git repo. Thankfully, there is
a good way to make it impossible for others to impersonate you, which is to
PGP-sign your Git commits. More on that later.
This is all well and good if we just want to analyze the differences between
individual commits. But what if we want to diff something else? Perhaps
someone recently pushed five commits to the ``ubuntu/resolute`` branch, and we
want to see what the repo looked like before and after their changes. There
are a few ways we can do this:
* The most obvious way is to use ``git log`` to identify the commits we want
to compare, and then compare them with ``git diff``. For instance, if the
commit HEAD is on is ``12345678``, and the commit immediately before the
batch of changes is ``abcdefab``, we can compare them with
``git diff abcdefab 12345678``.
* As a shortcut, if we want to refer to the HEAD commit, we don't have to type
out the full commit ID HEAD is on. We can just use ``HEAD``, like so:
``git diff abcdefab HEAD``.
* We can also combine ``HEAD`` with ``^``. If we want to compare the commit
HEAD is on with the commit before it, we can use ``git diff HEAD^ HEAD``.
This only goes back one commit though, and we want to go back five, so...
* You can actually use ``^`` multiple times to go back more than one commit!
To compare the commit that is five commits back from HEAD, with the commit
HEAD is on, we can use ``git diff HEAD^^^^^ HEAD``.
* Typing ``^`` over and over can become cumbersome. As a shortcut, you can use
``~N`` after a commit ID, where ``N`` is the number of commits to go
backward. This works with ``HEAD`` too, so we can use
``git diff HEAD~5 HEAD``. This will give the exact same results as
``git diff HEAD^^^^^ HEAD``.
It's possible to diff "things" other than commits also, so long as those
"things" point at commits. Branches and tags can be compared to other
branches, other tags, commits IDs, or HEAD. Git calls all of these things
"refs". Diffing two refs will diff the commits the refs point to.
Modifying a Git repo
^^^^^^^^^^^^^^^^^^^^
Most of the above covers how to look at existing commits and refs. What if we
want to make some changes to the repo and commit them? Let's try doing that
now, by changing the ``debian/control`` file. Open the file in a text editor
such as Featherpad, by running ``featherpad debian/control`` Scroll to the
bottom of the file, then change the description for ``lxqt-about-l10n`` to
say::
The about screen for LXQt
.
This package contains the l10n files needed by the lxqt-about.
It also contains an extra line in the description for qwerty.
(You will be adding the last line to the description here. Mind the extra
space at the start of each line, those are intentional. We'll cover those
further when we get into packaging itself.)
.. NOTE::
You can use whatever text editor you want, but once you start doing a lot
of packaging, you'll probably want to learn how to use a text editor that
runs directly in the terminal. This is a lot more efficient than having to
switch between a terminal window and a GUI text editor. The editor
generally recommended for Lubuntu development is Vim, which comes
preinstalled on Lubuntu. You can get started with learning to use Vim by
running ``vimtutor`` in a terminal window.
Save and close the file. Now let's see what Git thinks about what we've just
done, by running ``git status``. It will output the following::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: debian/control
no changes added to commit (use "git add" and/or "git commit -a")
Git sees that we modified the file, but claims that the changes we've made
aren't "staged for commit". In order to allow users to break up large changes
into multiple commits, Git uses the concept of a staging area (called the
"index"). Changes made to a repo have to be "staged" before they can be
committed, and when a new commit is made, only staged changes go into the
commit. This means if you make two unrelated changes to different parts of the
repo, you can stage one of those changes and commit it, while leaving the other
change unstaged. Once the first commit is made, you can stage and commit the
other change.
Changes can be staged by using ``git add``. You'll generally want to use
``git add`` in one of three ways:
* To stage an individual file, use ``git add path/to/file``. For instance, to
stage the changes we just made to ``debian/control``, you can use
``git add debian/control``.
* To stage all of the files that changed within a directory, use
``git add path/to/dir/*``. You can stage the changes made to
``debian/control`` using ``git add debian/*``. Note that this recurses into
subdirectories; if ``debian/control`` and ``debian/upstream/metadata`` were
both changed, ``git add debian/*`` would stage both those files.
* Finally, to stage *all* changes made within the repo, use ``git add -A``.
This is probably the way you'll use ``git add`` the most.
Now that we've staged the change, let's see what ``git status`` reports now::
On branch ubuntu/resolute
Your branch is up to date with 'origin/ubuntu/resolute'.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: debian/control
We're now ready to commit a change finally! As one might expect, changes are
committed by using ``git commit``.
* The easiest way to use this command is to just run ``git commit``; a text
editor will be opened, allowing you to write a description of the changes
you've made. This is good for if you need to write a long, detailed
explanation of what you've changed, but most of the time you won't need to
do that.
* If you only want a short description for the changes, you can give Git the
description by using ``git commit -m 'description here'``.
Let's commit the change we just made, by running
``git commit -m 'update lxqt-about-l10n description'``. You should now see
something similar to the following::
[ubuntu/resolute 4686cc1] update lxqt-about-l10n description
1 file changed, 1 insertion(+)
Congratulations, you have now committed your first change! If we look at
``git status`` now, we'll see::
On branch ubuntu/resolute
Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
This should all look familiar by now, except for the line
``Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.`` This means
that our copy of ``ubuntu/resolute`` has one commit in it that ``origin``'s
copy of ``ubuntu/resolute`` doesn't have yet.
Let's see what happens if we create a new file in the repo. Make sure you're
in the ``lxqt-about-packaging`` directory, and then create a file named
``test.md`` with the following contents::
# Test file
This is a test file.
Save and close the file, then run ``git status`` again. You should see::
On branch ubuntu/resolute
Your branch is ahead of 'origin/ubuntu/resolute' by 1 commit.
(use "git push" to publish your local commits)
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.md
nothing added to commit but untracked files present (use "git add" to track)
This is a bit different than what we saw earlier. That's because previously,
we modified a file Git already knew about in this repo, namely
``debian/control``. Git does not yet know that ``test.md`` is actually part of
the repo, so it's considered *untracked*. Git treats untracked files and
unstaged changes to tracked files in subtly different ways we'll investigate
later, but for now, you can mostly treat untracked files the same way you'd
treat any other modified file. We don't actually need to commit this file to
continue learning how Git works, so go ahead and delete the file with
``rm test.md``.
Inevitably, as you work on packaging, you are going to make mistakes, and
inevitably, you're going to end up committing some of those mistakes. Let's
look at the line we added to ``debian/control`` a bit ago::
It also contains an extra line in the description for qwerty.
This probably looked a bit strange to you. "qwerty" really doesn't describe
what we're doing accurately. "testing" would be a better word to use. Go ahead
and change ``qwerty`` to ``testing`` now, using Featherpad or your preferred
text editor. Stage the change with ``git add -A`` when you're done.
Now, we *could* just add the fix as a new commit. However, in this instance,
that's probably not the best thing to do since the commit exists only on your
computer. No one else knows that we wrote "qwerty" when we meant "testing", so
we can simply recreate the commit. To do that, use
``git commit --amend --no-edit``. This will discard the previous commit, and
create a new one that contains all of the changes from the previous commit
plus all of the changes in the index. It will also preserve our commit message
unchanged (that's what ``--no-edit`` does). If you want to change the commit
message too (or if the only thing you want to change is the commit message),
you can leave off ``--no-edit`` to get a commit message editor, or you can use
``-m`` to pass a one-line commit message, just like you would when using ``git
commit`` normally.
.. NOTE::
I intentionally avoided saying that we're *changing* the commit here.
Commits are immutable, and each commit links to the commit immediately
behind it. This allows Git to provides a number of helpful features, but it
also means that one shouldn't think in terms of "changing" a commit.
.. NOTE::
``git commit --amend`` is useful for recreating the last commit. But what
if we need to fix something from several commits ago? Because commits are
linked together and immutable, we have to recreate the bad commit and every
commit ahead of it. Git provides a utility for this, ``git rebase``, but
you'll rarely need it and it is tricky to use, so we won't cover this yet.
Amending a commit is useful for fixing something wrong with a commit. But what
if the commit is so far gone you'd rather just get rid of it and start over?
This extra line in the description of ``lxqt-about-l10n`` isn't really useful,
and it's probably not something that can be made useful in the future. This is
where ``git reset`` comes in handy. Whereas ``git commit`` creates commits,
``git reset`` destroys them. You can use ``git reset`` in a few different
ways, the two most useful are:
* Mixed reset, which gets rid of commits but leaves the actual changes made in
those commits in your working tree. Mixed reset is the default mode.
* Hard reset, which gets rid of commits *and* resets all tracked files to
a previous state. Note well that this resets *tracked* files. Files that are
untracked will be left alone even by a hard reset. This is one of the ways
Git treats tracked and untracked files differently.
To do a mixed reset to the previous commit, run::
git reset HEAD^
This will leave our changes intact, but the commit itself will be undone and
the changes will *not* be staged for commit. You can now make more changes and
then make a new commit, or you can completely undo all of our changes with::
git add -A
git reset --hard HEAD
This will first stage all modifications, so that any untracked files are
caught. Then it will hard-reset the repo back to the commit HEAD is pointing
to, discarding our changes.
When you're doing work in a Git repo, it's generally a good idea to work on a
branch other than the "primary" branch (``ubuntu/resolute`` in this instance).
This makes it easier to avoid running into conflicts with other developers.
It is possible to use ``git branch`` to create branches, but most of the time
when you're making a branch, you also want to switch to the branch. For this,
you can use the ``-b`` option of ``git checkout``, like so::
git checkout -b my-new-branch
Note that ``git checkout -b`` will fail if the branch already exists.
This is a good enough start for now. There are a few more things you'll want
to know about Git in your work as a packager (namely rebasing, tagging,
forking, and pushing), but it will be easiest to cover those as we need them.
Miscellaneous useful things to know about Git
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* You can diff the contents of your working tree with the HEAD commit by using
``git diff HEAD``. This is useful to see what all you've changed before
committing it. Note that untracked files will be ignored when you do this.
If you want to include those in the diff too, ``git add`` them first.
* Raw diffs are oftentimes hard to read. If you want a more comfortable
viewing experience, install Meld (``sudo apt install meld``), then use
``git difftool --tool=meld --dir-diff`` instead of ``git diff``. This will
let you visualize the changes between two commits in much more detail than a
raw diff allows. You can even make (limited) changes to your working tree
here by modifying files on the right side of the Meld window. (Be warned
that changes made to the left side of the window will be silently discarded,
and that copying files from the left side of the window to the right side
will not work; those files will be silently discarded as well.)
* You can checkout more than just branches. ``git checkout`` can be used on
any ref. If you check out something other than a branch however, you will
enter what Git calls "detached HEAD state". This means that any commits you
make will not be integrated into any existing branch, and if you check
something else out later, you'll probably have a hard time finding those
commits later. If you decide you want to keep the changes you've made, use
``git switch -c new-branch-name`` to make a new branch with any changes
you've made so far.
* As a general rule, do not use force-push unless you're fixing commits you
just pushed. Other users may have pulled those commits, and will have to fix
their repos if you do this. There are exception to this rule, you'll be able
to spot them as you gain experience.
Foundations of Debian packaging
-------------------------------
TODO