Bisecting Fedora kernel

This post shows how to bisect a Fedora kernel to find the source of a regression. I needed that recently and I found no good guide, so I’m at least capturing my notes here, perhaps you find it useful. This approach can be used to identify which exact commit caused a bad kernel behavior on your hardware, and then report it to kernel maintainers. Note, you need to have a reliable way of reproducing the problem. If it happens randomly and infrequently, it’s much harder to debug.

0. Try the latest Rawhide kernel

Before you spend too much time on this, it’s always worth a shot to test the latest Rawhide kernel. Perhaps the bug is fixed already?

Usually the kernel consists of these installed packages: kernel, kernel-core, kernel-modules, kernel-modules-core, kernel-modules-extra. But see what you have installed on your system, e.g. with: rpm -qa | grep ^kernel | sort .

Install the latest Rawhide kernel:

sudo dnf update --setopt=installonly_limit=0 --repo fedora --releasever rawhide kernel{,-core,-modules,-modules-core,-modules-extra}

You want to use --setopt=installonly_limit=0 throughout this exercise to make sure you don’t accidentally remove a working kernel from your system and don’t end up with just broken ones (there’s a limit of three kernels installed at the same time by default). But it means you’ll need to remove tested kernels manually from time to time, otherwise you run out of space in /boot.

Reboot and keep pressing F8 during startup to display the GRUB boot menu. Make sure to select the newly installed kernel, boot it, test it. Note down whether it’s good or bad. If the problem is still there, we’ll need to continue debugging.

Note: When you want to remove that tested kernel, obviously you can’t be currently running from it. Then use standard dnf remove to get rid of it, or use dnf history for a more convenient way (e.g. dnf history undo last).

I. Narrow down the issue in Fedora-packaged kernels

As the first step, it’s useful to figure out which Fedora-packaged kernel is the last one with good behavior (a “good kernel“), and which one is the first one with bad behavior (a “bad kernel“). That will help you narrow down the scope. It’s much faster to download and install already built kernels than to compile your own (which we’ll do later).

Most probably you’re currently running a bad kernel (because you’re reading this). So reboot, display the GRUB boot menu and boot an older kernel. See if it’s good or bad, note it down. Unless the problem is very recent, all available kernels (usually three) in the GRUB menu will be bad. It’s time to start downloading older kernels from Koji. Use a reasonable strategy, e.g. install a month old kernel, or several months old, and gradually halve the intervals and narrow down until you find the latest good kernel. You don’t need to worry about using kernels from other Fedora releases (as you can see in their .fcNN suffix), they are standalone and work in any release. You can download the kernel subpackages manually, or use koji command (from the koji package), e.g.:

koji download-build --arch x86_64 kernel-6.5.0-0.rc6.43.fc39

That downloads many more subpackages than you need, so install just those needed (see the previous section), e.g. like this:

sudo dnf --setopt=installonly_limit=0 install ./kernel{,-core,-modules,-modules-core,-modules-extra}-6.5*.rpm

For each picked kernel, install it, boot into it, test it, note down whether it’s good or bad. Continue until you’ve found the latest good packaged kernel and the first bad packaged kernel.

II. Find git commits used for building identified good and bad kernels

Now that you have the closest good and bad packaged kernel, we need to figure out which git commits from the upstream Linux kernel were used to build them. In some cases, the git commit hash is included directly in the RPM filename. For example in my case, I reported that kernel-6.4.0-0.rc0.20230427git6e98b09da931.5.fc39 is the last good kernel, and kernel-6.4.0-0.rc0.20230428git33afd4b76393.7.fc39 is the first bad kernel. From those filenames, you can see that git commit 6e98b09da931 is good and git commit 33afd4b76393 is bad.

Not always is the commit hash part of the filename, e.g. with the example of kernel-6.5.0-0.rc6.43.fc39. In this case, you need to download the .src.rpm file from that build. Either manually from Koji, or using:

koji download-build --arch src kernel-6.5.0-0.rc6.43.fc39

Unpack that .src.rpm (my favorite decompress tool is deco), find linux-*.tar.xz archive and run the following command (adjust the archive filename):

$ xzcat -qq linux-6.5-rc6.tar.xz | git get-tar-commit-id
2ccdd1b13c591d306f0401d98dedc4bdcd02b421

(This command is documented in the kernel.spec file, also in that directory). Now you know the git commit hash used for that kernel build. Figure out commits for both the good and bad kernel you identified.

III. Use git bisect to find the exact commit that broke it

It’s time to clone the upstream Linux kernel repo:

git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ~/src/linux

And also the Fedora distgit kernel repo:

fedpkg clone -a ~/distgit/kernel

We’ll now use git bisect to arrive at the breaking commit which caused the problem. After each step, we’ll need to build the kernel, test it, and mark it as good or bad. Let’s start:

cd ~/src/linux
git bisect start
git bisect good YOUR_GOOD_COMMIT
git bisect bad YOUR_BAD_COMMIT

Git now prints a commit hash to be tested (and switches the repository to that commit), and an estimate of how many steps remain. We now need to take the current contents of the source code and build our own kernel.

Note: When building the kernel, I was advised to avoid the overhead of packaging, to speed up the process. I’m sure it’s a good advice, but I didn’t find a good guide on how to do that (including how to retrieve the Fedora kernel config, build the kernel manually, copy it to the right places, create initramfs, create a boot option in GRUB, etc). So I just ran the whole process including packaging. On my machine, the compilation took about 40 minutes and packaging took 10 minutes, and I needed to do about 11 rounds, so it was an OK tradeoff for me. (If you can write a guide how to do that without packaging, please do and link it in the comments, I’d love to read it).

Let’s create a tarball of the current source code like this:

git archive --prefix=linux-local/ HEAD | xz -0 -T0 > linux-local.tar.xz

Usually the tarballs have a version number in both the filename and the included directory (which is then also matched in a spec file). You can do that if you wish, I didn’t want to spend too much time on throwaway builds, so I just used a static filename and overwrote it each time.

Let’s move the tarball to the distgit repo:

mv ~/src/linux/linux-local.tar.xz ~/distgit/kernel/

Now we need to adjust the distgit spec file a bit:

cd ~/distgit/kernel
# edit kernel.spec

I made the following changes to the spec file:

-# define buildid .local
+%define buildid .local
-%define specrpmversion 6.4.9
+%define specrpmversion 6.4.0
-%define specversion 6.4.9
+%define specversion 6.4.0
-%define tarfile_release 6.4.9
+%define tarfile_release local
-%define specrelease 200%{?buildid}%{?dist}
+%define specrelease 0.gitYOUR_TESTED_COMMIT%{?buildid}%{?dist}

Now we can start the build:

nice fedpkg mockbuild --with baseonly --with vanilla --without debuginfo

Options --with baseonly and --without debuginfo make sure we don’t build unnecessary stuff. --with vanilla was needed, because Fedora-specific patches didn’t apply to the older source code.

After a long time, your results should be available in results_kernel/ and look something like this:

$ ls -1 results_kernel/6.4.0/0.git6e98b09da931.local.fc38/
build.log
hw_info.log
installed_pkgs.log
kernel-6.4.0-0.git6e98b09da931.local.fc38.src.rpm
kernel-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-core-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-devel-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-devel-matched-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-modules-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-modules-core-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-modules-extra-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-modules-internal-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
kernel-uki-virt-6.4.0-0.git6e98b09da931.local.fc38.x86_64.rpm
root.log
state.log

See that all the RPMs have the git commit hash identifier that you specified in the spec file. Now you just need to install the kernel (see in a previous section), boot it (make sure to display the GRUB menu and verify that the correct kernel is selected), and test it.

Note: If you have Secure Boot enabled, you’ll need to disable it in order to boot your own kernel (or figure out how to sign it yourself). Don’t forget to re-enable it once this is all over.

Once you’ve determined whether this kernel is good or bad, tell it to git bisect:

cd /src/linux
git bisect good   # or bad

And now the whole cycle repeats. Create a new archive using git archive, move it to the distgit directory, adjust the specrelease field in kernel.spec to match the new commit hash, and use fedpkg to build another kernel. Eventually, git bisect will print out the exact commit that caused the problem.

IV. Report your findings

Report the problem and the identified breaking commit into Red Hat Bugzilla under the kernel component. Please also save and attach the bisect log:

cd /src/linux
git bisect log > git-bisect-log.txt

Then also report this problem (possibly a regression) to the kernel upstream and mention it in the RH Bugzilla ticket. Thanks and good luck.

Show a side-by-side git diff on any commit in tig using Meld

Side-by-side diffs are more readable to me than in-line diffs. Long time ago, I started using Meld to display them when working with git. But I always needed to manually specify branch or commit names. This week I finally spent some time and found a way to invoke Meld directly from tig, so that I can see the diff side-by-side while browsing a commit history in tig (for example, when I want to review a proposed branch containing 10 new commits, and I want to inspect each of them individually). Here’s a short howto.

First, let’s configure Meld as your git difftool:

git config --global diff.tool meld

You can now see a diff between two branches/commits with:

git difftool -d branch1 branch2

That’s a lot of typing, though, so let’s create a handy alias:

git config --global alias.dt 'difftool -d'

And now you can use:

git dt branch1 branch2

And now, let’s integrate this into tig. Edit ~/.config/tig/config and add this snippet:

# use difftool to compare a commit in main/diff view with its parent
# https://github.com/jonas/tig/issues/219#issuecomment-406817763
bind main w !git difftool -d %(commit)^!
bind diff w !git difftool -d %(commit)^!

Notice I chose the “w” key as a shortcut key, because it’s unassigned by default. You can choose a different shortcut of course, see man tigrc.

Now anytime you want to see a side-by-side diff on any commit displayed in tig:

You simply press w and you’ll see the diff between the commit and its parent show up in Meld:

This improved my life a lot, perhaps it helped you as well 🙂 Cheers.

Git Tip of the Day – viewing diffs graphically with meld

I got tired of viewing diffs in a terminal with red and green text. I wanted to see diffs more comfortably, with syntax highlighting, segment highlighting (not just full lines), easy scrolling, searching, switching between files. I wanted to have something like Phabricator diff, Netbeans diff or PyCharm diff. So I finally spent 20 minutes (why do most annoyances take you 20 minutes to solve, but you delay them for years?) and found out that git now has a cool new command, git difftool. And it supports meld, a great graphical comparison program.

Let’s compare this:

clidiff
git diff in gnome-terminal

With this:

meld1
meld diff overview

meld2
meld file diff

Awesome, right?!! All you need to do is this:

$ git difftool --tool=meld -d <diffspec>

Option -d makes it work in directory mode, which works much better with meld. Of course this is too long to type every time, to let’s remember meld as the default diff tool, and even make an alias for it:

$ git config --global diff.tool meld
$ git config --global alias.dt 'difftool -d'

(Also, don’t forget to enable syntax highlighting in meld preferences, it was disabled for me for some reason.)

And now, voila:

$ git dt master..develop

I’m very happy about the increased readability and productivity.

 

Flattr this

Git Tip of the Day – The Ultimate Tip

If you like this Git tip series, I have something for you. If you ever feel hunger for more Git tips, these are my recommendations:

  • Pro Git book — Read this book if you want to work seriously with Git. It’s good.
  • Git Ready website — There are so many Git tips that you will feel like in Git heaven. Or Git hell.
And that’s all, ladies and gentlemen. Thanks for your attention.

Flattr this

Git Tip of the Day – recover lost commits

Today’s tip will be a little different. I found an interesting information about recovering lost commits in the Pro Git book. And because this book is under CC BY-NC-SA licence, I’ll copy the whole section (slightly modified) here. No sense in re-writing it. Of course the license still applies for that text below.


At some point in your Git journey, you may accidentally lose a commit. Generally, this happens because you force-delete a branch that had work on it, and it turns out you wanted the branch after all; or you hard-reset a branch, thus abandoning commits that you wanted something from. Assuming this happens, how can you get your commits back?

Here’s an example that hard-resets the master branch in your test repository to an older commit and then recovers the lost commits. First, let’s review where your repository is at this point:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Now, move the master branch back to the middle commit:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

You’ve effectively lost the top two commits — you have no branch from which those commits are reachable. You need to find the latest commit SHA and then add a branch that points to it. The trick is finding that latest commit SHA — it’s not like you’ve memorized it, right?

Often, the quickest way is to use a tool called git reflog. As you’re working, Git silently records what your HEAD is every time you change it. Each time you commit or change branches, the reflog is updated. You can see where you’ve been at any time by running git reflog:

$ git reflog
1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating HEAD
ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD

Here we can see the two commits that we have had checked out, however there is not much information here. To see the same information in a much more useful way, we can run git log -g, which will give you a normal log output for your reflog.

$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:22:37 2009 -0700

    third commit

commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

     modified repo a bit

It looks like the bottom commit is the one you lost, so you can recover it by creating a new branch at that commit. For example, you can start a branch named recover-branch at that commit (ab1afef):

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Cool — now you have a branch named recover-branch that is where your master branch used to be, making the first two commits reachable again. Next, suppose your loss was for some reason not in the reflog — you can simulate that by removing recover-branch and deleting the reflog. Now the first two commits aren’t reachable by anything:

$ git branch -D recover-branch
$ rm -Rf .git/logs/

Because the reflog data is kept in the .git/logs/ directory, you effectively have no reflog. How can you recover that commit at this point? One way is to use the git fsck utility, which checks your database for integrity. If you run it with the --full option, it shows you all objects that aren’t pointed to by another object:

$ git fsck --full
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

In this case, you can see your missing commit after the dangling commit. You can recover it the same way, by adding a branch that points to that SHA.


Enjoy!

Flattr this

Git Tip of the Day – rebasing is fun

Rebasing means re-ordering the commits in history. It always changes history. It means rebasing is good for your local branches, but not for branches that many people work on. Because in that case changed history means a lot of annoyed programmers.

Rebasing feature branch to master

The most common use-case is probably with feature branches. You split your feature branch from master. You develop a feature, appending a few commits. Now, you would like to send a patch to the project. But in the meantime the master branch moved. Project developers politely ask you to send the patch based on the current master. What to do? Rebase against master.

$ git checkout feature
$ git rebase master

This will find the common ancestor of feature and master (the split point). Then it will apply all changes from master. And then it will apply all changes you have done on feature since the split point. See the images.

Your code will then seem as if you based it on the current master. You can then do a simple patch git diff master feature and send it.

Rebasing a single branch

Rebasing can also be used on a single branch. It can re-order the commits in history, delete some commits, squash some commits, and more.

Let’s say I have realized I would like to have my last three commits in the feature branch in a different order:

$ git rebase --interactive HEAD~3
pick bb6f59e Update minimum python version requirement to 2.6 in the spec file.
pick 4e929b8 Improve docs in autoqa package.
pick 7796850 changed NEWS

# Rebase d03c059..7796850 onto d03c059
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

HEAD~3 denotes the fourth most recent commit. That is the base, the last commit that stays intact. It opens an editor with the last three commits in their commit order, most recent being last. You can now easily change commit order by re-ordering the lines. You can also delete commit by deleting the line.

If you change the keyword in the first column, you can edit commit, reword it, squash several commits into one, and many more. Play with it, it’s fun. If you choose to edit some commit, the rebasing process will stop at that point and allow you to do the changes. After you’re done, just execute git rebase --continue to continue with the process. If git find some conflict it can’t solve, it will again stop, you resolve the conflict and use the same command for continuing. You can abort the whole process anytime, if you wish, by issuing git rebase --abort.

Of course, always back-up your code by keeping a back-up branch before performing a difficult rebase process.

Enjoy!

Flattr this

Git Tip of the Day – changing last commit

Very often you realize you want to change something right after you’ve committed it. At least I do. How to change the last commit?

It is extremely easy to do. Let’s say you want to change just the commit message. All you need is to run:

$ git commit --amend

That’s all. An editor will open up for you with the last commit message and you can edit it. Piece of cake.

Now something more advanced! You realized you forgot to add some file/remove some file/change some file. You need to change the file contents of the last commit. How to do that?

It’s easy. First, do all the changes you need:

$ vim file_to_be_changed
$ touch file_to_be_added

Now put the desired changes into the staging area:

$ git add file_to_be_changed
$ git add file_to_be_added
$ git rm file_to_be_removed

And now run the same magic command as before:

$ git commit --amend

That’s all. Not only git will allow you to change the commit message, but it will also add the changes in the staging area to the last commit. Work done. Ask for pay raise.

Important warning at the end: Changing last commit (even just the commit message) changes SHA1 checksum of the commit. Never change commits that you already pushed to some remote location if there is a chance somebody else already based his/her work on it. You would cause such person a lot of difficulties. Change commits for your local development only.

Enjoy!

Flattr this

Git Tip of the Day – splitting changes into several patches

Sometimes you change the code and then you realize you would like to commit it as several separate patches. For example you fixed two bugs at once but then you decided you want to have the commits separated into two logical units – first bug fix and second bug fix.

As long as the changes don’t involve the same file, it’s easy to do. You just git add first file, commit, git add second file, commit. But what will you do if the two changes involved the same file?

There is a simple solution: git add --patch

Let’s see an example. I have made two changes in my README file – deleted one line and added one line. I want to stage it as two separate patches.

$ git add --patch README
diff --git a/README b/README
index 05ad773..2768fc1 100644
--- a/README
+++ b/README
@@ -1,4 +1,3 @@
-Welcome to autoqa!

 Everything here is under heavy development so don't expect anything to
 stay stable - modules, APIs, whatever.  If it's expected to stay stable we
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -8,6 +7,7 @@ but if you have questions please feel free to contact us.
 == Architecture ==

 There's four main things in this tree: 'autoqa', 'events', 'watchers', and 'tests'.
+foo!

 'autoqa' is the main binary that kicks off tests (using the autotest framework -
 see http://autotest.kernel.org/ for details). It's typically supposed to be 
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n

Git detects “hunks” (changed sections) in the file and asks for every hunk whether you want to stage it or not (see the prompt “Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?“) . You can see that I staged the first hunk, and I didn’t staged the second one. (For explanation of the all the letters in the prompts type in the question mark.).

Now I see something like this:

$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	modified:   README
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   README
#

Some parts of README were staged, some parts were not. I can display the difference between my HEAD and the staging area:

$ git diff --staged 
diff --git a/README b/README
index 05ad773..bbc5249 100644
--- a/README
+++ b/README
@@ -1,4 +1,3 @@
-Welcome to autoqa!

 Everything here is under heavy development so don't expect anything to
 stay stable - modules, APIs, whatever.  If it's expected to stay stable we

You can see that only one changed (the deleted line) is part of my staging area. Now I can commit and only this change will be part of it. Then I’ll stage the remaining changes in README and commit again. I’ve just easily split two changes in one file into two different commits. Voila!

Interactive mode

You can do the same way through interactive mode if you prefer:

$ git add --interactive 
           staged     unstaged path
  1:    unchanged        +1/-1 README

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now>

You are presented with the list of changed files and you can do different operations on them. Don’t forget to press h or 8 in this step. Action patch or 5 is exactly the same we saw with git add --patch. After you select which file to work on (and press Enter once more), you select all the desired hunks:

What now> p   
           staged     unstaged path
  1:    unchanged        +1/-1 README
Patch update>> 1
           staged     unstaged path
* 1:    unchanged        +1/-1 README
Patch update>> 
diff --git a/README b/README
index 05ad773..2768fc1 100644
--- a/README
+++ b/README
@@ -1,4 +1,3 @@
-Welcome to autoqa!
 
 Everything here is under heavy development so don't expect anything to
 stay stable - modules, APIs, whatever.  If it's expected to stay stable we
Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? y
@@ -8,6 +7,7 @@ but if you have questions please feel free to contact us.
 == Architecture ==
 
 There's four main things in this tree: 'autoqa', 'events', 'watchers', and 'tests'.
+foo!
 
 'autoqa' is the main binary that kicks off tests (using the autotest framework -
 see http://autotest.kernel.org/ for details). It's typically supposed to be 
Stage this hunk [y,n,q,a,d,/,K,g,e,?]? n

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now>

Now I can display the status:

What now> s
           staged     unstaged path
  1:        +0/-1        +1/-0 README

*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now>

In the README file I have 0 lines added and 1 line removed in the staging area, and one line added and zero lines removed in the unstaged area.

I can easily display the diff between HEAD and the staging area:

What now> d
           staged     unstaged path
  1:        +0/-1        +1/-0 README
Review diff>> 1
diff --git a/README b/README
index 05ad773..bbc5249 100644
--- a/README
+++ b/README
@@ -1,4 +1,3 @@
-Welcome to autoqa!
 
 Everything here is under heavy development so don't expect anything to
 stay stable - modules, APIs, whatever.  If it's expected to stay stable we
*** Commands ***
  1: status	  2: update	  3: revert	  4: add untracked
  5: patch	  6: diff	  7: quit	  8: help
What now>

That’s exactly what I want. Now I can just quit the interactive mode and commit.

The first approach (non-interactive) seems faster to me, but interactive mode can be also appealing for some people.

Enjoy!

Flattr this

Git Tip of the Day – double and triple points syntax

There’s a lot of cryptic syntax in git. Let’s talk today about using double and triple points in commands.

Double points syntax is used mostly when selecting an interval of commits. Let’s see an example:

$ git log master..feature

This will show you all commits in the feature branch that were added since it branched off from master. To be more precise – two points syntax will select all commits available in the feature branch and not available in the master branch.

By the way, you can also write the command above like this:

$ git log feature --not master

Again it means select all commits from freature that are not in master. The advantage of the latter syntax is that you can use it with more than two branches:

$ git log feature1 feature2 --not master

This will show you commits from two feature branches that are not in master.

Of course this is not just about log command. All commands working with commit intervals should support it. E.g. format-patch:

$ git format-patch master..feature

And other commands.

Triple points syntax is on the other hand used to select commits that are in either of the branches, but not at both at the same time. An example:

$ git log master...feature

This will show you all commits that are either in master or in feature, but not in both. (If you intend to use this command, be sure to study the –left-right option, that will add indicators to each commit from which branch they are from).

Now what I find most confusing about git is a double and triple points syntax in diff command. The git authors say that diff command doesn’t operate of commit interval but on two commits (end-points) directly, therefore the above semantics doesn’t apply. They (mis)used the syntax for something else in this command. Oh, well. Let’s see.

First, the standard usage of diff command is following:

$ git diff master feature

That will display differences directly between these two commits. If you added something in feature, it will be seen as an added line in the diff/patch view. If you added something in master (since the time the feature branched off), it will be seen as a removed line in the diff/patch view. Not really handy if you want to see just the changes you made.

The double points syntax is equivalent to the no points syntax:

$ git diff master..feature

So it does not look for their common ancestor and compares it to feature, as you might expect, no. That’s what the triple points syntax is for!

$ git diff master...feature

This will finally show you the changes you have done on feature since you branched off from master. It is equivalent to:

$ git diff $(git-merge-base master feature) feature

As the bottom line, most commands use a standardized meaning of double and triple points syntax (see man gitrevisions). However the diff command uses a different syntax – triple points equals to double points in gitrevisions and double points has no special meaning. Confusing? I believe so. But the syntax is too important so we have to remember it anyway.

Cheers.

Flattr this

Git Tip of the Day – track remote branches easily

Because I always forget it and then can’t remember, I have to write this down.

I have some local and remote branches in my project:

$ git branch -avv
* master                         74b7a34 [origin/master] Release 0.6.0
  release-0.6                    69adc4e [origin/release-0.6] Release 0.6.0
  remotes/origin/HEAD            -> origin/master
  remotes/origin/aqc             b4ff4ba update to Fedora 14, remove crontab handling
  remotes/origin/clumens         ce5b3f1 Rollback previous README change.  The --url hook argument still exists.
  remotes/origin/copylib         1888da9 copy config files and autoqa library to the client
  remotes/origin/hongqing        8fc7a53 run analog in install.py

Now if I want to check out a remote branch called aqc, have it available in a local branch and set it as tracking, there is a neat short command to do all of this at once (instead of combining different options and whatnot):

$ git checkout aqc
Branch aqc set up to track remote branch aqc from origin.
Switched to a new branch 'aqc'
$ git branch -vv
* aqc         b4ff4ba [origin/aqc] update to Fedora 14, remove crontab handling
  master      74b7a34 [origin/master] Release 0.6.0
  release-0.6 69adc4e [origin/release-0.6] Release 0.6.0

Oh yes.

Flattr this