There is no built in way to do this.
As nologin effectively noted in a comment, if you achieve the desired set of commits, you have a new history, incompatible with the original history. If that's OK, there is a process—not built in, but not extremely difficult—by which you can achieve the desired set of commits. First, though, be sure about what you want.
Edit: the rest of this applies only to the question as originally phrased. The caveat below does not apply to the updated question, which now says that the commits are in fact linear. See Luciano's answer for a nice way to use git rebase -i
with a few simple tools to achieve the desired result.
ou describe commits as linear, and they might actually be linear, but they might not. They will be linear in some areas. But commits form a Directed Acyclic Graph or DAG. This graph is the history in the repository. In those parts where it is linear, it's pretty simple:
... <-F <-G <-H <-- master
Here, the branch name master
identifies, or points to, commit H
. More precisely, the name master
stores the hash ID of commit H
. Commit H
, meanwhile, stores the hash ID of H
's parent commit G
, which stores the hash ID of its parent F
, and so on. By starting at the end and working backwards, git log
shows you these commits, and that is the history.
Some commits, however, are merge commits. Such a commit has two (or more, but usually just two) parents. We can draw them this way:
I--J
/ \
...--H M <-- dev
\ /
K--L
Here the branch name dev
points to commit M
, but M
points back to both J
and L
. J
points back to I
; L
points back to K
; and I
and K
both point back to the commit from which the two sub-branches within the branch formed, namely commit H
(to which the name master
presumably points: commits H
and earlier are on both master
and dev
).
If commits I
, L
, and M
are all made by author BBBB, but J
and K
are by author AAAA, what do you intend to do here? If you keep M
(by BBBB), and keep J
because it's by a different author AAAA, you must also keep L
even though it's by BBBB. However, if all of I-J
and K-L
and M
are by AAAA
, you might choose to collapse them all into a single commit whose parent is H
:
...--H--M' <-- dev
So it's your job to figure out which commits you want to keep, and what you want to do about merge commits. You must keep merge commits if you need to keep the structure (the fork-and-merge at H
and M
). If you want to eliminate the branch-and-merge structure, you must discard merge commits, but then you must figure out what to do with oddball commits like I
and L
if they're by some other author.
Whatever you decide, when you're finally done, the way to achieve the result you want is:
Start with a list of all commits (by hash ID) that you wish to retain and/or all commits that you wish to discard. (Either suffices, since we'll assume that you're going to hold the universe of All Commits steady while you do this—i.e., not add new commits to the repository while you're computing these lists and making changes to the repository.)
Then run git filter-branch
. Choose at least the --commit-filter
. You may want additional filters, depending on what other history-data you're intent on discarding here. (For instance, each commit has a log message: do you want to combine all the log messages, or throw away the ones from commits whose snapshot you're throwing away? That is what you're doing: you're producing a fictional history. You can make up as much of it as you like, keeping only whatever you like from the original history, discarding the rest. What you keep and what you discard is up to you. Your new repository is incompatible with the old repositories: changing even a single bit anywhere in history renders the remaining history invalid and incompatible. So you might as well go as far as you like: it's really all-or-nothing!)
In your commit filter—read the git filter-branch
documentation for details—use skip_commit
to skip the commits you don't want and git commit-tree "$@"
to make the commits that you wish to keep. To decide, just see if $GIT_COMMIT
is in the keep or discard list.
The filter-branch command will take care of enumerating each commit, one at a time, in the correct order so that you can emit or exclude the commit from the history you're creating as you go. After it's invoked your commit filter on each such commit, it will write the hash ID of the last copied commit into the hash name. The original history is now effectively gone (but still findable via the refs/original/refs/head/branch
name; this name won't be in any new clones, and you can discard it when you're ready; again, see the documentation).