зеркало из https://github.com/microsoft/git.git
Merge branch 'master' into ph/strbuf
* master: (94 commits) Fixed update-hook example allow-users format. Documentation/git-svn: updated design philosophy notes t/t4014: test "am -3" with mode-only change. git-commit.sh: Shell script cleanup preserve executable bits in zip archives Fix lapsus in builtin-apply.c git-push: documentation and tests for pushing only branches git-svnimport: Use separate arguments in the pipe for git-rev-parse contrib/fast-import: add perl version of simple example contrib/fast-import: add simple shell example rev-list --bisect: Bisection "distance" clean up. rev-list --bisect: Move some bisection code into best_bisection. rev-list --bisect: Move finding bisection into do_find_bisection. Document ls-files --with-tree=<tree-ish> git-commit: partial commit of paths only removed from the index git-commit: Allow partial commit of file removal. send-email: make message-id generation a bit more robust git-apply: fix whitespace stripping git-gui: Disable native platform text selection in "lists" apply --index-info: fall back to current index for mode changes ...
This commit is contained in:
Коммит
39bd2eb56a
|
@ -123,7 +123,7 @@ cmd-list.made: cmd-list.perl $(MAN1_TXT)
|
|||
perl ./cmd-list.perl
|
||||
date >$@
|
||||
|
||||
git.7 git.html: git.txt core-intro.txt
|
||||
git.7 git.html: git.txt
|
||||
|
||||
clean:
|
||||
$(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 *.texi *.texi+ howto-index.txt howto/*.html doc.dep
|
||||
|
|
|
@ -630,9 +630,17 @@ pack.deltaCacheSize::
|
|||
A value of 0 means no limit. Defaults to 0.
|
||||
|
||||
pack.deltaCacheLimit::
|
||||
The maxium size of a delta, that is cached in
|
||||
The maximum size of a delta, that is cached in
|
||||
gitlink:git-pack-objects[1]. Defaults to 1000.
|
||||
|
||||
pack.threads::
|
||||
Specifies the number of threads to spawn when searching for best
|
||||
delta matches. This requires that gitlink:git-pack-objects[1]
|
||||
be compiled with pthreads otherwise this option is ignored with a
|
||||
warning. This is meant to reduce packing time on multiprocessor
|
||||
machines. The required amount of memory for the delta search window
|
||||
is however multiplied by the number of threads.
|
||||
|
||||
pull.octopus::
|
||||
The default merge strategy to use when pulling multiple branches
|
||||
at once.
|
||||
|
|
|
@ -1,592 +0,0 @@
|
|||
////////////////////////////////////////////////////////////////
|
||||
|
||||
GIT - the stupid content tracker
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
"git" can mean anything, depending on your mood.
|
||||
|
||||
- random three-letter combination that is pronounceable, and not
|
||||
actually used by any common UNIX command. The fact that it is a
|
||||
mispronunciation of "get" may or may not be relevant.
|
||||
- stupid. contemptible and despicable. simple. Take your pick from the
|
||||
dictionary of slang.
|
||||
- "global information tracker": you're in a good mood, and it actually
|
||||
works for you. Angels sing, and a light suddenly fills the room.
|
||||
- "goddamn idiotic truckload of sh*t": when it breaks
|
||||
|
||||
This is a (not so) stupid but extremely fast directory content manager.
|
||||
It doesn't do a whole lot at its core, but what it 'does' do is track
|
||||
directory contents efficiently.
|
||||
|
||||
There are two object abstractions: the "object database", and the
|
||||
"current directory cache" aka "index".
|
||||
|
||||
The Object Database
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
The object database is literally just a content-addressable collection
|
||||
of objects. All objects are named by their content, which is
|
||||
approximated by the SHA1 hash of the object itself. Objects may refer
|
||||
to other objects (by referencing their SHA1 hash), and so you can
|
||||
build up a hierarchy of objects.
|
||||
|
||||
All objects have a statically determined "type" aka "tag", which is
|
||||
determined at object creation time, and which identifies the format of
|
||||
the object (i.e. how it is used, and how it can refer to other
|
||||
objects). There are currently four different object types: "blob",
|
||||
"tree", "commit" and "tag".
|
||||
|
||||
A "blob" object cannot refer to any other object, and is, like the type
|
||||
implies, a pure storage object containing some user data. It is used to
|
||||
actually store the file data, i.e. a blob object is associated with some
|
||||
particular version of some file.
|
||||
|
||||
A "tree" object is an object that ties one or more "blob" objects into a
|
||||
directory structure. In addition, a tree object can refer to other tree
|
||||
objects, thus creating a directory hierarchy.
|
||||
|
||||
A "commit" object ties such directory hierarchies together into
|
||||
a DAG of revisions - each "commit" is associated with exactly one tree
|
||||
(the directory hierarchy at the time of the commit). In addition, a
|
||||
"commit" refers to one or more "parent" commit objects that describe the
|
||||
history of how we arrived at that directory hierarchy.
|
||||
|
||||
As a special case, a commit object with no parents is called the "root"
|
||||
object, and is the point of an initial project commit. Each project
|
||||
must have at least one root, and while you can tie several different
|
||||
root objects together into one project by creating a commit object which
|
||||
has two or more separate roots as its ultimate parents, that's probably
|
||||
just going to confuse people. So aim for the notion of "one root object
|
||||
per project", even if git itself does not enforce that.
|
||||
|
||||
A "tag" object symbolically identifies and can be used to sign other
|
||||
objects. It contains the identifier and type of another object, a
|
||||
symbolic name (of course!) and, optionally, a signature.
|
||||
|
||||
Regardless of object type, all objects share the following
|
||||
characteristics: they are all deflated with zlib, and have a header
|
||||
that not only specifies their type, but also provides size information
|
||||
about the data in the object. It's worth noting that the SHA1 hash
|
||||
that is used to name the object is the hash of the original data
|
||||
plus this header, so `sha1sum` 'file' does not match the object name
|
||||
for 'file'.
|
||||
(Historical note: in the dawn of the age of git the hash
|
||||
was the sha1 of the 'compressed' object.)
|
||||
|
||||
As a result, the general consistency of an object can always be tested
|
||||
independently of the contents or the type of the object: all objects can
|
||||
be validated by verifying that (a) their hashes match the content of the
|
||||
file and (b) the object successfully inflates to a stream of bytes that
|
||||
forms a sequence of <ascii type without space> + <space> + <ascii decimal
|
||||
size> + <byte\0> + <binary object data>.
|
||||
|
||||
The structured objects can further have their structure and
|
||||
connectivity to other objects verified. This is generally done with
|
||||
the `git-fsck` program, which generates a full dependency graph
|
||||
of all objects, and verifies their internal consistency (in addition
|
||||
to just verifying their superficial consistency through the hash).
|
||||
|
||||
The object types in some more detail:
|
||||
|
||||
Blob Object
|
||||
~~~~~~~~~~~
|
||||
A "blob" object is nothing but a binary blob of data, and doesn't
|
||||
refer to anything else. There is no signature or any other
|
||||
verification of the data, so while the object is consistent (it 'is'
|
||||
indexed by its sha1 hash, so the data itself is certainly correct), it
|
||||
has absolutely no other attributes. No name associations, no
|
||||
permissions. It is purely a blob of data (i.e. normally "file
|
||||
contents").
|
||||
|
||||
In particular, since the blob is entirely defined by its data, if two
|
||||
files in a directory tree (or in multiple different versions of the
|
||||
repository) have the same contents, they will share the same blob
|
||||
object. The object is totally independent of its location in the
|
||||
directory tree, and renaming a file does not change the object that
|
||||
file is associated with in any way.
|
||||
|
||||
A blob is typically created when gitlink:git-update-index[1]
|
||||
(or gitlink:git-add[1]) is run, and its data can be accessed by
|
||||
gitlink:git-cat-file[1].
|
||||
|
||||
Tree Object
|
||||
~~~~~~~~~~~
|
||||
The next hierarchical object type is the "tree" object. A tree object
|
||||
is a list of mode/name/blob data, sorted by name. Alternatively, the
|
||||
mode data may specify a directory mode, in which case instead of
|
||||
naming a blob, that name is associated with another TREE object.
|
||||
|
||||
Like the "blob" object, a tree object is uniquely determined by the
|
||||
set contents, and so two separate but identical trees will always
|
||||
share the exact same object. This is true at all levels, i.e. it's
|
||||
true for a "leaf" tree (which does not refer to any other trees, only
|
||||
blobs) as well as for a whole subdirectory.
|
||||
|
||||
For that reason a "tree" object is just a pure data abstraction: it
|
||||
has no history, no signatures, no verification of validity, except
|
||||
that since the contents are again protected by the hash itself, we can
|
||||
trust that the tree is immutable and its contents never change.
|
||||
|
||||
So you can trust the contents of a tree to be valid, the same way you
|
||||
can trust the contents of a blob, but you don't know where those
|
||||
contents 'came' from.
|
||||
|
||||
Side note on trees: since a "tree" object is a sorted list of
|
||||
"filename+content", you can create a diff between two trees without
|
||||
actually having to unpack two trees. Just ignore all common parts,
|
||||
and your diff will look right. In other words, you can effectively
|
||||
(and efficiently) tell the difference between any two random trees by
|
||||
O(n) where "n" is the size of the difference, rather than the size of
|
||||
the tree.
|
||||
|
||||
Side note 2 on trees: since the name of a "blob" depends entirely and
|
||||
exclusively on its contents (i.e. there are no names or permissions
|
||||
involved), you can see trivial renames or permission changes by
|
||||
noticing that the blob stayed the same. However, renames with data
|
||||
changes need a smarter "diff" implementation.
|
||||
|
||||
A tree is created with gitlink:git-write-tree[1] and
|
||||
its data can be accessed by gitlink:git-ls-tree[1].
|
||||
Two trees can be compared with gitlink:git-diff-tree[1].
|
||||
|
||||
Commit Object
|
||||
~~~~~~~~~~~~~
|
||||
The "commit" object is an object that introduces the notion of
|
||||
history into the picture. In contrast to the other objects, it
|
||||
doesn't just describe the physical state of a tree, it describes how
|
||||
we got there, and why.
|
||||
|
||||
A "commit" is defined by the tree-object that it results in, the
|
||||
parent commits (zero, one or more) that led up to that point, and a
|
||||
comment on what happened. Again, a commit is not trusted per se:
|
||||
the contents are well-defined and "safe" due to the cryptographically
|
||||
strong signatures at all levels, but there is no reason to believe
|
||||
that the tree is "good" or that the merge information makes sense.
|
||||
The parents do not have to actually have any relationship with the
|
||||
result, for example.
|
||||
|
||||
Note on commits: unlike real SCM's, commits do not contain
|
||||
rename information or file mode change information. All of that is
|
||||
implicit in the trees involved (the result tree, and the result trees
|
||||
of the parents), and describing that makes no sense in this idiotic
|
||||
file manager.
|
||||
|
||||
A commit is created with gitlink:git-commit-tree[1] and
|
||||
its data can be accessed by gitlink:git-cat-file[1].
|
||||
|
||||
Trust
|
||||
~~~~~
|
||||
An aside on the notion of "trust". Trust is really outside the scope
|
||||
of "git", but it's worth noting a few things. First off, since
|
||||
everything is hashed with SHA1, you 'can' trust that an object is
|
||||
intact and has not been messed with by external sources. So the name
|
||||
of an object uniquely identifies a known state - just not a state that
|
||||
you may want to trust.
|
||||
|
||||
Furthermore, since the SHA1 signature of a commit refers to the
|
||||
SHA1 signatures of the tree it is associated with and the signatures
|
||||
of the parent, a single named commit specifies uniquely a whole set
|
||||
of history, with full contents. You can't later fake any step of the
|
||||
way once you have the name of a commit.
|
||||
|
||||
So to introduce some real trust in the system, the only thing you need
|
||||
to do is to digitally sign just 'one' special note, which includes the
|
||||
name of a top-level commit. Your digital signature shows others
|
||||
that you trust that commit, and the immutability of the history of
|
||||
commits tells others that they can trust the whole history.
|
||||
|
||||
In other words, you can easily validate a whole archive by just
|
||||
sending out a single email that tells the people the name (SHA1 hash)
|
||||
of the top commit, and digitally sign that email using something
|
||||
like GPG/PGP.
|
||||
|
||||
To assist in this, git also provides the tag object...
|
||||
|
||||
Tag Object
|
||||
~~~~~~~~~~
|
||||
Git provides the "tag" object to simplify creating, managing and
|
||||
exchanging symbolic and signed tokens. The "tag" object at its
|
||||
simplest simply symbolically identifies another object by containing
|
||||
the sha1, type and symbolic name.
|
||||
|
||||
However it can optionally contain additional signature information
|
||||
(which git doesn't care about as long as there's less than 8k of
|
||||
it). This can then be verified externally to git.
|
||||
|
||||
Note that despite the tag features, "git" itself only handles content
|
||||
integrity; the trust framework (and signature provision and
|
||||
verification) has to come from outside.
|
||||
|
||||
A tag is created with gitlink:git-mktag[1],
|
||||
its data can be accessed by gitlink:git-cat-file[1],
|
||||
and the signature can be verified by
|
||||
gitlink:git-verify-tag[1].
|
||||
|
||||
|
||||
The "index" aka "Current Directory Cache"
|
||||
-----------------------------------------
|
||||
The index is a simple binary file, which contains an efficient
|
||||
representation of a virtual directory content at some random time. It
|
||||
does so by a simple array that associates a set of names, dates,
|
||||
permissions and content (aka "blob") objects together. The cache is
|
||||
always kept ordered by name, and names are unique (with a few very
|
||||
specific rules) at any point in time, but the cache has no long-term
|
||||
meaning, and can be partially updated at any time.
|
||||
|
||||
In particular, the index certainly does not need to be consistent with
|
||||
the current directory contents (in fact, most operations will depend on
|
||||
different ways to make the index 'not' be consistent with the directory
|
||||
hierarchy), but it has three very important attributes:
|
||||
|
||||
'(a) it can re-generate the full state it caches (not just the
|
||||
directory structure: it contains pointers to the "blob" objects so
|
||||
that it can regenerate the data too)'
|
||||
|
||||
As a special case, there is a clear and unambiguous one-way mapping
|
||||
from a current directory cache to a "tree object", which can be
|
||||
efficiently created from just the current directory cache without
|
||||
actually looking at any other data. So a directory cache at any one
|
||||
time uniquely specifies one and only one "tree" object (but has
|
||||
additional data to make it easy to match up that tree object with what
|
||||
has happened in the directory)
|
||||
|
||||
'(b) it has efficient methods for finding inconsistencies between that
|
||||
cached state ("tree object waiting to be instantiated") and the
|
||||
current state.'
|
||||
|
||||
'(c) it can additionally efficiently represent information about merge
|
||||
conflicts between different tree objects, allowing each pathname to be
|
||||
associated with sufficient information about the trees involved that
|
||||
you can create a three-way merge between them.'
|
||||
|
||||
Those are the three ONLY things that the directory cache does. It's a
|
||||
cache, and the normal operation is to re-generate it completely from a
|
||||
known tree object, or update/compare it with a live tree that is being
|
||||
developed. If you blow the directory cache away entirely, you generally
|
||||
haven't lost any information as long as you have the name of the tree
|
||||
that it described.
|
||||
|
||||
At the same time, the index is at the same time also the
|
||||
staging area for creating new trees, and creating a new tree always
|
||||
involves a controlled modification of the index file. In particular,
|
||||
the index file can have the representation of an intermediate tree that
|
||||
has not yet been instantiated. So the index can be thought of as a
|
||||
write-back cache, which can contain dirty information that has not yet
|
||||
been written back to the backing store.
|
||||
|
||||
|
||||
|
||||
The Workflow
|
||||
------------
|
||||
Generally, all "git" operations work on the index file. Some operations
|
||||
work *purely* on the index file (showing the current state of the
|
||||
index), but most operations move data to and from the index file. Either
|
||||
from the database or from the working directory. Thus there are four
|
||||
main combinations:
|
||||
|
||||
1) working directory -> index
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You update the index with information from the working directory with
|
||||
the gitlink:git-update-index[1] command. You
|
||||
generally update the index information by just specifying the filename
|
||||
you want to update, like so:
|
||||
|
||||
git-update-index filename
|
||||
|
||||
but to avoid common mistakes with filename globbing etc, the command
|
||||
will not normally add totally new entries or remove old entries,
|
||||
i.e. it will normally just update existing cache entries.
|
||||
|
||||
To tell git that yes, you really do realize that certain files no
|
||||
longer exist, or that new files should be added, you
|
||||
should use the `--remove` and `--add` flags respectively.
|
||||
|
||||
NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
|
||||
necessarily be removed: if the files still exist in your directory
|
||||
structure, the index will be updated with their new status, not
|
||||
removed. The only thing `--remove` means is that update-cache will be
|
||||
considering a removed file to be a valid thing, and if the file really
|
||||
does not exist any more, it will update the index accordingly.
|
||||
|
||||
As a special case, you can also do `git-update-index --refresh`, which
|
||||
will refresh the "stat" information of each index to match the current
|
||||
stat information. It will 'not' update the object status itself, and
|
||||
it will only update the fields that are used to quickly test whether
|
||||
an object still matches its old backing store object.
|
||||
|
||||
2) index -> object database
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You write your current index file to a "tree" object with the program
|
||||
|
||||
git-write-tree
|
||||
|
||||
that doesn't come with any options - it will just write out the
|
||||
current index into the set of tree objects that describe that state,
|
||||
and it will return the name of the resulting top-level tree. You can
|
||||
use that tree to re-generate the index at any time by going in the
|
||||
other direction:
|
||||
|
||||
3) object database -> index
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You read a "tree" file from the object database, and use that to
|
||||
populate (and overwrite - don't do this if your index contains any
|
||||
unsaved state that you might want to restore later!) your current
|
||||
index. Normal operation is just
|
||||
|
||||
git-read-tree <sha1 of tree>
|
||||
|
||||
and your index file will now be equivalent to the tree that you saved
|
||||
earlier. However, that is only your 'index' file: your working
|
||||
directory contents have not been modified.
|
||||
|
||||
4) index -> working directory
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You update your working directory from the index by "checking out"
|
||||
files. This is not a very common operation, since normally you'd just
|
||||
keep your files updated, and rather than write to your working
|
||||
directory, you'd tell the index files about the changes in your
|
||||
working directory (i.e. `git-update-index`).
|
||||
|
||||
However, if you decide to jump to a new version, or check out somebody
|
||||
else's version, or just restore a previous tree, you'd populate your
|
||||
index file with read-tree, and then you need to check out the result
|
||||
with
|
||||
|
||||
git-checkout-index filename
|
||||
|
||||
or, if you want to check out all of the index, use `-a`.
|
||||
|
||||
NOTE! git-checkout-index normally refuses to overwrite old files, so
|
||||
if you have an old version of the tree already checked out, you will
|
||||
need to use the "-f" flag ('before' the "-a" flag or the filename) to
|
||||
'force' the checkout.
|
||||
|
||||
|
||||
Finally, there are a few odds and ends which are not purely moving
|
||||
from one representation to the other:
|
||||
|
||||
5) Tying it all together
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
To commit a tree you have instantiated with "git-write-tree", you'd
|
||||
create a "commit" object that refers to that tree and the history
|
||||
behind it - most notably the "parent" commits that preceded it in
|
||||
history.
|
||||
|
||||
Normally a "commit" has one parent: the previous state of the tree
|
||||
before a certain change was made. However, sometimes it can have two
|
||||
or more parent commits, in which case we call it a "merge", due to the
|
||||
fact that such a commit brings together ("merges") two or more
|
||||
previous states represented by other commits.
|
||||
|
||||
In other words, while a "tree" represents a particular directory state
|
||||
of a working directory, a "commit" represents that state in "time",
|
||||
and explains how we got there.
|
||||
|
||||
You create a commit object by giving it the tree that describes the
|
||||
state at the time of the commit, and a list of parents:
|
||||
|
||||
git-commit-tree <tree> -p <parent> [-p <parent2> ..]
|
||||
|
||||
and then giving the reason for the commit on stdin (either through
|
||||
redirection from a pipe or file, or by just typing it at the tty).
|
||||
|
||||
git-commit-tree will return the name of the object that represents
|
||||
that commit, and you should save it away for later use. Normally,
|
||||
you'd commit a new `HEAD` state, and while git doesn't care where you
|
||||
save the note about that state, in practice we tend to just write the
|
||||
result to the file pointed at by `.git/HEAD`, so that we can always see
|
||||
what the last committed state was.
|
||||
|
||||
Here is an ASCII art by Jon Loeliger that illustrates how
|
||||
various pieces fit together.
|
||||
|
||||
------------
|
||||
|
||||
commit-tree
|
||||
commit obj
|
||||
+----+
|
||||
| |
|
||||
| |
|
||||
V V
|
||||
+-----------+
|
||||
| Object DB |
|
||||
| Backing |
|
||||
| Store |
|
||||
+-----------+
|
||||
^
|
||||
write-tree | |
|
||||
tree obj | |
|
||||
| | read-tree
|
||||
| | tree obj
|
||||
V
|
||||
+-----------+
|
||||
| Index |
|
||||
| "cache" |
|
||||
+-----------+
|
||||
update-index ^
|
||||
blob obj | |
|
||||
| |
|
||||
checkout-index -u | | checkout-index
|
||||
stat | | blob obj
|
||||
V
|
||||
+-----------+
|
||||
| Working |
|
||||
| Directory |
|
||||
+-----------+
|
||||
|
||||
------------
|
||||
|
||||
|
||||
6) Examining the data
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can examine the data represented in the object database and the
|
||||
index with various helper tools. For every object, you can use
|
||||
gitlink:git-cat-file[1] to examine details about the
|
||||
object:
|
||||
|
||||
git-cat-file -t <objectname>
|
||||
|
||||
shows the type of the object, and once you have the type (which is
|
||||
usually implicit in where you find the object), you can use
|
||||
|
||||
git-cat-file blob|tree|commit|tag <objectname>
|
||||
|
||||
to show its contents. NOTE! Trees have binary content, and as a result
|
||||
there is a special helper for showing that content, called
|
||||
`git-ls-tree`, which turns the binary content into a more easily
|
||||
readable form.
|
||||
|
||||
It's especially instructive to look at "commit" objects, since those
|
||||
tend to be small and fairly self-explanatory. In particular, if you
|
||||
follow the convention of having the top commit name in `.git/HEAD`,
|
||||
you can do
|
||||
|
||||
git-cat-file commit HEAD
|
||||
|
||||
to see what the top commit was.
|
||||
|
||||
7) Merging multiple trees
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Git helps you do a three-way merge, which you can expand to n-way by
|
||||
repeating the merge procedure arbitrary times until you finally
|
||||
"commit" the state. The normal situation is that you'd only do one
|
||||
three-way merge (two parents), and commit it, but if you like to, you
|
||||
can do multiple parents in one go.
|
||||
|
||||
To do a three-way merge, you need the two sets of "commit" objects
|
||||
that you want to merge, use those to find the closest common parent (a
|
||||
third "commit" object), and then use those commit objects to find the
|
||||
state of the directory ("tree" object) at these points.
|
||||
|
||||
To get the "base" for the merge, you first look up the common parent
|
||||
of two commits with
|
||||
|
||||
git-merge-base <commit1> <commit2>
|
||||
|
||||
which will return you the commit they are both based on. You should
|
||||
now look up the "tree" objects of those commits, which you can easily
|
||||
do with (for example)
|
||||
|
||||
git-cat-file commit <commitname> | head -1
|
||||
|
||||
since the tree object information is always the first line in a commit
|
||||
object.
|
||||
|
||||
Once you know the three trees you are going to merge (the one
|
||||
"original" tree, aka the common case, and the two "result" trees, aka
|
||||
the branches you want to merge), you do a "merge" read into the
|
||||
index. This will complain if it has to throw away your old index contents, so you should
|
||||
make sure that you've committed those - in fact you would normally
|
||||
always do a merge against your last commit (which should thus match
|
||||
what you have in your current index anyway).
|
||||
|
||||
To do the merge, do
|
||||
|
||||
git-read-tree -m -u <origtree> <yourtree> <targettree>
|
||||
|
||||
which will do all trivial merge operations for you directly in the
|
||||
index file, and you can just write the result out with
|
||||
`git-write-tree`.
|
||||
|
||||
Historical note. We did not have `-u` facility when this
|
||||
section was first written, so we used to warn that
|
||||
the merge is done in the index file, not in your
|
||||
working tree, and your working tree will not match your
|
||||
index after this step.
|
||||
This is no longer true. The above command, thanks to `-u`
|
||||
option, updates your working tree with the merge results for
|
||||
paths that have been trivially merged.
|
||||
|
||||
|
||||
8) Merging multiple trees, continued
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sadly, many merges aren't trivial. If there are files that have
|
||||
been added, moved or removed, or if both branches have modified the
|
||||
same file, you will be left with an index tree that contains "merge
|
||||
entries" in it. Such an index tree can 'NOT' be written out to a tree
|
||||
object, and you will have to resolve any such merge clashes using
|
||||
other tools before you can write out the result.
|
||||
|
||||
You can examine such index state with `git-ls-files --unmerged`
|
||||
command. An example:
|
||||
|
||||
------------------------------------------------
|
||||
$ git-read-tree -m $orig HEAD $target
|
||||
$ git-ls-files --unmerged
|
||||
100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello.c
|
||||
100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello.c
|
||||
100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello.c
|
||||
------------------------------------------------
|
||||
|
||||
Each line of the `git-ls-files --unmerged` output begins with
|
||||
the blob mode bits, blob SHA1, 'stage number', and the
|
||||
filename. The 'stage number' is git's way to say which tree it
|
||||
came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
|
||||
tree, and stage3 `$target` tree.
|
||||
|
||||
Earlier we said that trivial merges are done inside
|
||||
`git-read-tree -m`. For example, if the file did not change
|
||||
from `$orig` to `HEAD` nor `$target`, or if the file changed
|
||||
from `$orig` to `HEAD` and `$orig` to `$target` the same way,
|
||||
obviously the final outcome is what is in `HEAD`. What the
|
||||
above example shows is that file `hello.c` was changed from
|
||||
`$orig` to `HEAD` and `$orig` to `$target` in a different way.
|
||||
You could resolve this by running your favorite 3-way merge
|
||||
program, e.g. `diff3` or `merge`, on the blob objects from
|
||||
these three stages yourself, like this:
|
||||
|
||||
------------------------------------------------
|
||||
$ git-cat-file blob 263414f... >hello.c~1
|
||||
$ git-cat-file blob 06fa6a2... >hello.c~2
|
||||
$ git-cat-file blob cc44c73... >hello.c~3
|
||||
$ merge hello.c~2 hello.c~1 hello.c~3
|
||||
------------------------------------------------
|
||||
|
||||
This would leave the merge result in `hello.c~2` file, along
|
||||
with conflict markers if there are conflicts. After verifying
|
||||
the merge result makes sense, you can tell git what the final
|
||||
merge result for this file is by:
|
||||
|
||||
mv -f hello.c~2 hello.c
|
||||
git-update-index hello.c
|
||||
|
||||
When a path is in unmerged state, running `git-update-index` for
|
||||
that path tells git to mark the path resolved.
|
||||
|
||||
The above is the description of a git merge at the lowest level,
|
||||
to help you understand what conceptually happens under the hood.
|
||||
In practice, nobody, not even git itself, uses three `git-cat-file`
|
||||
for this. There is `git-merge-index` program that extracts the
|
||||
stages to temporary files and calls a "merge" script on it:
|
||||
|
||||
git-merge-index git-merge-one-file hello.c
|
||||
|
||||
and that is what higher level `git merge -s resolve` is implemented
|
||||
with.
|
|
@ -4,34 +4,24 @@ A git core tutorial for developers
|
|||
Introduction
|
||||
------------
|
||||
|
||||
This is trying to be a short tutorial on setting up and using a git
|
||||
repository, mainly because being hands-on and using explicit examples is
|
||||
often the best way of explaining what is going on.
|
||||
This tutorial explains how to use the "core" git programs to set up and
|
||||
work with a git repository.
|
||||
|
||||
In normal life, most people wouldn't use the "core" git programs
|
||||
directly, but rather script around them to make them more palatable.
|
||||
Understanding the core git stuff may help some people get those scripts
|
||||
done, though, and it may also be instructive in helping people
|
||||
understand what it is that the higher-level helper scripts are actually
|
||||
doing.
|
||||
If you just need to use git as a revision control system you may prefer
|
||||
to start with link:tutorial.html[a tutorial introduction to git] or
|
||||
link:user-manual.html[the git user manual].
|
||||
|
||||
However, an understanding of these low-level tools can be helpful if
|
||||
you want to understand git's internals.
|
||||
|
||||
The core git is often called "plumbing", with the prettier user
|
||||
interfaces on top of it called "porcelain". You may not want to use the
|
||||
plumbing directly very often, but it can be good to know what the
|
||||
plumbing does for when the porcelain isn't flushing.
|
||||
|
||||
The material presented here often goes deep describing how things
|
||||
work internally. If you are mostly interested in using git as a
|
||||
SCM, you can skip them during your first pass.
|
||||
|
||||
[NOTE]
|
||||
And those "too deep" descriptions are often marked as Note.
|
||||
|
||||
[NOTE]
|
||||
If you are already familiar with another version control system,
|
||||
like CVS, you may want to take a look at
|
||||
link:everyday.html[Everyday GIT in 20 commands or so] first
|
||||
before reading this.
|
||||
Deeper technical details are often marked as Notes, which you can
|
||||
skip on your first reading.
|
||||
|
||||
|
||||
Creating a git repository
|
||||
|
@ -1686,5 +1676,3 @@ merge two at a time, documenting how you resolved the conflicts,
|
|||
and the reason why you preferred changes made in one side over
|
||||
the other. Otherwise it would make the project history harder
|
||||
to follow, not easier.
|
||||
|
||||
[ to be continued.. cvsimports ]
|
||||
|
|
|
@ -15,7 +15,8 @@ SYNOPSIS
|
|||
DESCRIPTION
|
||||
-----------
|
||||
Creates an archive of the specified format containing the tree
|
||||
structure for the named tree. If <prefix> is specified it is
|
||||
structure for the named tree, and writes it out to the standard
|
||||
output. If <prefix> is specified it is
|
||||
prepended to the filenames in the archive.
|
||||
|
||||
'git-archive' behaves differently when given a tree ID versus when
|
||||
|
@ -31,7 +32,7 @@ OPTIONS
|
|||
-------
|
||||
|
||||
--format=<fmt>::
|
||||
Format of the resulting archive: 'tar', 'zip'... The default
|
||||
Format of the resulting archive: 'tar' or 'zip'. The default
|
||||
is 'tar'.
|
||||
|
||||
--list, -l::
|
||||
|
|
|
@ -142,7 +142,7 @@ FILES
|
|||
If not set explicitly with '--file', there are three files where
|
||||
git-config will search for configuration options:
|
||||
|
||||
.git/config::
|
||||
$GIT_DIR/config::
|
||||
Repository specific configuration file. (The filename is
|
||||
of course relative to the repository root, not the working
|
||||
directory.)
|
||||
|
|
|
@ -220,11 +220,6 @@ git filter-branch --commit-filter '
|
|||
fi' HEAD
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
Note that the changes introduced by the commits, and not reverted by
|
||||
subsequent commits, will still be in the rewritten branch. If you want
|
||||
to throw out _changes_ together with the commits, you should use the
|
||||
interactive mode of gitlink:git-rebase[1].
|
||||
|
||||
The function 'skip_commits' is defined as follows:
|
||||
|
||||
--------------------------
|
||||
|
|
|
@ -15,7 +15,7 @@ SYNOPSIS
|
|||
[-x <pattern>|--exclude=<pattern>]
|
||||
[-X <file>|--exclude-from=<file>]
|
||||
[--exclude-per-directory=<file>]
|
||||
[--error-unmatch]
|
||||
[--error-unmatch] [--with-tree=<tree-ish>]
|
||||
[--full-name] [--abbrev] [--] [<file>]\*
|
||||
|
||||
DESCRIPTION
|
||||
|
@ -81,6 +81,13 @@ OPTIONS
|
|||
If any <file> does not appear in the index, treat this as an
|
||||
error (return 1).
|
||||
|
||||
--with-tree=<tree-ish>::
|
||||
When using --error-unmatch to expand the user supplied
|
||||
<file> (i.e. path pattern) arguments to paths, pretend
|
||||
that paths which were removed in the index since the
|
||||
named <tree-ish> are still present. Using this option
|
||||
with `-s` or `-u` options does not make any sense.
|
||||
|
||||
-t::
|
||||
Identify the file status with the following tags (followed by
|
||||
a space) at the start of each line:
|
||||
|
|
|
@ -169,6 +169,14 @@ base-name::
|
|||
length, this option typically shrinks the resulting
|
||||
packfile by 3-5 per-cent.
|
||||
|
||||
--threads=<n>::
|
||||
Specifies the number of threads to spawn when searching for best
|
||||
delta matches. This requires that pack-objects be compiled with
|
||||
pthreads otherwise this option is ignored with a warning.
|
||||
This is meant to reduce packing time on multiprocessor machines.
|
||||
The required amount of memory for the delta search window is
|
||||
however multiplied by the number of threads.
|
||||
|
||||
--index-version=<version>[,<offset>]::
|
||||
This is intended to be used by the test suite only. It allows
|
||||
to force the version for the generated pack index, and to force
|
||||
|
|
|
@ -48,7 +48,7 @@ even if it does not result in a fast forward update.
|
|||
Note: If no explicit refspec is found, (that is neither
|
||||
on the command line nor in any Push line of the
|
||||
corresponding remotes file---see below), then all the
|
||||
refs that exist both on the local side and on the remote
|
||||
heads that exist both on the local side and on the remote
|
||||
side are updated.
|
||||
+
|
||||
`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
|
||||
|
@ -61,7 +61,7 @@ the remote repository.
|
|||
|
||||
\--all::
|
||||
Instead of naming each ref to push, specifies that all
|
||||
refs be pushed.
|
||||
refs under `$GIT_DIR/refs/heads/` be pushed.
|
||||
|
||||
\--tags::
|
||||
All refs under `$GIT_DIR/refs/tags` are pushed, in
|
||||
|
|
|
@ -10,7 +10,7 @@ SYNOPSIS
|
|||
--------
|
||||
[verse]
|
||||
'git-remote'
|
||||
'git-remote' add [-t <branch>] [-m <branch>] [-f] <name> <url>
|
||||
'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url>
|
||||
'git-remote' show <name>
|
||||
'git-remote' prune <name>
|
||||
'git-remote' update [group]
|
||||
|
@ -45,6 +45,10 @@ multiple branches without grabbing all branches.
|
|||
With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
|
||||
up to point at remote's `<master>` branch instead of whatever
|
||||
branch the `HEAD` at the remote repository actually points at.
|
||||
+
|
||||
In mirror mode, enabled with `--mirror`, the refs will not be stored
|
||||
in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option
|
||||
only makes sense in bare repositories.
|
||||
|
||||
'show'::
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ OPTIONS
|
|||
|
||||
\--all::
|
||||
Instead of explicitly specifying which refs to update,
|
||||
update all refs that locally exist.
|
||||
update all heads that locally exist.
|
||||
|
||||
\--force::
|
||||
Usually, the command refuses to update a remote ref that
|
||||
|
@ -70,7 +70,7 @@ With '--all' flag, all refs that exist locally are transferred to
|
|||
the remote side. You cannot specify any '<ref>' if you use
|
||||
this flag.
|
||||
|
||||
Without '--all' and without any '<ref>', the refs that exist
|
||||
Without '--all' and without any '<ref>', the heads that exist
|
||||
both on the local side and on the remote side are updated.
|
||||
|
||||
When one or more '<ref>' are specified explicitly, it can be either a
|
||||
|
|
|
@ -478,11 +478,12 @@ previous commits in SVN.
|
|||
DESIGN PHILOSOPHY
|
||||
-----------------
|
||||
Merge tracking in Subversion is lacking and doing branched development
|
||||
with Subversion is cumbersome as a result. git-svn does not do
|
||||
automated merge/branch tracking by default and leaves it entirely up to
|
||||
the user on the git side. git-svn does however follow copy
|
||||
history of the directory that it is tracking, however (much like
|
||||
how 'svn log' works).
|
||||
with Subversion can be cumbersome as a result. While git-svn can track
|
||||
copy history (including branches and tags) for repositories adopting a
|
||||
standard layout, it cannot yet represent merge history that happened
|
||||
inside git back upstream to SVN users. Therefore it is advised that
|
||||
users keep history as linear as possible inside git to ease
|
||||
compatibility with SVN (see the CAVEATS section below).
|
||||
|
||||
CAVEATS
|
||||
-------
|
||||
|
|
|
@ -134,9 +134,9 @@ FURTHER DOCUMENTATION
|
|||
See the references above to get started using git. The following is
|
||||
probably more detail than necessary for a first-time user.
|
||||
|
||||
The <<Discussion,Discussion>> section below and the
|
||||
link:core-tutorial.html[Core tutorial] both provide introductions to the
|
||||
underlying git architecture.
|
||||
The link:user-manual.html#git-concepts[git concepts chapter of the
|
||||
user-manual] and the link:core-tutorial.html[Core tutorial] both provide
|
||||
introductions to the underlying git architecture.
|
||||
|
||||
See also the link:howto-index.html[howto] documents for some useful
|
||||
examples.
|
||||
|
@ -474,7 +474,56 @@ for further details.
|
|||
|
||||
Discussion[[Discussion]]
|
||||
------------------------
|
||||
include::core-intro.txt[]
|
||||
|
||||
More detail on the following is available from the
|
||||
link:user-manual.html#git-concepts[git concepts chapter of the
|
||||
user-manual] and the link:core-tutorial.html[Core tutorial].
|
||||
|
||||
A git project normally consists of a working directory with a ".git"
|
||||
subdirectory at the top level. The .git directory contains, among other
|
||||
things, a compressed object database representing the complete history
|
||||
of the project, an "index" file which links that history to the current
|
||||
contents of the working tree, and named pointers into that history such
|
||||
as tags and branch heads.
|
||||
|
||||
The object database contains objects of three main types: blobs, which
|
||||
hold file data; trees, which point to blobs and other trees to build up
|
||||
directory heirarchies; and commits, which each reference a single tree
|
||||
and some number of parent commits.
|
||||
|
||||
The commit, equivalent to what other systems call a "changeset" or
|
||||
"version", represents a step in the project's history, and each parent
|
||||
represents an immediately preceding step. Commits with more than one
|
||||
parent represent merges of independent lines of development.
|
||||
|
||||
All objects are named by the SHA1 hash of their contents, normally
|
||||
written as a string of 40 hex digits. Such names are globally unique.
|
||||
The entire history leading up to a commit can be vouched for by signing
|
||||
just that commit. A fourth object type, the tag, is provided for this
|
||||
purpose.
|
||||
|
||||
When first created, objects are stored in individual files, but for
|
||||
efficiency may later be compressed together into "pack files".
|
||||
|
||||
Named pointers called refs mark interesting points in history. A ref
|
||||
may contain the SHA1 name of an object or the name of another ref. Refs
|
||||
with names beginning `ref/head/` contain the SHA1 name of the most
|
||||
recent commit (or "head") of a branch under developement. SHA1 names of
|
||||
tags of interest are stored under `ref/tags/`. A special ref named
|
||||
`HEAD` contains the name of the currently checked-out branch.
|
||||
|
||||
The index file is initialized with a list of all paths and, for each
|
||||
path, a blob object and a set of attributes. The blob object represents
|
||||
the contents of the file as of the head of the current branch. The
|
||||
attributes (last modified time, size, etc.) are taken from the
|
||||
corresponding file in the working tree. Subsequent changes to the
|
||||
working tree can be found by comparing these attributes. The index may
|
||||
be updated with new content, and new commits may be created from the
|
||||
content stored in the index.
|
||||
|
||||
The index is also capable of storing multiple entries (called "stages")
|
||||
for a given pathname. These stages are used to hold the various
|
||||
unmerged version of a file when a merge is in progress.
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
|
|
@ -160,9 +160,9 @@ whom. The format of each file would look like this:
|
|||
|
||||
refs/heads/master junio
|
||||
refs/heads/cogito$ pasky
|
||||
refs/heads/bw/ linus
|
||||
refs/heads/tmp/ *
|
||||
refs/tags/v[0-9]* junio
|
||||
refs/heads/bw/.* linus
|
||||
refs/heads/tmp/.* .*
|
||||
refs/tags/v[0-9].* junio
|
||||
|
||||
With this, Linus can push or create "bw/penguin" or "bw/zebra"
|
||||
or "bw/panda" branches, Pasky can do only "cogito", and JC can
|
||||
|
|
|
@ -182,7 +182,7 @@ has that commit at all). Since the object name is computed as a hash over the
|
|||
contents of the commit, you are guaranteed that the commit can never change
|
||||
without its name also changing.
|
||||
|
||||
In fact, in <<git-internals>> we shall see that everything stored in git
|
||||
In fact, in <<git-concepts>> we shall see that everything stored in git
|
||||
history, including file data and directory contents, is stored in an object
|
||||
with a name that is a hash of its contents.
|
||||
|
||||
|
@ -2708,190 +2708,202 @@ See gitlink:git-config[1] for more details on the configuration
|
|||
options mentioned above.
|
||||
|
||||
|
||||
[[git-internals]]
|
||||
Git internals
|
||||
=============
|
||||
[[git-concepts]]
|
||||
Git concepts
|
||||
============
|
||||
|
||||
Git depends on two fundamental abstractions: the "object database", and
|
||||
the "current directory cache" aka "index".
|
||||
Git is built on a small number of simple but powerful ideas. While it
|
||||
is possible to get things done without understanding them, you will find
|
||||
git much more intuitive if you do.
|
||||
|
||||
We start with the most important, the <<def_object_database,object
|
||||
database>> and the <<def_index,index>>.
|
||||
|
||||
[[the-object-database]]
|
||||
The Object Database
|
||||
-------------------
|
||||
|
||||
The object database is literally just a content-addressable collection
|
||||
of objects. All objects are named by their content, which is
|
||||
approximated by the SHA1 hash of the object itself. Objects may refer
|
||||
to other objects (by referencing their SHA1 hash), and so you can
|
||||
build up a hierarchy of objects.
|
||||
|
||||
All objects have a statically determined "type" which is
|
||||
determined at object creation time, and which identifies the format of
|
||||
the object (i.e. how it is used, and how it can refer to other
|
||||
objects). There are currently four different object types: "blob",
|
||||
"tree", "commit", and "tag".
|
||||
We already saw in <<understanding-commits>> that all commits are stored
|
||||
under a 40-digit "object name". In fact, all the information needed to
|
||||
represent the history of a project is stored in objects with such names.
|
||||
In each case the name is calculated by taking the SHA1 hash of the
|
||||
contents of the object. The SHA1 hash is a cryptographic hash function.
|
||||
What that means to us is that it is impossible to find two different
|
||||
objects with the same name. This has a number of advantages; among
|
||||
others:
|
||||
|
||||
A <<def_blob_object,"blob" object>> cannot refer to any other object,
|
||||
and is, as the name implies, a pure storage object containing some
|
||||
user data. It is used to actually store the file data, i.e. a blob
|
||||
object is associated with some particular version of some file.
|
||||
- Git can quickly determine whether two objects are identical or not,
|
||||
just by comparing names.
|
||||
- Since object names are computed the same way in ever repository, the
|
||||
same content stored in two repositories will always be stored under
|
||||
the same name.
|
||||
- Git can detect errors when it reads an object, by checking that the
|
||||
object's name is still the SHA1 hash of its contents.
|
||||
|
||||
A <<def_tree_object,"tree" object>> is an object that ties one or more
|
||||
(See <<object-details>> for the details of the object formatting and
|
||||
SHA1 calculation.)
|
||||
|
||||
There are four different types of objects: "blob", "tree", "commit", and
|
||||
"tag".
|
||||
|
||||
- A <<def_blob_object,"blob" object>> is used to store file data.
|
||||
- A <<def_tree_object,"tree" object>> is an object that ties one or more
|
||||
"blob" objects into a directory structure. In addition, a tree object
|
||||
can refer to other tree objects, thus creating a directory hierarchy.
|
||||
|
||||
A <<def_commit_object,"commit" object>> ties such directory hierarchies
|
||||
- A <<def_commit_object,"commit" object>> ties such directory hierarchies
|
||||
together into a <<def_DAG,directed acyclic graph>> of revisions - each
|
||||
"commit" is associated with exactly one tree (the directory hierarchy at
|
||||
the time of the commit). In addition, a "commit" refers to one or more
|
||||
"parent" commit objects that describe the history of how we arrived at
|
||||
that directory hierarchy.
|
||||
|
||||
As a special case, a commit object with no parents is called the "root"
|
||||
commit, and is the point of an initial project commit. Each project
|
||||
must have at least one root, and while you can tie several different
|
||||
root objects together into one project by creating a commit object which
|
||||
has two or more separate roots as its ultimate parents, that's probably
|
||||
just going to confuse people. So aim for the notion of "one root object
|
||||
per project", even if git itself does not enforce that.
|
||||
|
||||
A <<def_tag_object,"tag" object>> symbolically identifies and can be
|
||||
used to sign other objects. It contains the identifier and type of
|
||||
commit contains the object name of exactly one tree designating the
|
||||
directory hierarchy at the time of the commit. In addition, a commit
|
||||
refers to "parent" commit objects that describe the history of how we
|
||||
arrived at that directory hierarchy.
|
||||
- A <<def_tag_object,"tag" object>> symbolically identifies and can be
|
||||
used to sign other objects. It contains the object name and type of
|
||||
another object, a symbolic name (of course!) and, optionally, a
|
||||
signature.
|
||||
|
||||
Regardless of object type, all objects share the following
|
||||
characteristics: they are all deflated with zlib, and have a header
|
||||
that not only specifies their type, but also provides size information
|
||||
about the data in the object. It's worth noting that the SHA1 hash
|
||||
that is used to name the object is the hash of the original data
|
||||
plus this header, so `sha1sum` 'file' does not match the object name
|
||||
for 'file'.
|
||||
(Historical note: in the dawn of the age of git the hash
|
||||
was the sha1 of the 'compressed' object.)
|
||||
|
||||
As a result, the general consistency of an object can always be tested
|
||||
independently of the contents or the type of the object: all objects can
|
||||
be validated by verifying that (a) their hashes match the content of the
|
||||
file and (b) the object successfully inflates to a stream of bytes that
|
||||
forms a sequence of <ascii type without space> {plus} <space> {plus} <ascii decimal
|
||||
size> {plus} <byte\0> {plus} <binary object data>.
|
||||
|
||||
The structured objects can further have their structure and
|
||||
connectivity to other objects verified. This is generally done with
|
||||
the `git-fsck` program, which generates a full dependency graph
|
||||
of all objects, and verifies their internal consistency (in addition
|
||||
to just verifying their superficial consistency through the hash).
|
||||
|
||||
The object types in some more detail:
|
||||
|
||||
[[blob-object]]
|
||||
Blob Object
|
||||
-----------
|
||||
|
||||
A "blob" object is nothing but a binary blob of data, and doesn't
|
||||
refer to anything else. There is no signature or any other
|
||||
verification of the data, so while the object is consistent (it 'is'
|
||||
indexed by its sha1 hash, so the data itself is certainly correct), it
|
||||
has absolutely no other attributes. No name associations, no
|
||||
permissions. It is purely a blob of data (i.e. normally "file
|
||||
contents").
|
||||
|
||||
In particular, since the blob is entirely defined by its data, if two
|
||||
files in a directory tree (or in multiple different versions of the
|
||||
repository) have the same contents, they will share the same blob
|
||||
object. The object is totally independent of its location in the
|
||||
directory tree, and renaming a file does not change the object that
|
||||
file is associated with in any way.
|
||||
|
||||
A blob is typically created when gitlink:git-update-index[1]
|
||||
is run, and its data can be accessed by gitlink:git-cat-file[1].
|
||||
|
||||
[[tree-object]]
|
||||
Tree Object
|
||||
-----------
|
||||
|
||||
The next hierarchical object type is the "tree" object. A tree object
|
||||
is a list of mode/name/blob data, sorted by name. Alternatively, the
|
||||
mode data may specify a directory mode, in which case instead of
|
||||
naming a blob, that name is associated with another TREE object.
|
||||
|
||||
Like the "blob" object, a tree object is uniquely determined by the
|
||||
set contents, and so two separate but identical trees will always
|
||||
share the exact same object. This is true at all levels, i.e. it's
|
||||
true for a "leaf" tree (which does not refer to any other trees, only
|
||||
blobs) as well as for a whole subdirectory.
|
||||
|
||||
For that reason a "tree" object is just a pure data abstraction: it
|
||||
has no history, no signatures, no verification of validity, except
|
||||
that since the contents are again protected by the hash itself, we can
|
||||
trust that the tree is immutable and its contents never change.
|
||||
|
||||
So you can trust the contents of a tree to be valid, the same way you
|
||||
can trust the contents of a blob, but you don't know where those
|
||||
contents 'came' from.
|
||||
|
||||
Side note on trees: since a "tree" object is a sorted list of
|
||||
"filename+content", you can create a diff between two trees without
|
||||
actually having to unpack two trees. Just ignore all common parts,
|
||||
and your diff will look right. In other words, you can effectively
|
||||
(and efficiently) tell the difference between any two random trees by
|
||||
O(n) where "n" is the size of the difference, rather than the size of
|
||||
the tree.
|
||||
|
||||
Side note 2 on trees: since the name of a "blob" depends entirely and
|
||||
exclusively on its contents (i.e. there are no names or permissions
|
||||
involved), you can see trivial renames or permission changes by
|
||||
noticing that the blob stayed the same. However, renames with data
|
||||
changes need a smarter "diff" implementation.
|
||||
|
||||
A tree is created with gitlink:git-write-tree[1] and
|
||||
its data can be accessed by gitlink:git-ls-tree[1].
|
||||
Two trees can be compared with gitlink:git-diff-tree[1].
|
||||
|
||||
[[commit-object]]
|
||||
Commit Object
|
||||
-------------
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The "commit" object is an object that introduces the notion of
|
||||
history into the picture. In contrast to the other objects, it
|
||||
doesn't just describe the physical state of a tree, it describes how
|
||||
we got there, and why.
|
||||
The "commit" object links a physical state of a tree with a description
|
||||
of how we got there and why. Use the --pretty=raw option to
|
||||
gitlink:git-show[1] or gitlink:git-log[1] to examine your favorite
|
||||
commit:
|
||||
|
||||
A "commit" is defined by the tree-object that it results in, the
|
||||
parent commits (zero, one or more) that led up to that point, and a
|
||||
comment on what happened. Again, a commit is not trusted per se:
|
||||
the contents are well-defined and "safe" due to the cryptographically
|
||||
strong signatures at all levels, but there is no reason to believe
|
||||
that the tree is "good" or that the merge information makes sense.
|
||||
The parents do not have to actually have any relationship with the
|
||||
result, for example.
|
||||
------------------------------------------------
|
||||
$ git show -s --pretty=raw 2be7fcb476
|
||||
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
|
||||
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
|
||||
parent 257a84d9d02e90447b149af58b271c19405edb6a
|
||||
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
|
||||
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700
|
||||
|
||||
Note on commits: unlike some SCM's, commits do not contain
|
||||
rename information or file mode change information. All of that is
|
||||
implicit in the trees involved (the result tree, and the result trees
|
||||
of the parents), and describing that makes no sense in this idiotic
|
||||
file manager.
|
||||
Fix misspelling of 'suppress' in docs
|
||||
|
||||
A commit is created with gitlink:git-commit-tree[1] and
|
||||
its data can be accessed by gitlink:git-cat-file[1].
|
||||
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
||||
------------------------------------------------
|
||||
|
||||
As you can see, a commit is defined by:
|
||||
|
||||
- a tree: The SHA1 name of a tree object (as defined below), representing
|
||||
the contents of a directory at a certain point in time.
|
||||
- parent(s): The SHA1 name of some number of commits which represent the
|
||||
immediately prevoius step(s) in the history of the project. The
|
||||
example above has one parent; merge commits may have more than
|
||||
one. A commit with no parents is called a "root" commit, and
|
||||
represents the initial revision of a project. Each project must have
|
||||
at least one root. A project can also have multiple roots, though
|
||||
that isn't common (or necessarily a good idea).
|
||||
- an author: The name of the person responsible for this change, together
|
||||
with its date.
|
||||
- a committer: The name of the person who actually created the commit,
|
||||
with the date it was done. This may be different from the author, for
|
||||
example, if the author was someone who wrote a patch and emailed it
|
||||
to the person who used it to create the commit.
|
||||
- a comment describing this commit.
|
||||
|
||||
Note that a commit does not itself contain any information about what
|
||||
actually changed; all changes are calculated by comparing the contents
|
||||
of the tree referred to by this commit with the trees associated with
|
||||
its parents. In particular, git does not attempt to record file renames
|
||||
explicitly, though it can identify cases where the existence of the same
|
||||
file data at changing paths suggests a rename. (See, for example, the
|
||||
-M option to gitlink:git-diff[1]).
|
||||
|
||||
A commit is usually created by gitlink:git-commit[1], which creates a
|
||||
commit whose parent is normally the current HEAD, and whose tree is
|
||||
taken from the content currently stored in the index.
|
||||
|
||||
[[tree-object]]
|
||||
Tree Object
|
||||
~~~~~~~~~~~
|
||||
|
||||
The ever-versatile gitlink:git-show[1] command can also be used to
|
||||
examine tree objects, but gitlink:git-ls-tree[1] will give you more
|
||||
details:
|
||||
|
||||
------------------------------------------------
|
||||
$ git ls-tree fb3a8bdd0ce
|
||||
100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c .gitignore
|
||||
100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d .mailmap
|
||||
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING
|
||||
040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745 Documentation
|
||||
100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200 GIT-VERSION-GEN
|
||||
100644 blob 289b046a443c0647624607d471289b2c7dcd470b INSTALL
|
||||
100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1 Makefile
|
||||
100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52 README
|
||||
...
|
||||
------------------------------------------------
|
||||
|
||||
As you can see, a tree object contains a list of entries, each with a
|
||||
mode, object type, SHA1 name, and name, sorted by name. It represents
|
||||
the contents of a single directory tree.
|
||||
|
||||
The object type may be a blob, representing the contents of a file, or
|
||||
another tree, representing the contents of a subdirectory. Since trees
|
||||
and blobs, like all other objects, are named by the SHA1 hash of their
|
||||
contents, two trees have the same SHA1 name if and only if their
|
||||
contents (including, recursively, the contents of all subdirectories)
|
||||
are identical. This allows git to quickly determine the differences
|
||||
between two related tree objects, since it can ignore any entries with
|
||||
identical object names.
|
||||
|
||||
(Note: in the presence of submodules, trees may also have commits as
|
||||
entries. See gitlink:git-submodule[1] and gitlink:gitmodules.txt[1]
|
||||
for partial documentation.)
|
||||
|
||||
Note that the files all have mode 644 or 755: git actually only pays
|
||||
attention to the executable bit.
|
||||
|
||||
[[blob-object]]
|
||||
Blob Object
|
||||
~~~~~~~~~~~
|
||||
|
||||
You can use gitlink:git-show[1] to examine the contents of a blob; take,
|
||||
for example, the blob in the entry for "COPYING" from the tree above:
|
||||
|
||||
------------------------------------------------
|
||||
$ git show 6ff87c4664
|
||||
|
||||
Note that the only valid version of the GPL as far as this project
|
||||
is concerned is _this_ particular version of the license (ie v2, not
|
||||
v2.2 or v3.x or whatever), unless explicitly otherwise stated.
|
||||
...
|
||||
------------------------------------------------
|
||||
|
||||
A "blob" object is nothing but a binary blob of data. It doesn't refer
|
||||
to anything else or have attributes of any kind.
|
||||
|
||||
Since the blob is entirely defined by its data, if two files in a
|
||||
directory tree (or in multiple different versions of the repository)
|
||||
have the same contents, they will share the same blob object. The object
|
||||
is totally independent of its location in the directory tree, and
|
||||
renaming a file does not change the object that file is associated with.
|
||||
|
||||
Note that any tree or blob object can be examined using
|
||||
gitlink:git-show[1] with the <revision>:<path> syntax. This can
|
||||
sometimes be useful for browsing the contents of a tree that is not
|
||||
currently checked out.
|
||||
|
||||
[[trust]]
|
||||
Trust
|
||||
-----
|
||||
~~~~~
|
||||
|
||||
An aside on the notion of "trust". Trust is really outside the scope
|
||||
of "git", but it's worth noting a few things. First off, since
|
||||
everything is hashed with SHA1, you 'can' trust that an object is
|
||||
intact and has not been messed with by external sources. So the name
|
||||
of an object uniquely identifies a known state - just not a state that
|
||||
you may want to trust.
|
||||
If you receive the SHA1 name of a blob from one source, and its contents
|
||||
from another (possibly untrusted) source, you can still trust that those
|
||||
contents are correct as long as the SHA1 name agrees. This is because
|
||||
the SHA1 is designed so that it is infeasible to find different contents
|
||||
that produce the same hash.
|
||||
|
||||
Furthermore, since the SHA1 signature of a commit refers to the
|
||||
SHA1 signatures of the tree it is associated with and the signatures
|
||||
of the parent, a single named commit specifies uniquely a whole set
|
||||
of history, with full contents. You can't later fake any step of the
|
||||
way once you have the name of a commit.
|
||||
Similarly, you need only trust the SHA1 name of a top-level tree object
|
||||
to trust the contents of the entire directory that it refers to, and if
|
||||
you receive the SHA1 name of a commit from a trusted source, then you
|
||||
can easily verify the entire history of commits reachable through
|
||||
parents of that commit, and all of those contents of the trees referred
|
||||
to by those commits.
|
||||
|
||||
So to introduce some real trust in the system, the only thing you need
|
||||
to do is to digitally sign just 'one' special note, which includes the
|
||||
|
@ -2908,103 +2920,294 @@ To assist in this, git also provides the tag object...
|
|||
|
||||
[[tag-object]]
|
||||
Tag Object
|
||||
----------
|
||||
~~~~~~~~~~
|
||||
|
||||
Git provides the "tag" object to simplify creating, managing and
|
||||
exchanging symbolic and signed tokens. The "tag" object at its
|
||||
simplest simply symbolically identifies another object by containing
|
||||
the sha1, type and symbolic name.
|
||||
A tag object contains an object, object type, tag name, the name of the
|
||||
person ("tagger") who created the tag, and a message, which may contain
|
||||
a signature, as can be seen using the gitlink:git-cat-file[1]:
|
||||
|
||||
However it can optionally contain additional signature information
|
||||
(which git doesn't care about as long as there's less than 8k of
|
||||
it). This can then be verified externally to git.
|
||||
------------------------------------------------
|
||||
$ git cat-file tag v1.5.0
|
||||
object 437b1b20df4b356c9342dac8d38849f24ef44f27
|
||||
type commit
|
||||
tag v1.5.0
|
||||
tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000
|
||||
|
||||
Note that despite the tag features, "git" itself only handles content
|
||||
integrity; the trust framework (and signature provision and
|
||||
verification) has to come from outside.
|
||||
GIT 1.5.0
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
A tag is created with gitlink:git-mktag[1],
|
||||
its data can be accessed by gitlink:git-cat-file[1],
|
||||
and the signature can be verified by
|
||||
gitlink:git-verify-tag[1].
|
||||
iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
|
||||
nLE/L9aUXdWeTFPron96DLA=
|
||||
=2E+0
|
||||
-----END PGP SIGNATURE-----
|
||||
------------------------------------------------
|
||||
|
||||
See the gitlink:git-tag[1] command to learn how to create and verify tag
|
||||
objects. (Note that gitlink:git-tag[1] can also be used to create
|
||||
"lightweight tags", which are not tag objects at all, but just simple
|
||||
references in .git/refs/tags/).
|
||||
|
||||
[[pack-files]]
|
||||
How git stores objects efficiently: pack files
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Newly created objects are initially created in a file named after the
|
||||
object's SHA1 hash (stored in .git/objects).
|
||||
|
||||
Unfortunately this system becomes inefficient once a project has a
|
||||
lot of objects. Try this on an old project:
|
||||
|
||||
------------------------------------------------
|
||||
$ git count-objects
|
||||
6930 objects, 47620 kilobytes
|
||||
------------------------------------------------
|
||||
|
||||
The first number is the number of objects which are kept in
|
||||
individual files. The second is the amount of space taken up by
|
||||
those "loose" objects.
|
||||
|
||||
You can save space and make git faster by moving these loose objects in
|
||||
to a "pack file", which stores a group of objects in an efficient
|
||||
compressed format; the details of how pack files are formatted can be
|
||||
found in link:technical/pack-format.txt[technical/pack-format.txt].
|
||||
|
||||
To put the loose objects into a pack, just run git repack:
|
||||
|
||||
------------------------------------------------
|
||||
$ git repack
|
||||
Generating pack...
|
||||
Done counting 6020 objects.
|
||||
Deltifying 6020 objects.
|
||||
100% (6020/6020) done
|
||||
Writing 6020 objects.
|
||||
100% (6020/6020) done
|
||||
Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
|
||||
Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.
|
||||
------------------------------------------------
|
||||
|
||||
You can then run
|
||||
|
||||
------------------------------------------------
|
||||
$ git prune
|
||||
------------------------------------------------
|
||||
|
||||
to remove any of the "loose" objects that are now contained in the
|
||||
pack. This will also remove any unreferenced objects (which may be
|
||||
created when, for example, you use "git reset" to remove a commit).
|
||||
You can verify that the loose objects are gone by looking at the
|
||||
.git/objects directory or by running
|
||||
|
||||
------------------------------------------------
|
||||
$ git count-objects
|
||||
0 objects, 0 kilobytes
|
||||
------------------------------------------------
|
||||
|
||||
Although the object files are gone, any commands that refer to those
|
||||
objects will work exactly as they did before.
|
||||
|
||||
The gitlink:git-gc[1] command performs packing, pruning, and more for
|
||||
you, so is normally the only high-level command you need.
|
||||
|
||||
[[dangling-objects]]
|
||||
Dangling objects
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The gitlink:git-fsck[1] command will sometimes complain about dangling
|
||||
objects. They are not a problem.
|
||||
|
||||
The most common cause of dangling objects is that you've rebased a
|
||||
branch, or you have pulled from somebody else who rebased a branch--see
|
||||
<<cleaning-up-history>>. In that case, the old head of the original
|
||||
branch still exists, as does everything it pointed to. The branch
|
||||
pointer itself just doesn't, since you replaced it with another one.
|
||||
|
||||
There are also other situations that cause dangling objects. For
|
||||
example, a "dangling blob" may arise because you did a "git add" of a
|
||||
file, but then, before you actually committed it and made it part of the
|
||||
bigger picture, you changed something else in that file and committed
|
||||
that *updated* thing - the old state that you added originally ends up
|
||||
not being pointed to by any commit or tree, so it's now a dangling blob
|
||||
object.
|
||||
|
||||
Similarly, when the "recursive" merge strategy runs, and finds that
|
||||
there are criss-cross merges and thus more than one merge base (which is
|
||||
fairly unusual, but it does happen), it will generate one temporary
|
||||
midway tree (or possibly even more, if you had lots of criss-crossing
|
||||
merges and more than two merge bases) as a temporary internal merge
|
||||
base, and again, those are real objects, but the end result will not end
|
||||
up pointing to them, so they end up "dangling" in your repository.
|
||||
|
||||
Generally, dangling objects aren't anything to worry about. They can
|
||||
even be very useful: if you screw something up, the dangling objects can
|
||||
be how you recover your old tree (say, you did a rebase, and realized
|
||||
that you really didn't want to - you can look at what dangling objects
|
||||
you have, and decide to reset your head to some old dangling state).
|
||||
|
||||
For commits, you can just use:
|
||||
|
||||
------------------------------------------------
|
||||
$ gitk <dangling-commit-sha-goes-here> --not --all
|
||||
------------------------------------------------
|
||||
|
||||
This asks for all the history reachable from the given commit but not
|
||||
from any branch, tag, or other reference. If you decide it's something
|
||||
you want, you can always create a new reference to it, e.g.,
|
||||
|
||||
------------------------------------------------
|
||||
$ git branch recovered-branch <dangling-commit-sha-goes-here>
|
||||
------------------------------------------------
|
||||
|
||||
For blobs and trees, you can't do the same, but you can still examine
|
||||
them. You can just do
|
||||
|
||||
------------------------------------------------
|
||||
$ git show <dangling-blob/tree-sha-goes-here>
|
||||
------------------------------------------------
|
||||
|
||||
to show what the contents of the blob were (or, for a tree, basically
|
||||
what the "ls" for that directory was), and that may give you some idea
|
||||
of what the operation was that left that dangling object.
|
||||
|
||||
Usually, dangling blobs and trees aren't very interesting. They're
|
||||
almost always the result of either being a half-way mergebase (the blob
|
||||
will often even have the conflict markers from a merge in it, if you
|
||||
have had conflicting merges that you fixed up by hand), or simply
|
||||
because you interrupted a "git fetch" with ^C or something like that,
|
||||
leaving _some_ of the new objects in the object database, but just
|
||||
dangling and useless.
|
||||
|
||||
Anyway, once you are sure that you're not interested in any dangling
|
||||
state, you can just prune all unreachable objects:
|
||||
|
||||
------------------------------------------------
|
||||
$ git prune
|
||||
------------------------------------------------
|
||||
|
||||
and they'll be gone. But you should only run "git prune" on a quiescent
|
||||
repository - it's kind of like doing a filesystem fsck recovery: you
|
||||
don't want to do that while the filesystem is mounted.
|
||||
|
||||
(The same is true of "git-fsck" itself, btw - but since
|
||||
git-fsck never actually *changes* the repository, it just reports
|
||||
on what it found, git-fsck itself is never "dangerous" to run.
|
||||
Running it while somebody is actually changing the repository can cause
|
||||
confusing and scary messages, but it won't actually do anything bad. In
|
||||
contrast, running "git prune" while somebody is actively changing the
|
||||
repository is a *BAD* idea).
|
||||
|
||||
[[the-index]]
|
||||
The "index" aka "Current Directory Cache"
|
||||
-----------------------------------------
|
||||
The index
|
||||
-----------
|
||||
|
||||
The index is a simple binary file, which contains an efficient
|
||||
representation of the contents of a virtual directory. It
|
||||
does so by a simple array that associates a set of names, dates,
|
||||
permissions and content (aka "blob") objects together. The cache is
|
||||
always kept ordered by name, and names are unique (with a few very
|
||||
specific rules) at any point in time, but the cache has no long-term
|
||||
meaning, and can be partially updated at any time.
|
||||
The index is a binary file (generally kept in .git/index) containing a
|
||||
sorted list of path names, each with permissions and the SHA1 of a blob
|
||||
object; gitlink:git-ls-files[1] can show you the contents of the index:
|
||||
|
||||
In particular, the index certainly does not need to be consistent with
|
||||
the current directory contents (in fact, most operations will depend on
|
||||
different ways to make the index 'not' be consistent with the directory
|
||||
hierarchy), but it has three very important attributes:
|
||||
-------------------------------------------------
|
||||
$ git ls-files --stage
|
||||
100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0 .gitignore
|
||||
100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0 .mailmap
|
||||
100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0 COPYING
|
||||
100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0 Documentation/.gitignore
|
||||
100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0 Documentation/Makefile
|
||||
...
|
||||
100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0 xdiff/xtypes.h
|
||||
100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0 xdiff/xutils.c
|
||||
100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0 xdiff/xutils.h
|
||||
-------------------------------------------------
|
||||
|
||||
'(a) it can re-generate the full state it caches (not just the
|
||||
directory structure: it contains pointers to the "blob" objects so
|
||||
that it can regenerate the data too)'
|
||||
Note that in older documentation you may see the index called the
|
||||
"current directory cache" or just the "cache". It has three important
|
||||
properties:
|
||||
|
||||
As a special case, there is a clear and unambiguous one-way mapping
|
||||
from a current directory cache to a "tree object", which can be
|
||||
efficiently created from just the current directory cache without
|
||||
actually looking at any other data. So a directory cache at any one
|
||||
time uniquely specifies one and only one "tree" object (but has
|
||||
additional data to make it easy to match up that tree object with what
|
||||
has happened in the directory)
|
||||
1. The index contains all the information necessary to generate a single
|
||||
(uniquely determined) tree object.
|
||||
+
|
||||
For example, running gitlink:git-commit[1] generates this tree object
|
||||
from the index, stores it in the object database, and uses it as the
|
||||
tree object associated with the new commit.
|
||||
|
||||
'(b) it has efficient methods for finding inconsistencies between that
|
||||
cached state ("tree object waiting to be instantiated") and the
|
||||
current state.'
|
||||
2. The index enables fast comparisons between the tree object it defines
|
||||
and the working tree.
|
||||
+
|
||||
It does this by storing some additional data for each entry (such as
|
||||
the last modified time). This data is not displayed above, and is not
|
||||
stored in the created tree object, but it can be used to determine
|
||||
quickly which files in the working directory differ from what was
|
||||
stored in the index, and thus save git from having to read all of the
|
||||
data from such files to look for changes.
|
||||
|
||||
'(c) it can additionally efficiently represent information about merge
|
||||
conflicts between different tree objects, allowing each pathname to be
|
||||
3. It can efficiently represent information about merge conflicts
|
||||
between different tree objects, allowing each pathname to be
|
||||
associated with sufficient information about the trees involved that
|
||||
you can create a three-way merge between them.'
|
||||
you can create a three-way merge between them.
|
||||
+
|
||||
We saw in <<conflict-resolution>> that during a merge the index can
|
||||
store multiple versions of a single file (called "stages"). The third
|
||||
column in the gitlink:git-ls-files[1] output above is the stage
|
||||
number, and will take on values other than 0 for files with merge
|
||||
conflicts.
|
||||
|
||||
Those are the ONLY three things that the directory cache does. It's a
|
||||
cache, and the normal operation is to re-generate it completely from a
|
||||
known tree object, or update/compare it with a live tree that is being
|
||||
developed. If you blow the directory cache away entirely, you generally
|
||||
haven't lost any information as long as you have the name of the tree
|
||||
that it described.
|
||||
The index is thus a sort of temporary staging area, which is filled with
|
||||
a tree which you are in the process of working on.
|
||||
|
||||
At the same time, the index is also the staging area for creating
|
||||
new trees, and creating a new tree always involves a controlled
|
||||
modification of the index file. In particular, the index file can
|
||||
have the representation of an intermediate tree that has not yet been
|
||||
instantiated. So the index can be thought of as a write-back cache,
|
||||
which can contain dirty information that has not yet been written back
|
||||
to the backing store.
|
||||
If you blow the index away entirely, you generally haven't lost any
|
||||
information as long as you have the name of the tree that it described.
|
||||
|
||||
[[low-level-operations]]
|
||||
Low-level git operations
|
||||
========================
|
||||
|
||||
Many of the higher-level commands were originally implemented as shell
|
||||
scripts using a smaller core of low-level git commands. These can still
|
||||
be useful when doing unusual things with git, or just as a way to
|
||||
understand its inner workings.
|
||||
|
||||
[[object-manipulation]]
|
||||
Object access and manipulation
|
||||
------------------------------
|
||||
|
||||
The gitlink:git-cat-file[1] command can show the contents of any object,
|
||||
though the higher-level gitlink:git-show[1] is usually more useful.
|
||||
|
||||
The gitlink:git-commit-tree[1] command allows constructing commits with
|
||||
arbitrary parents and trees.
|
||||
|
||||
A tree can be created with gitlink:git-write-tree[1] and its data can be
|
||||
accessed by gitlink:git-ls-tree[1]. Two trees can be compared with
|
||||
gitlink:git-diff-tree[1].
|
||||
|
||||
A tag is created with gitlink:git-mktag[1], and the signature can be
|
||||
verified by gitlink:git-verify-tag[1], though it is normally simpler to
|
||||
use gitlink:git-tag[1] for both.
|
||||
|
||||
[[the-workflow]]
|
||||
The Workflow
|
||||
------------
|
||||
|
||||
High-level operations such as gitlink:git-commit[1],
|
||||
gitlink:git-checkout[1] and git-reset[1] work by moving data between the
|
||||
working tree, the index, and the object database. Git provides
|
||||
low-level operations which perform each of these steps individually.
|
||||
|
||||
Generally, all "git" operations work on the index file. Some operations
|
||||
work *purely* on the index file (showing the current state of the
|
||||
index), but most operations move data to and from the index file. Either
|
||||
from the database or from the working directory. Thus there are four
|
||||
main combinations:
|
||||
index), but most operations move data between the index file and either
|
||||
the database or the working directory. Thus there are four main
|
||||
combinations:
|
||||
|
||||
[[working-directory-to-index]]
|
||||
working directory -> index
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You update the index with information from the working directory with
|
||||
the gitlink:git-update-index[1] command. You
|
||||
generally update the index information by just specifying the filename
|
||||
you want to update, like so:
|
||||
The gitlink:git-update-index[1] command updates the index with
|
||||
information from the working directory. You generally update the
|
||||
index information by just specifying the filename you want to update,
|
||||
like so:
|
||||
|
||||
-------------------------------------------------
|
||||
$ git-update-index filename
|
||||
$ git update-index filename
|
||||
-------------------------------------------------
|
||||
|
||||
but to avoid common mistakes with filename globbing etc, the command
|
||||
|
@ -3028,6 +3231,9 @@ stat information. It will 'not' update the object status itself, and
|
|||
it will only update the fields that are used to quickly test whether
|
||||
an object still matches its old backing store object.
|
||||
|
||||
The previously introduced gitlink:git-add[1] is just a wrapper for
|
||||
gitlink:git-update-index[1].
|
||||
|
||||
[[index-to-object-database]]
|
||||
index -> object database
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -3035,7 +3241,7 @@ index -> object database
|
|||
You write your current index file to a "tree" object with the program
|
||||
|
||||
-------------------------------------------------
|
||||
$ git-write-tree
|
||||
$ git write-tree
|
||||
-------------------------------------------------
|
||||
|
||||
that doesn't come with any options - it will just write out the
|
||||
|
@ -3326,153 +3532,44 @@ $ git-merge-index git-merge-one-file hello.c
|
|||
|
||||
and that is what higher level `git merge -s resolve` is implemented with.
|
||||
|
||||
[[pack-files]]
|
||||
How git stores objects efficiently: pack files
|
||||
----------------------------------------------
|
||||
[[hacking-git]]
|
||||
Hacking git
|
||||
===========
|
||||
|
||||
We've seen how git stores each object in a file named after the
|
||||
object's SHA1 hash.
|
||||
This chapter covers internal details of the git implementation which
|
||||
probably only git developers need to understand.
|
||||
|
||||
Unfortunately this system becomes inefficient once a project has a
|
||||
lot of objects. Try this on an old project:
|
||||
[[object-details]]
|
||||
Object storage format
|
||||
---------------------
|
||||
|
||||
------------------------------------------------
|
||||
$ git count-objects
|
||||
6930 objects, 47620 kilobytes
|
||||
------------------------------------------------
|
||||
All objects have a statically determined "type" which identifies the
|
||||
format of the object (i.e. how it is used, and how it can refer to other
|
||||
objects). There are currently four different object types: "blob",
|
||||
"tree", "commit", and "tag".
|
||||
|
||||
The first number is the number of objects which are kept in
|
||||
individual files. The second is the amount of space taken up by
|
||||
those "loose" objects.
|
||||
Regardless of object type, all objects share the following
|
||||
characteristics: they are all deflated with zlib, and have a header
|
||||
that not only specifies their type, but also provides size information
|
||||
about the data in the object. It's worth noting that the SHA1 hash
|
||||
that is used to name the object is the hash of the original data
|
||||
plus this header, so `sha1sum` 'file' does not match the object name
|
||||
for 'file'.
|
||||
(Historical note: in the dawn of the age of git the hash
|
||||
was the sha1 of the 'compressed' object.)
|
||||
|
||||
You can save space and make git faster by moving these loose objects in
|
||||
to a "pack file", which stores a group of objects in an efficient
|
||||
compressed format; the details of how pack files are formatted can be
|
||||
found in link:technical/pack-format.txt[technical/pack-format.txt].
|
||||
As a result, the general consistency of an object can always be tested
|
||||
independently of the contents or the type of the object: all objects can
|
||||
be validated by verifying that (a) their hashes match the content of the
|
||||
file and (b) the object successfully inflates to a stream of bytes that
|
||||
forms a sequence of <ascii type without space> {plus} <space> {plus} <ascii decimal
|
||||
size> {plus} <byte\0> {plus} <binary object data>.
|
||||
|
||||
To put the loose objects into a pack, just run git repack:
|
||||
|
||||
------------------------------------------------
|
||||
$ git repack
|
||||
Generating pack...
|
||||
Done counting 6020 objects.
|
||||
Deltifying 6020 objects.
|
||||
100% (6020/6020) done
|
||||
Writing 6020 objects.
|
||||
100% (6020/6020) done
|
||||
Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
|
||||
Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.
|
||||
------------------------------------------------
|
||||
|
||||
You can then run
|
||||
|
||||
------------------------------------------------
|
||||
$ git prune
|
||||
------------------------------------------------
|
||||
|
||||
to remove any of the "loose" objects that are now contained in the
|
||||
pack. This will also remove any unreferenced objects (which may be
|
||||
created when, for example, you use "git reset" to remove a commit).
|
||||
You can verify that the loose objects are gone by looking at the
|
||||
.git/objects directory or by running
|
||||
|
||||
------------------------------------------------
|
||||
$ git count-objects
|
||||
0 objects, 0 kilobytes
|
||||
------------------------------------------------
|
||||
|
||||
Although the object files are gone, any commands that refer to those
|
||||
objects will work exactly as they did before.
|
||||
|
||||
The gitlink:git-gc[1] command performs packing, pruning, and more for
|
||||
you, so is normally the only high-level command you need.
|
||||
|
||||
[[dangling-objects]]
|
||||
Dangling objects
|
||||
----------------
|
||||
|
||||
The gitlink:git-fsck[1] command will sometimes complain about dangling
|
||||
objects. They are not a problem.
|
||||
|
||||
The most common cause of dangling objects is that you've rebased a
|
||||
branch, or you have pulled from somebody else who rebased a branch--see
|
||||
<<cleaning-up-history>>. In that case, the old head of the original
|
||||
branch still exists, as does everything it pointed to. The branch
|
||||
pointer itself just doesn't, since you replaced it with another one.
|
||||
|
||||
There are also other situations that cause dangling objects. For
|
||||
example, a "dangling blob" may arise because you did a "git add" of a
|
||||
file, but then, before you actually committed it and made it part of the
|
||||
bigger picture, you changed something else in that file and committed
|
||||
that *updated* thing - the old state that you added originally ends up
|
||||
not being pointed to by any commit or tree, so it's now a dangling blob
|
||||
object.
|
||||
|
||||
Similarly, when the "recursive" merge strategy runs, and finds that
|
||||
there are criss-cross merges and thus more than one merge base (which is
|
||||
fairly unusual, but it does happen), it will generate one temporary
|
||||
midway tree (or possibly even more, if you had lots of criss-crossing
|
||||
merges and more than two merge bases) as a temporary internal merge
|
||||
base, and again, those are real objects, but the end result will not end
|
||||
up pointing to them, so they end up "dangling" in your repository.
|
||||
|
||||
Generally, dangling objects aren't anything to worry about. They can
|
||||
even be very useful: if you screw something up, the dangling objects can
|
||||
be how you recover your old tree (say, you did a rebase, and realized
|
||||
that you really didn't want to - you can look at what dangling objects
|
||||
you have, and decide to reset your head to some old dangling state).
|
||||
|
||||
For commits, you can just use:
|
||||
|
||||
------------------------------------------------
|
||||
$ gitk <dangling-commit-sha-goes-here> --not --all
|
||||
------------------------------------------------
|
||||
|
||||
This asks for all the history reachable from the given commit but not
|
||||
from any branch, tag, or other reference. If you decide it's something
|
||||
you want, you can always create a new reference to it, e.g.,
|
||||
|
||||
------------------------------------------------
|
||||
$ git branch recovered-branch <dangling-commit-sha-goes-here>
|
||||
------------------------------------------------
|
||||
|
||||
For blobs and trees, you can't do the same, but you can still examine
|
||||
them. You can just do
|
||||
|
||||
------------------------------------------------
|
||||
$ git show <dangling-blob/tree-sha-goes-here>
|
||||
------------------------------------------------
|
||||
|
||||
to show what the contents of the blob were (or, for a tree, basically
|
||||
what the "ls" for that directory was), and that may give you some idea
|
||||
of what the operation was that left that dangling object.
|
||||
|
||||
Usually, dangling blobs and trees aren't very interesting. They're
|
||||
almost always the result of either being a half-way mergebase (the blob
|
||||
will often even have the conflict markers from a merge in it, if you
|
||||
have had conflicting merges that you fixed up by hand), or simply
|
||||
because you interrupted a "git fetch" with ^C or something like that,
|
||||
leaving _some_ of the new objects in the object database, but just
|
||||
dangling and useless.
|
||||
|
||||
Anyway, once you are sure that you're not interested in any dangling
|
||||
state, you can just prune all unreachable objects:
|
||||
|
||||
------------------------------------------------
|
||||
$ git prune
|
||||
------------------------------------------------
|
||||
|
||||
and they'll be gone. But you should only run "git prune" on a quiescent
|
||||
repository - it's kind of like doing a filesystem fsck recovery: you
|
||||
don't want to do that while the filesystem is mounted.
|
||||
|
||||
(The same is true of "git-fsck" itself, btw - but since
|
||||
git-fsck never actually *changes* the repository, it just reports
|
||||
on what it found, git-fsck itself is never "dangerous" to run.
|
||||
Running it while somebody is actually changing the repository can cause
|
||||
confusing and scary messages, but it won't actually do anything bad. In
|
||||
contrast, running "git prune" while somebody is actively changing the
|
||||
repository is a *BAD* idea).
|
||||
The structured objects can further have their structure and
|
||||
connectivity to other objects verified. This is generally done with
|
||||
the `git-fsck` program, which generates a full dependency graph
|
||||
of all objects, and verifies their internal consistency (in addition
|
||||
to just verifying their superficial consistency through the hash).
|
||||
|
||||
[[birdview-on-the-source-code]]
|
||||
A birds-eye view of Git's source code
|
||||
|
@ -3926,25 +4023,26 @@ Appendix B: Notes and todo list for this manual
|
|||
This is a work in progress.
|
||||
|
||||
The basic requirements:
|
||||
- It must be readable in order, from beginning to end, by
|
||||
someone intelligent with a basic grasp of the UNIX
|
||||
command line, but without any special knowledge of git. If
|
||||
necessary, any other prerequisites should be specifically
|
||||
mentioned as they arise.
|
||||
- Whenever possible, section headings should clearly describe
|
||||
the task they explain how to do, in language that requires
|
||||
no more knowledge than necessary: for example, "importing
|
||||
patches into a project" rather than "the git-am command"
|
||||
|
||||
- It must be readable in order, from beginning to end, by someone
|
||||
intelligent with a basic grasp of the UNIX command line, but without
|
||||
any special knowledge of git. If necessary, any other prerequisites
|
||||
should be specifically mentioned as they arise.
|
||||
- Whenever possible, section headings should clearly describe the task
|
||||
they explain how to do, in language that requires no more knowledge
|
||||
than necessary: for example, "importing patches into a project" rather
|
||||
than "the git-am command"
|
||||
|
||||
Think about how to create a clear chapter dependency graph that will
|
||||
allow people to get to important topics without necessarily reading
|
||||
everything in between.
|
||||
|
||||
Scan Documentation/ for other stuff left out; in particular:
|
||||
howto's
|
||||
some of technical/?
|
||||
hooks
|
||||
list of commands in gitlink:git[1]
|
||||
|
||||
- howto's
|
||||
- some of technical/?
|
||||
- hooks
|
||||
- list of commands in gitlink:git[1]
|
||||
|
||||
Scan email archives for other stuff left out
|
||||
|
||||
|
@ -3973,3 +4071,5 @@ Write a chapter on using plumbing and writing scripts.
|
|||
Alternates, clone -reference, etc.
|
||||
|
||||
git unpack-objects -r for recovery
|
||||
|
||||
submodules
|
||||
|
|
12
Makefile
12
Makefile
|
@ -124,6 +124,9 @@ all::
|
|||
# If not set it defaults to the bare 'wish'. If it is set to the empty
|
||||
# string then NO_TCLTK will be forced (this is used by configure script).
|
||||
#
|
||||
# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
|
||||
# parallel delta searching when packing objects.
|
||||
#
|
||||
|
||||
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
||||
@$(SHELL_PATH) ./GIT-VERSION-GEN
|
||||
|
@ -208,7 +211,7 @@ SCRIPT_SH = \
|
|||
git-ls-remote.sh \
|
||||
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
|
||||
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
|
||||
git-repack.sh git-request-pull.sh git-reset.sh \
|
||||
git-repack.sh git-request-pull.sh \
|
||||
git-sh-setup.sh \
|
||||
git-am.sh \
|
||||
git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
|
||||
|
@ -355,6 +358,7 @@ BUILTIN_OBJS = \
|
|||
builtin-reflog.o \
|
||||
builtin-config.o \
|
||||
builtin-rerere.o \
|
||||
builtin-reset.o \
|
||||
builtin-rev-list.o \
|
||||
builtin-rev-parse.o \
|
||||
builtin-revert.o \
|
||||
|
@ -442,6 +446,7 @@ ifeq ($(uname_O),Cygwin)
|
|||
endif
|
||||
ifeq ($(uname_S),FreeBSD)
|
||||
NEEDS_LIBICONV = YesPlease
|
||||
NO_MEMMEM = YesPlease
|
||||
BASIC_CFLAGS += -I/usr/local/include
|
||||
BASIC_LDFLAGS += -L/usr/local/lib
|
||||
endif
|
||||
|
@ -674,6 +679,11 @@ ifdef NO_MEMMEM
|
|||
COMPAT_OBJS += compat/memmem.o
|
||||
endif
|
||||
|
||||
ifdef THREADED_DELTA_SEARCH
|
||||
BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
|
||||
EXTLIBS += -lpthread
|
||||
endif
|
||||
|
||||
ifeq ($(TCLTK_PATH),)
|
||||
NO_TCLTK=NoThanks
|
||||
endif
|
||||
|
|
|
@ -192,7 +192,8 @@ static int write_zip_entry(const unsigned char *sha1,
|
|||
compressed_size = 0;
|
||||
} else if (S_ISREG(mode) || S_ISLNK(mode)) {
|
||||
method = 0;
|
||||
attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
|
||||
attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
|
||||
(mode & 0111) ? ((mode) << 16) : 0;
|
||||
if (S_ISREG(mode) && zlib_compression_level != 0)
|
||||
method = 8;
|
||||
result = 0;
|
||||
|
@ -231,7 +232,8 @@ static int write_zip_entry(const unsigned char *sha1,
|
|||
}
|
||||
|
||||
copy_le32(dirent.magic, 0x02014b50);
|
||||
copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
|
||||
copy_le16(dirent.creator_version,
|
||||
S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
|
||||
copy_le16(dirent.version, 10);
|
||||
copy_le16(dirent.flags, 0);
|
||||
copy_le16(dirent.compression_method, method);
|
||||
|
|
|
@ -95,14 +95,14 @@ static void update_callback(struct diff_queue_struct *q,
|
|||
const char *path = p->one->path;
|
||||
switch (p->status) {
|
||||
default:
|
||||
die("unexpacted diff status %c", p->status);
|
||||
die("unexpected diff status %c", p->status);
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
add_file_to_cache(path, verbose);
|
||||
break;
|
||||
case DIFF_STATUS_DELETED:
|
||||
remove_file_from_cache(path);
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
if (verbose)
|
||||
printf("remove '%s'\n", path);
|
||||
break;
|
||||
|
|
|
@ -241,7 +241,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
|
|||
if (name) {
|
||||
char *cp = name;
|
||||
while (p_value) {
|
||||
cp = strchr(name, '/');
|
||||
cp = strchr(cp, '/');
|
||||
if (!cp)
|
||||
break;
|
||||
cp++;
|
||||
|
@ -1612,15 +1612,22 @@ static int apply_line(char *output, const char *patch, int plen)
|
|||
|
||||
buf = output;
|
||||
if (need_fix_leading_space) {
|
||||
int consecutive_spaces = 0;
|
||||
/* between patch[1..last_tab_in_indent] strip the
|
||||
* funny spaces, updating them to tab as needed.
|
||||
*/
|
||||
for (i = 1; i < last_tab_in_indent; i++, plen--) {
|
||||
char ch = patch[i];
|
||||
if (ch != ' ')
|
||||
if (ch != ' ') {
|
||||
consecutive_spaces = 0;
|
||||
*output++ = ch;
|
||||
else if ((i % 8) == 0)
|
||||
} else {
|
||||
consecutive_spaces++;
|
||||
if (consecutive_spaces == 8) {
|
||||
*output++ = '\t';
|
||||
consecutive_spaces = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
fixed = 1;
|
||||
i = last_tab_in_indent;
|
||||
|
@ -2152,6 +2159,20 @@ static int check_patch_list(struct patch *patch)
|
|||
return err;
|
||||
}
|
||||
|
||||
/* This function tries to read the sha1 from the current index */
|
||||
static int get_current_sha1(const char *path, unsigned char *sha1)
|
||||
{
|
||||
int pos;
|
||||
|
||||
if (read_cache() < 0)
|
||||
return -1;
|
||||
pos = cache_name_pos(path, strlen(path));
|
||||
if (pos < 0)
|
||||
return -1;
|
||||
hashcpy(sha1, active_cache[pos]->sha1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void show_index_list(struct patch *list)
|
||||
{
|
||||
struct patch *patch;
|
||||
|
@ -2168,8 +2189,16 @@ static void show_index_list(struct patch *list)
|
|||
if (0 < patch->is_new)
|
||||
sha1_ptr = null_sha1;
|
||||
else if (get_sha1(patch->old_sha1_prefix, sha1))
|
||||
die("sha1 information is lacking or useless (%s).",
|
||||
name);
|
||||
/* git diff has no index line for mode/type changes */
|
||||
if (!patch->lines_added && !patch->lines_deleted) {
|
||||
if (get_current_sha1(patch->new_name, sha1) ||
|
||||
get_current_sha1(patch->old_name, sha1))
|
||||
die("mode change for %s, which is not "
|
||||
"in current HEAD", name);
|
||||
sha1_ptr = sha1;
|
||||
} else
|
||||
die("sha1 information is lacking or useless "
|
||||
"(%s).", name);
|
||||
else
|
||||
sha1_ptr = sha1;
|
||||
|
||||
|
@ -2319,7 +2348,6 @@ static void remove_file(struct patch *patch, int rmdir_empty)
|
|||
if (update_index) {
|
||||
if (remove_file_from_cache(patch->old_name) < 0)
|
||||
die("unable to remove %s from index", patch->old_name);
|
||||
cache_tree_invalidate_path(active_cache_tree, patch->old_name);
|
||||
}
|
||||
if (!cached) {
|
||||
if (S_ISGITLINK(patch->old_mode)) {
|
||||
|
@ -2467,7 +2495,6 @@ static void create_file(struct patch *patch)
|
|||
mode = S_IFREG | 0644;
|
||||
create_one_file(path, mode, buf, size);
|
||||
add_index_file(path, mode, buf, size);
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
}
|
||||
|
||||
/* phase zero is to remove, phase one is to create */
|
||||
|
|
|
@ -187,6 +187,78 @@ static int exec_grep(int argc, const char **argv)
|
|||
else die("maximum number of args exceeded"); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* If you send a singleton filename to grep, it does not give
|
||||
* the name of the file. GNU grep has "-H" but we would want
|
||||
* that behaviour in a portable way.
|
||||
*
|
||||
* So we keep two pathnames in argv buffer unsent to grep in
|
||||
* the main loop if we need to do more than one grep.
|
||||
*/
|
||||
static int flush_grep(struct grep_opt *opt,
|
||||
int argc, int arg0, const char **argv, int *kept)
|
||||
{
|
||||
int status;
|
||||
int count = argc - arg0;
|
||||
const char *kept_0 = NULL;
|
||||
|
||||
if (count <= 2) {
|
||||
/*
|
||||
* Because we keep at least 2 paths in the call from
|
||||
* the main loop (i.e. kept != NULL), and MAXARGS is
|
||||
* far greater than 2, this usually is a call to
|
||||
* conclude the grep. However, the user could attempt
|
||||
* to overflow the argv buffer by giving too many
|
||||
* options to leave very small number of real
|
||||
* arguments even for the call in the main loop.
|
||||
*/
|
||||
if (kept)
|
||||
die("insanely many options to grep");
|
||||
|
||||
/*
|
||||
* If we have two or more paths, we do not have to do
|
||||
* anything special, but we need to push /dev/null to
|
||||
* get "-H" behaviour of GNU grep portably but when we
|
||||
* are not doing "-l" nor "-L" nor "-c".
|
||||
*/
|
||||
if (count == 1 &&
|
||||
!opt->name_only &&
|
||||
!opt->unmatch_name_only &&
|
||||
!opt->count) {
|
||||
argv[argc++] = "/dev/null";
|
||||
argv[argc] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
else if (kept) {
|
||||
/*
|
||||
* Called because we found many paths and haven't finished
|
||||
* iterating over the cache yet. We keep two paths
|
||||
* for the concluding call. argv[argc-2] and argv[argc-1]
|
||||
* has the last two paths, so save the first one away,
|
||||
* replace it with NULL while sending the list to grep,
|
||||
* and recover them after we are done.
|
||||
*/
|
||||
*kept = 2;
|
||||
kept_0 = argv[argc-2];
|
||||
argv[argc-2] = NULL;
|
||||
argc -= 2;
|
||||
}
|
||||
|
||||
status = exec_grep(argc, argv);
|
||||
|
||||
if (kept_0) {
|
||||
/*
|
||||
* Then recover them. Now the last arg is beyond the
|
||||
* terminating NULL which is at argc, and the second
|
||||
* from the last is what we saved away in kept_0
|
||||
*/
|
||||
argv[arg0++] = kept_0;
|
||||
argv[arg0] = argv[argc+1];
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
||||
{
|
||||
int i, nr, argc, hit, len, status;
|
||||
|
@ -253,22 +325,12 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
|||
push_arg(p->pattern);
|
||||
}
|
||||
|
||||
/*
|
||||
* To make sure we get the header printed out when we want it,
|
||||
* add /dev/null to the paths to grep. This is unnecessary
|
||||
* (and wrong) with "-l" or "-L", which always print out the
|
||||
* name anyway.
|
||||
*
|
||||
* GNU grep has "-H", but this is portable.
|
||||
*/
|
||||
if (!opt->name_only && !opt->unmatch_name_only)
|
||||
push_arg("/dev/null");
|
||||
|
||||
hit = 0;
|
||||
argc = nr;
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
char *name;
|
||||
int kept;
|
||||
if (!S_ISREG(ntohl(ce->ce_mode)))
|
||||
continue;
|
||||
if (!pathspec_matches(paths, ce->name))
|
||||
|
@ -283,10 +345,10 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
|||
argv[argc++] = name;
|
||||
if (argc < MAXARGS && !ce_stage(ce))
|
||||
continue;
|
||||
status = exec_grep(argc, argv);
|
||||
status = flush_grep(opt, argc, nr, argv, &kept);
|
||||
if (0 < status)
|
||||
hit = 1;
|
||||
argc = nr;
|
||||
argc = nr + kept;
|
||||
if (ce_stage(ce)) {
|
||||
do {
|
||||
i++;
|
||||
|
@ -296,7 +358,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
|
|||
}
|
||||
}
|
||||
if (argc > nr) {
|
||||
status = exec_grep(argc, argv);
|
||||
status = flush_grep(opt, argc, nr, argv, NULL);
|
||||
if (0 < status)
|
||||
hit = 1;
|
||||
}
|
||||
|
|
|
@ -437,6 +437,34 @@ static void gen_message_id(char *dest, unsigned int length, char *base)
|
|||
(int)(email_end - email_start - 1), email_start + 1);
|
||||
}
|
||||
|
||||
static const char *clean_message_id(const char *msg_id)
|
||||
{
|
||||
char ch;
|
||||
const char *a, *z, *m;
|
||||
char *n;
|
||||
size_t len;
|
||||
|
||||
m = msg_id;
|
||||
while ((ch = *m) && (isspace(ch) || (ch == '<')))
|
||||
m++;
|
||||
a = m;
|
||||
z = NULL;
|
||||
while ((ch = *m)) {
|
||||
if (!isspace(ch) && (ch != '>'))
|
||||
z = m;
|
||||
m++;
|
||||
}
|
||||
if (!z)
|
||||
die("insane in-reply-to: %s", msg_id);
|
||||
if (++z == m)
|
||||
return a;
|
||||
len = z - a;
|
||||
n = xmalloc(len + 1);
|
||||
memcpy(n, a, len);
|
||||
n[len] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
int cmd_format_patch(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
struct commit *commit;
|
||||
|
@ -625,7 +653,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
|
|||
if (numbered)
|
||||
rev.total = total + start_number - 1;
|
||||
rev.add_signoff = add_signoff;
|
||||
rev.ref_message_id = in_reply_to;
|
||||
if (in_reply_to)
|
||||
rev.ref_message_id = clean_message_id(in_reply_to);
|
||||
while (0 <= --nr) {
|
||||
int shown;
|
||||
commit = list[nr];
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "quote.h"
|
||||
#include "dir.h"
|
||||
#include "builtin.h"
|
||||
#include "tree.h"
|
||||
|
||||
static int abbrev;
|
||||
static int show_deleted;
|
||||
|
@ -26,6 +27,7 @@ static int prefix_offset;
|
|||
static const char **pathspec;
|
||||
static int error_unmatch;
|
||||
static char *ps_matched;
|
||||
static const char *with_tree;
|
||||
|
||||
static const char *tag_cached = "";
|
||||
static const char *tag_unmerged = "";
|
||||
|
@ -247,6 +249,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
|
|||
continue;
|
||||
if (show_unmerged && !ce_stage(ce))
|
||||
continue;
|
||||
if (ce->ce_flags & htons(CE_UPDATE))
|
||||
continue;
|
||||
show_ce_entry(ce_stage(ce) ? tag_unmerged : tag_cached, ce);
|
||||
}
|
||||
}
|
||||
|
@ -332,6 +336,67 @@ static const char *verify_pathspec(const char *prefix)
|
|||
return real_prefix;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the tree specified with --with-tree option
|
||||
* (typically, HEAD) into stage #1 and then
|
||||
* squash them down to stage #0. This is used for
|
||||
* --error-unmatch to list and check the path patterns
|
||||
* that were given from the command line. We are not
|
||||
* going to write this index out.
|
||||
*/
|
||||
static void overlay_tree(const char *tree_name, const char *prefix)
|
||||
{
|
||||
struct tree *tree;
|
||||
unsigned char sha1[20];
|
||||
const char **match;
|
||||
struct cache_entry *last_stage0 = NULL;
|
||||
int i;
|
||||
|
||||
if (get_sha1(tree_name, sha1))
|
||||
die("tree-ish %s not found.", tree_name);
|
||||
tree = parse_tree_indirect(sha1);
|
||||
if (!tree)
|
||||
die("bad tree-ish %s", tree_name);
|
||||
|
||||
/* Hoist the unmerged entries up to stage #3 to make room */
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
if (!ce_stage(ce))
|
||||
continue;
|
||||
ce->ce_flags |= htons(CE_STAGEMASK);
|
||||
}
|
||||
|
||||
if (prefix) {
|
||||
static const char *(matchbuf[2]);
|
||||
matchbuf[0] = prefix;
|
||||
matchbuf [1] = NULL;
|
||||
match = matchbuf;
|
||||
} else
|
||||
match = NULL;
|
||||
if (read_tree(tree, 1, match))
|
||||
die("unable to read tree entries %s", tree_name);
|
||||
|
||||
for (i = 0; i < active_nr; i++) {
|
||||
struct cache_entry *ce = active_cache[i];
|
||||
switch (ce_stage(ce)) {
|
||||
case 0:
|
||||
last_stage0 = ce;
|
||||
/* fallthru */
|
||||
default:
|
||||
continue;
|
||||
case 1:
|
||||
/*
|
||||
* If there is stage #0 entry for this, we do not
|
||||
* need to show it. We use CE_UPDATE bit to mark
|
||||
* such an entry.
|
||||
*/
|
||||
if (last_stage0 &&
|
||||
!strcmp(last_stage0->name, ce->name))
|
||||
ce->ce_flags |= htons(CE_UPDATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char ls_files_usage[] =
|
||||
"git-ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
|
||||
"[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
|
||||
|
@ -452,6 +517,10 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
error_unmatch = 1;
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--with-tree=")) {
|
||||
with_tree = arg + 12;
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--abbrev=")) {
|
||||
abbrev = strtoul(arg+9, NULL, 10);
|
||||
if (abbrev && abbrev < MINIMUM_ABBREV)
|
||||
|
@ -503,6 +572,15 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
|
|||
read_cache();
|
||||
if (prefix)
|
||||
prune_cache(prefix);
|
||||
if (with_tree) {
|
||||
/*
|
||||
* Basic sanity check; show-stages and show-unmerged
|
||||
* would not make any sense with this option.
|
||||
*/
|
||||
if (show_stage || show_unmerged)
|
||||
die("ls-files --with-tree is incompatible with -s or -u");
|
||||
overlay_tree(with_tree, prefix);
|
||||
}
|
||||
show_files(&dir, prefix);
|
||||
|
||||
if (ps_matched) {
|
||||
|
|
|
@ -276,11 +276,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
|
|||
add_file_to_cache(path, verbose);
|
||||
}
|
||||
|
||||
for (i = 0; i < deleted.nr; i++) {
|
||||
const char *path = deleted.items[i].path;
|
||||
remove_file_from_cache(path);
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
}
|
||||
for (i = 0; i < deleted.nr; i++)
|
||||
remove_file_from_cache(deleted.items[i].path);
|
||||
|
||||
if (active_cache_changed) {
|
||||
if (write_cache(newfd, active_cache, active_nr) ||
|
||||
|
|
|
@ -15,12 +15,16 @@
|
|||
#include "list-objects.h"
|
||||
#include "progress.h"
|
||||
|
||||
#ifdef THREADED_DELTA_SEARCH
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
static const char pack_usage[] = "\
|
||||
git-pack-objects [{ -q | --progress | --all-progress }] \n\
|
||||
[--max-pack-size=N] [--local] [--incremental] \n\
|
||||
[--window=N] [--window-memory=N] [--depth=N] \n\
|
||||
[--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
|
||||
[--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
|
||||
[--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
|
||||
[--stdout | base-name] [<ref-list | <object-list]";
|
||||
|
||||
struct object_entry {
|
||||
|
@ -68,6 +72,7 @@ static int progress = 1;
|
|||
static int window = 10;
|
||||
static uint32_t pack_size_limit;
|
||||
static int depth = 50;
|
||||
static int delta_search_threads = 1;
|
||||
static int pack_to_stdout;
|
||||
static int num_preferred_base;
|
||||
static struct progress progress_state;
|
||||
|
@ -78,7 +83,6 @@ static unsigned long delta_cache_size = 0;
|
|||
static unsigned long max_delta_cache_size = 0;
|
||||
static unsigned long cache_max_small_delta_size = 1000;
|
||||
|
||||
static unsigned long window_memory_usage = 0;
|
||||
static unsigned long window_memory_limit = 0;
|
||||
|
||||
/*
|
||||
|
@ -1291,6 +1295,31 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef THREADED_DELTA_SEARCH
|
||||
|
||||
static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#define read_lock() pthread_mutex_lock(&read_mutex)
|
||||
#define read_unlock() pthread_mutex_unlock(&read_mutex)
|
||||
|
||||
static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#define cache_lock() pthread_mutex_lock(&cache_mutex)
|
||||
#define cache_unlock() pthread_mutex_unlock(&cache_mutex)
|
||||
|
||||
static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
#define progress_lock() pthread_mutex_lock(&progress_mutex)
|
||||
#define progress_unlock() pthread_mutex_unlock(&progress_mutex)
|
||||
|
||||
#else
|
||||
|
||||
#define read_lock() (void)0
|
||||
#define read_unlock() (void)0
|
||||
#define cache_lock() (void)0
|
||||
#define cache_unlock() (void)0
|
||||
#define progress_lock() (void)0
|
||||
#define progress_unlock() (void)0
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* We search for deltas _backwards_ in a list sorted by type and
|
||||
* by size, so that we see progressively smaller and smaller files.
|
||||
|
@ -1300,7 +1329,7 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
|
|||
* one.
|
||||
*/
|
||||
static int try_delta(struct unpacked *trg, struct unpacked *src,
|
||||
unsigned max_depth)
|
||||
unsigned max_depth, unsigned long *mem_usage)
|
||||
{
|
||||
struct object_entry *trg_entry = trg->entry;
|
||||
struct object_entry *src_entry = src->entry;
|
||||
|
@ -1313,12 +1342,6 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
|
|||
if (trg_entry->type != src_entry->type)
|
||||
return -1;
|
||||
|
||||
/* We do not compute delta to *create* objects we are not
|
||||
* going to pack.
|
||||
*/
|
||||
if (trg_entry->preferred_base)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* We do not bother to try a delta that we discarded
|
||||
* on an earlier try, but only when reusing delta data.
|
||||
|
@ -1355,24 +1378,28 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
|
|||
|
||||
/* Load data if not already done */
|
||||
if (!trg->data) {
|
||||
read_lock();
|
||||
trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
|
||||
read_unlock();
|
||||
if (!trg->data)
|
||||
die("object %s cannot be read",
|
||||
sha1_to_hex(trg_entry->idx.sha1));
|
||||
if (sz != trg_size)
|
||||
die("object %s inconsistent object length (%lu vs %lu)",
|
||||
sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
|
||||
window_memory_usage += sz;
|
||||
*mem_usage += sz;
|
||||
}
|
||||
if (!src->data) {
|
||||
read_lock();
|
||||
src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
|
||||
read_unlock();
|
||||
if (!src->data)
|
||||
die("object %s cannot be read",
|
||||
sha1_to_hex(src_entry->idx.sha1));
|
||||
if (sz != src_size)
|
||||
die("object %s inconsistent object length (%lu vs %lu)",
|
||||
sha1_to_hex(src_entry->idx.sha1), sz, src_size);
|
||||
window_memory_usage += sz;
|
||||
*mem_usage += sz;
|
||||
}
|
||||
if (!src->index) {
|
||||
src->index = create_delta_index(src->data, src_size);
|
||||
|
@ -1382,7 +1409,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
|
|||
warning("suboptimal pack - out of memory");
|
||||
return 0;
|
||||
}
|
||||
window_memory_usage += sizeof_delta_index(src->index);
|
||||
*mem_usage += sizeof_delta_index(src->index);
|
||||
}
|
||||
|
||||
delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
|
||||
|
@ -1402,17 +1429,27 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
|
|||
trg_entry->delta_size = delta_size;
|
||||
trg->depth = src->depth + 1;
|
||||
|
||||
/*
|
||||
* Handle memory allocation outside of the cache
|
||||
* accounting lock. Compiler will optimize the strangeness
|
||||
* away when THREADED_DELTA_SEARCH is not defined.
|
||||
*/
|
||||
if (trg_entry->delta_data)
|
||||
free(trg_entry->delta_data);
|
||||
cache_lock();
|
||||
if (trg_entry->delta_data) {
|
||||
delta_cache_size -= trg_entry->delta_size;
|
||||
free(trg_entry->delta_data);
|
||||
trg_entry->delta_data = NULL;
|
||||
}
|
||||
|
||||
if (delta_cacheable(src_size, trg_size, delta_size)) {
|
||||
trg_entry->delta_data = xrealloc(delta_buf, delta_size);
|
||||
delta_cache_size += trg_entry->delta_size;
|
||||
} else
|
||||
cache_unlock();
|
||||
trg_entry->delta_data = xrealloc(delta_buf, delta_size);
|
||||
} else {
|
||||
cache_unlock();
|
||||
free(delta_buf);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -1429,68 +1466,60 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
|
|||
return m;
|
||||
}
|
||||
|
||||
static void free_unpacked(struct unpacked *n)
|
||||
static unsigned long free_unpacked(struct unpacked *n)
|
||||
{
|
||||
window_memory_usage -= sizeof_delta_index(n->index);
|
||||
unsigned long freed_mem = sizeof_delta_index(n->index);
|
||||
free_delta_index(n->index);
|
||||
n->index = NULL;
|
||||
if (n->data) {
|
||||
freed_mem += n->entry->size;
|
||||
free(n->data);
|
||||
n->data = NULL;
|
||||
window_memory_usage -= n->entry->size;
|
||||
}
|
||||
n->entry = NULL;
|
||||
n->depth = 0;
|
||||
return freed_mem;
|
||||
}
|
||||
|
||||
static void find_deltas(struct object_entry **list, int window, int depth)
|
||||
static void find_deltas(struct object_entry **list, unsigned list_size,
|
||||
int window, int depth, unsigned *processed)
|
||||
{
|
||||
uint32_t i = nr_objects, idx = 0, count = 0, processed = 0;
|
||||
uint32_t i = list_size, idx = 0, count = 0;
|
||||
unsigned int array_size = window * sizeof(struct unpacked);
|
||||
struct unpacked *array;
|
||||
int max_depth;
|
||||
unsigned long mem_usage = 0;
|
||||
|
||||
if (!nr_objects)
|
||||
return;
|
||||
array = xmalloc(array_size);
|
||||
memset(array, 0, array_size);
|
||||
if (progress)
|
||||
start_progress(&progress_state, "Deltifying %u objects...", "", nr_result);
|
||||
|
||||
do {
|
||||
struct object_entry *entry = list[--i];
|
||||
struct unpacked *n = array + idx;
|
||||
int j;
|
||||
int j, max_depth, best_base = -1;
|
||||
|
||||
if (!entry->preferred_base)
|
||||
processed++;
|
||||
|
||||
if (progress)
|
||||
display_progress(&progress_state, processed);
|
||||
|
||||
if (entry->delta)
|
||||
/* This happens if we decided to reuse existing
|
||||
* delta from a pack. "!no_reuse_delta &&" is implied.
|
||||
*/
|
||||
continue;
|
||||
|
||||
if (entry->size < 50)
|
||||
continue;
|
||||
|
||||
if (entry->no_try_delta)
|
||||
continue;
|
||||
|
||||
free_unpacked(n);
|
||||
mem_usage -= free_unpacked(n);
|
||||
n->entry = entry;
|
||||
|
||||
while (window_memory_limit &&
|
||||
window_memory_usage > window_memory_limit &&
|
||||
mem_usage > window_memory_limit &&
|
||||
count > 1) {
|
||||
uint32_t tail = (idx + window - count) % window;
|
||||
free_unpacked(array + tail);
|
||||
mem_usage -= free_unpacked(array + tail);
|
||||
count--;
|
||||
}
|
||||
|
||||
/* We do not compute delta to *create* objects we are not
|
||||
* going to pack.
|
||||
*/
|
||||
if (entry->preferred_base)
|
||||
goto next;
|
||||
|
||||
progress_lock();
|
||||
(*processed)++;
|
||||
if (progress)
|
||||
display_progress(&progress_state, *processed);
|
||||
progress_unlock();
|
||||
|
||||
/*
|
||||
* If the current object is at pack edge, take the depth the
|
||||
* objects that depend on the current object into account
|
||||
|
@ -1505,6 +1534,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
|
||||
j = window;
|
||||
while (--j > 0) {
|
||||
int ret;
|
||||
uint32_t other_idx = idx + j;
|
||||
struct unpacked *m;
|
||||
if (other_idx >= window)
|
||||
|
@ -1512,8 +1542,11 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
m = array + other_idx;
|
||||
if (!m->entry)
|
||||
break;
|
||||
if (try_delta(n, m, max_depth) < 0)
|
||||
ret = try_delta(n, m, max_depth, &mem_usage);
|
||||
if (ret < 0)
|
||||
break;
|
||||
else if (ret > 0)
|
||||
best_base = other_idx;
|
||||
}
|
||||
|
||||
/* if we made n a delta, and if n is already at max
|
||||
|
@ -1523,6 +1556,23 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
if (entry->delta && depth <= n->depth)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Move the best delta base up in the window, after the
|
||||
* currently deltified object, to keep it longer. It will
|
||||
* be the first base object to be attempted next.
|
||||
*/
|
||||
if (entry->delta) {
|
||||
struct unpacked swap = array[best_base];
|
||||
int dist = (window + idx - best_base) % window;
|
||||
int dst = best_base;
|
||||
while (dist--) {
|
||||
int src = (dst + 1) % window;
|
||||
array[dst] = array[src];
|
||||
dst = src;
|
||||
}
|
||||
array[dst] = swap;
|
||||
}
|
||||
|
||||
next:
|
||||
idx++;
|
||||
if (count + 1 < window)
|
||||
|
@ -1531,9 +1581,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
idx = 0;
|
||||
} while (i > 0);
|
||||
|
||||
if (progress)
|
||||
stop_progress(&progress_state);
|
||||
|
||||
for (i = 0; i < window; ++i) {
|
||||
free_delta_index(array[i].index);
|
||||
free(array[i].data);
|
||||
|
@ -1541,21 +1588,145 @@ static void find_deltas(struct object_entry **list, int window, int depth)
|
|||
free(array);
|
||||
}
|
||||
|
||||
#ifdef THREADED_DELTA_SEARCH
|
||||
|
||||
struct thread_params {
|
||||
pthread_t thread;
|
||||
struct object_entry **list;
|
||||
unsigned list_size;
|
||||
int window;
|
||||
int depth;
|
||||
unsigned *processed;
|
||||
};
|
||||
|
||||
static pthread_mutex_t data_request = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t data_ready = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
|
||||
static struct thread_params *data_requester;
|
||||
|
||||
static void *threaded_find_deltas(void *arg)
|
||||
{
|
||||
struct thread_params *me = arg;
|
||||
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&data_request);
|
||||
data_requester = me;
|
||||
pthread_mutex_unlock(&data_provider);
|
||||
pthread_mutex_lock(&data_ready);
|
||||
pthread_mutex_unlock(&data_request);
|
||||
|
||||
if (!me->list_size)
|
||||
return NULL;
|
||||
|
||||
find_deltas(me->list, me->list_size,
|
||||
me->window, me->depth, me->processed);
|
||||
}
|
||||
}
|
||||
|
||||
static void ll_find_deltas(struct object_entry **list, unsigned list_size,
|
||||
int window, int depth, unsigned *processed)
|
||||
{
|
||||
struct thread_params *target, p[delta_search_threads];
|
||||
int i, ret;
|
||||
unsigned chunk_size;
|
||||
|
||||
if (delta_search_threads <= 1) {
|
||||
find_deltas(list, list_size, window, depth, processed);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&data_provider);
|
||||
pthread_mutex_lock(&data_ready);
|
||||
|
||||
for (i = 0; i < delta_search_threads; i++) {
|
||||
p[i].window = window;
|
||||
p[i].depth = depth;
|
||||
p[i].processed = processed;
|
||||
ret = pthread_create(&p[i].thread, NULL,
|
||||
threaded_find_deltas, &p[i]);
|
||||
if (ret)
|
||||
die("unable to create thread: %s", strerror(ret));
|
||||
}
|
||||
|
||||
/* this should be auto-tuned somehow */
|
||||
chunk_size = window * 1000;
|
||||
|
||||
do {
|
||||
unsigned sublist_size = chunk_size;
|
||||
if (sublist_size > list_size)
|
||||
sublist_size = list_size;
|
||||
|
||||
/* try to split chunks on "path" boundaries */
|
||||
while (sublist_size < list_size && list[sublist_size]->hash &&
|
||||
list[sublist_size]->hash == list[sublist_size-1]->hash)
|
||||
sublist_size++;
|
||||
|
||||
pthread_mutex_lock(&data_provider);
|
||||
target = data_requester;
|
||||
target->list = list;
|
||||
target->list_size = sublist_size;
|
||||
pthread_mutex_unlock(&data_ready);
|
||||
|
||||
list += sublist_size;
|
||||
list_size -= sublist_size;
|
||||
if (!sublist_size) {
|
||||
pthread_join(target->thread, NULL);
|
||||
i--;
|
||||
}
|
||||
} while (i);
|
||||
}
|
||||
|
||||
#else
|
||||
#define ll_find_deltas find_deltas
|
||||
#endif
|
||||
|
||||
static void prepare_pack(int window, int depth)
|
||||
{
|
||||
struct object_entry **delta_list;
|
||||
uint32_t i;
|
||||
uint32_t i, n, nr_deltas;
|
||||
|
||||
get_object_details();
|
||||
|
||||
if (!window || !depth)
|
||||
if (!nr_objects || !window || !depth)
|
||||
return;
|
||||
|
||||
delta_list = xmalloc(nr_objects * sizeof(*delta_list));
|
||||
for (i = 0; i < nr_objects; i++)
|
||||
delta_list[i] = objects + i;
|
||||
qsort(delta_list, nr_objects, sizeof(*delta_list), type_size_sort);
|
||||
find_deltas(delta_list, window+1, depth);
|
||||
nr_deltas = n = 0;
|
||||
|
||||
for (i = 0; i < nr_objects; i++) {
|
||||
struct object_entry *entry = objects + i;
|
||||
|
||||
if (entry->delta)
|
||||
/* This happens if we decided to reuse existing
|
||||
* delta from a pack. "!no_reuse_delta &&" is implied.
|
||||
*/
|
||||
continue;
|
||||
|
||||
if (entry->size < 50)
|
||||
continue;
|
||||
|
||||
if (entry->no_try_delta)
|
||||
continue;
|
||||
|
||||
if (!entry->preferred_base)
|
||||
nr_deltas++;
|
||||
|
||||
delta_list[n++] = entry;
|
||||
}
|
||||
|
||||
if (nr_deltas) {
|
||||
unsigned nr_done = 0;
|
||||
if (progress)
|
||||
start_progress(&progress_state,
|
||||
"Deltifying %u objects...", "",
|
||||
nr_deltas);
|
||||
qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
|
||||
ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
|
||||
if (progress)
|
||||
stop_progress(&progress_state);
|
||||
if (nr_done != nr_deltas)
|
||||
die("inconsistency with delta count");
|
||||
}
|
||||
free(delta_list);
|
||||
}
|
||||
|
||||
|
@ -1591,6 +1762,17 @@ static int git_pack_config(const char *k, const char *v)
|
|||
cache_max_small_delta_size = git_config_int(k, v);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(k, "pack.threads")) {
|
||||
delta_search_threads = git_config_int(k, v);
|
||||
if (delta_search_threads < 1)
|
||||
die("invalid number of threads specified (%d)",
|
||||
delta_search_threads);
|
||||
#ifndef THREADED_DELTA_SEARCH
|
||||
if (delta_search_threads > 1)
|
||||
warning("no threads support, ignoring %s", k);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
return git_default_config(k, v);
|
||||
}
|
||||
|
||||
|
@ -1750,6 +1932,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
|
|||
usage(pack_usage);
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--threads=")) {
|
||||
char *end;
|
||||
delta_search_threads = strtoul(arg+10, &end, 0);
|
||||
if (!arg[10] || *end || delta_search_threads < 1)
|
||||
usage(pack_usage);
|
||||
#ifndef THREADED_DELTA_SEARCH
|
||||
if (delta_search_threads > 1)
|
||||
warning("no threads support, "
|
||||
"ignoring %s", arg);
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
if (!prefixcmp(arg, "--depth=")) {
|
||||
char *end;
|
||||
depth = strtoul(arg+8, &end, 0);
|
||||
|
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* "git reset" builtin command
|
||||
*
|
||||
* Copyright (c) 2007 Carlos Rica
|
||||
*
|
||||
* Based on git-reset.sh, which is
|
||||
*
|
||||
* Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
|
||||
*/
|
||||
#include "cache.h"
|
||||
#include "tag.h"
|
||||
#include "object.h"
|
||||
#include "commit.h"
|
||||
#include "run-command.h"
|
||||
#include "refs.h"
|
||||
#include "diff.h"
|
||||
#include "diffcore.h"
|
||||
#include "tree.h"
|
||||
|
||||
static const char builtin_reset_usage[] =
|
||||
"git-reset [--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]";
|
||||
|
||||
static char *args_to_str(const char **argv)
|
||||
{
|
||||
char *buf = NULL;
|
||||
unsigned long len, space = 0, nr = 0;
|
||||
|
||||
for (; *argv; argv++) {
|
||||
len = strlen(*argv);
|
||||
ALLOC_GROW(buf, nr + 1 + len, space);
|
||||
if (nr)
|
||||
buf[nr++] = ' ';
|
||||
memcpy(buf + nr, *argv, len);
|
||||
nr += len;
|
||||
}
|
||||
ALLOC_GROW(buf, nr + 1, space);
|
||||
buf[nr] = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline int is_merge(void)
|
||||
{
|
||||
return !access(git_path("MERGE_HEAD"), F_OK);
|
||||
}
|
||||
|
||||
static int unmerged_files(void)
|
||||
{
|
||||
char b;
|
||||
ssize_t len;
|
||||
struct child_process cmd;
|
||||
const char *argv_ls_files[] = {"ls-files", "--unmerged", NULL};
|
||||
|
||||
memset(&cmd, 0, sizeof(cmd));
|
||||
cmd.argv = argv_ls_files;
|
||||
cmd.git_cmd = 1;
|
||||
cmd.out = -1;
|
||||
|
||||
if (start_command(&cmd))
|
||||
die("Could not run sub-command: git ls-files");
|
||||
|
||||
len = xread(cmd.out, &b, 1);
|
||||
if (len < 0)
|
||||
die("Could not read output from git ls-files: %s",
|
||||
strerror(errno));
|
||||
finish_command(&cmd);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
|
||||
{
|
||||
int i = 0;
|
||||
const char *args[6];
|
||||
|
||||
args[i++] = "read-tree";
|
||||
args[i++] = "-v";
|
||||
args[i++] = "--reset";
|
||||
if (is_hard_reset)
|
||||
args[i++] = "-u";
|
||||
args[i++] = sha1_to_hex(sha1);
|
||||
args[i] = NULL;
|
||||
|
||||
return run_command_v_opt(args, RUN_GIT_CMD);
|
||||
}
|
||||
|
||||
static void print_new_head_line(struct commit *commit)
|
||||
{
|
||||
const char *hex, *dots = "...", *body;
|
||||
|
||||
hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
|
||||
if (!hex) {
|
||||
hex = sha1_to_hex(commit->object.sha1);
|
||||
dots = "";
|
||||
}
|
||||
printf("HEAD is now at %s%s", hex, dots);
|
||||
body = strstr(commit->buffer, "\n\n");
|
||||
if (body) {
|
||||
const char *eol;
|
||||
size_t len;
|
||||
body += 2;
|
||||
eol = strchr(body, '\n');
|
||||
len = eol ? eol - body : strlen(body);
|
||||
printf(" %.*s\n", (int) len, body);
|
||||
}
|
||||
else
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static int update_index_refresh(void)
|
||||
{
|
||||
const char *argv_update_index[] = {"update-index", "--refresh", NULL};
|
||||
return run_command_v_opt(argv_update_index, RUN_GIT_CMD);
|
||||
}
|
||||
|
||||
static void update_index_from_diff(struct diff_queue_struct *q,
|
||||
struct diff_options *opt, void *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* do_diff_cache() mangled the index */
|
||||
discard_cache();
|
||||
read_cache();
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
struct diff_filespec *one = q->queue[i]->one;
|
||||
if (one->mode) {
|
||||
struct cache_entry *ce;
|
||||
ce = make_cache_entry(one->mode, one->sha1, one->path,
|
||||
0, 0);
|
||||
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
|
||||
ADD_CACHE_OK_TO_REPLACE);
|
||||
} else
|
||||
remove_file_from_cache(one->path);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_from_tree(const char *prefix, const char **argv,
|
||||
unsigned char *tree_sha1)
|
||||
{
|
||||
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
|
||||
int index_fd;
|
||||
struct diff_options opt;
|
||||
|
||||
memset(&opt, 0, sizeof(opt));
|
||||
diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
|
||||
opt.output_format = DIFF_FORMAT_CALLBACK;
|
||||
opt.format_callback = update_index_from_diff;
|
||||
|
||||
index_fd = hold_locked_index(lock, 1);
|
||||
read_cache();
|
||||
if (do_diff_cache(tree_sha1, &opt))
|
||||
return 1;
|
||||
diffcore_std(&opt);
|
||||
diff_flush(&opt);
|
||||
return write_cache(index_fd, active_cache, active_nr) ||
|
||||
close(index_fd) ||
|
||||
commit_locked_index(lock);
|
||||
}
|
||||
|
||||
static void prepend_reflog_action(const char *action, char *buf, size_t size)
|
||||
{
|
||||
const char *sep = ": ";
|
||||
const char *rla = getenv("GIT_REFLOG_ACTION");
|
||||
if (!rla)
|
||||
rla = sep = "";
|
||||
if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
|
||||
warning("Reflog action message too long: %.*s...", 50, buf);
|
||||
}
|
||||
|
||||
enum reset_type { MIXED, SOFT, HARD, NONE };
|
||||
static char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
|
||||
|
||||
int cmd_reset(int argc, const char **argv, const char *prefix)
|
||||
{
|
||||
int i = 1, reset_type = NONE, update_ref_status = 0;
|
||||
const char *rev = "HEAD";
|
||||
unsigned char sha1[20], *orig = NULL, sha1_orig[20],
|
||||
*old_orig = NULL, sha1_old_orig[20];
|
||||
struct commit *commit;
|
||||
char *reflog_action, msg[1024];
|
||||
|
||||
git_config(git_default_config);
|
||||
|
||||
reflog_action = args_to_str(argv);
|
||||
setenv("GIT_REFLOG_ACTION", reflog_action, 0);
|
||||
|
||||
if (i < argc) {
|
||||
if (!strcmp(argv[i], "--mixed")) {
|
||||
reset_type = MIXED;
|
||||
i++;
|
||||
}
|
||||
else if (!strcmp(argv[i], "--soft")) {
|
||||
reset_type = SOFT;
|
||||
i++;
|
||||
}
|
||||
else if (!strcmp(argv[i], "--hard")) {
|
||||
reset_type = HARD;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i < argc && argv[i][0] != '-')
|
||||
rev = argv[i++];
|
||||
|
||||
if (get_sha1(rev, sha1))
|
||||
die("Failed to resolve '%s' as a valid ref.", rev);
|
||||
|
||||
commit = lookup_commit_reference(sha1);
|
||||
if (!commit)
|
||||
die("Could not parse object '%s'.", rev);
|
||||
hashcpy(sha1, commit->object.sha1);
|
||||
|
||||
if (i < argc && !strcmp(argv[i], "--"))
|
||||
i++;
|
||||
else if (i < argc && argv[i][0] == '-')
|
||||
usage(builtin_reset_usage);
|
||||
|
||||
/* git reset tree [--] paths... can be used to
|
||||
* load chosen paths from the tree into the index without
|
||||
* affecting the working tree nor HEAD. */
|
||||
if (i < argc) {
|
||||
if (reset_type == MIXED)
|
||||
warning("--mixed option is deprecated with paths.");
|
||||
else if (reset_type != NONE)
|
||||
die("Cannot do %s reset with paths.",
|
||||
reset_type_names[reset_type]);
|
||||
if (read_from_tree(prefix, argv + i, sha1))
|
||||
return 1;
|
||||
return update_index_refresh() ? 1 : 0;
|
||||
}
|
||||
if (reset_type == NONE)
|
||||
reset_type = MIXED; /* by default */
|
||||
|
||||
/* Soft reset does not touch the index file nor the working tree
|
||||
* at all, but requires them in a good order. Other resets reset
|
||||
* the index file to the tree object we are switching to. */
|
||||
if (reset_type == SOFT) {
|
||||
if (is_merge() || unmerged_files())
|
||||
die("Cannot do a soft reset in the middle of a merge.");
|
||||
}
|
||||
else if (reset_index_file(sha1, (reset_type == HARD)))
|
||||
die("Could not reset index file to revision '%s'.", rev);
|
||||
|
||||
/* Any resets update HEAD to the head being switched to,
|
||||
* saving the previous head in ORIG_HEAD before. */
|
||||
if (!get_sha1("ORIG_HEAD", sha1_old_orig))
|
||||
old_orig = sha1_old_orig;
|
||||
if (!get_sha1("HEAD", sha1_orig)) {
|
||||
orig = sha1_orig;
|
||||
prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
|
||||
update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
|
||||
}
|
||||
else if (old_orig)
|
||||
delete_ref("ORIG_HEAD", old_orig);
|
||||
prepend_reflog_action("updating HEAD", msg, sizeof(msg));
|
||||
update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
|
||||
|
||||
switch (reset_type) {
|
||||
case HARD:
|
||||
if (!update_ref_status)
|
||||
print_new_head_line(commit);
|
||||
break;
|
||||
case SOFT: /* Nothing else to do. */
|
||||
break;
|
||||
case MIXED: /* Report what has not been updated. */
|
||||
update_index_refresh();
|
||||
break;
|
||||
}
|
||||
|
||||
unlink(git_path("MERGE_HEAD"));
|
||||
unlink(git_path("rr-cache/MERGE_RR"));
|
||||
unlink(git_path("MERGE_MSG"));
|
||||
unlink(git_path("SQUASH_MSG"));
|
||||
|
||||
free(reflog_action);
|
||||
|
||||
return update_ref_status;
|
||||
}
|
|
@ -188,7 +188,7 @@ static int count_interesting_parents(struct commit *commit)
|
|||
return count;
|
||||
}
|
||||
|
||||
static inline int halfway(struct commit_list *p, int distance, int nr)
|
||||
static inline int halfway(struct commit_list *p, int nr)
|
||||
{
|
||||
/*
|
||||
* Don't short-cut something we are not going to return!
|
||||
|
@ -201,8 +201,7 @@ static inline int halfway(struct commit_list *p, int distance, int nr)
|
|||
* 2 and 3 are halfway of 5.
|
||||
* 3 is halfway of 6 but 2 and 4 are not.
|
||||
*/
|
||||
distance *= 2;
|
||||
switch (distance - nr) {
|
||||
switch (2 * weight(p) - nr) {
|
||||
case -1: case 0: case 1:
|
||||
return 1;
|
||||
default:
|
||||
|
@ -254,6 +253,30 @@ static void show_list(const char *debug, int counted, int nr,
|
|||
}
|
||||
#endif /* DEBUG_BISECT */
|
||||
|
||||
static struct commit_list *best_bisection(struct commit_list *list, int nr)
|
||||
{
|
||||
struct commit_list *p, *best;
|
||||
int best_distance = -1;
|
||||
|
||||
best = list;
|
||||
for (p = list; p; p = p->next) {
|
||||
int distance;
|
||||
unsigned flags = p->item->object.flags;
|
||||
|
||||
if (revs.prune_fn && !(flags & TREECHANGE))
|
||||
continue;
|
||||
distance = weight(p);
|
||||
if (nr - distance < distance)
|
||||
distance = nr - distance;
|
||||
if (distance > best_distance) {
|
||||
best = p;
|
||||
best_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/*
|
||||
* zero or positive weight is the number of interesting commits it can
|
||||
* reach, including itself. Especially, weight = 0 means it does not
|
||||
|
@ -267,39 +290,12 @@ static void show_list(const char *debug, int counted, int nr,
|
|||
* unknown. After running count_distance() first, they will get zero
|
||||
* or positive distance.
|
||||
*/
|
||||
|
||||
static struct commit_list *find_bisection(struct commit_list *list,
|
||||
int *reaches, int *all)
|
||||
static struct commit_list *do_find_bisection(struct commit_list *list,
|
||||
int nr, int *weights)
|
||||
{
|
||||
int n, nr, on_list, counted, distance;
|
||||
struct commit_list *p, *best, *next, *last;
|
||||
int *weights;
|
||||
int n, counted;
|
||||
struct commit_list *p;
|
||||
|
||||
show_list("bisection 2 entry", 0, 0, list);
|
||||
|
||||
/*
|
||||
* Count the number of total and tree-changing items on the
|
||||
* list, while reversing the list.
|
||||
*/
|
||||
for (nr = on_list = 0, last = NULL, p = list;
|
||||
p;
|
||||
p = next) {
|
||||
unsigned flags = p->item->object.flags;
|
||||
|
||||
next = p->next;
|
||||
if (flags & UNINTERESTING)
|
||||
continue;
|
||||
p->next = last;
|
||||
last = p;
|
||||
if (!revs.prune_fn || (flags & TREECHANGE))
|
||||
nr++;
|
||||
on_list++;
|
||||
}
|
||||
list = last;
|
||||
show_list("bisection 2 sorted", 0, nr, list);
|
||||
|
||||
*all = nr;
|
||||
weights = xcalloc(on_list, sizeof(*weights));
|
||||
counted = 0;
|
||||
|
||||
for (n = 0, p = list; p; p = p->next) {
|
||||
|
@ -348,20 +344,14 @@ static struct commit_list *find_bisection(struct commit_list *list,
|
|||
for (p = list; p; p = p->next) {
|
||||
if (p->item->object.flags & UNINTERESTING)
|
||||
continue;
|
||||
n = weight(p);
|
||||
if (n != -2)
|
||||
if (weight(p) != -2)
|
||||
continue;
|
||||
distance = count_distance(p);
|
||||
weight_set(p, count_distance(p));
|
||||
clear_distance(list);
|
||||
weight_set(p, distance);
|
||||
|
||||
/* Does it happen to be at exactly half-way? */
|
||||
if (halfway(p, distance, nr)) {
|
||||
p->next = NULL;
|
||||
*reaches = distance;
|
||||
free(weights);
|
||||
if (halfway(p, nr))
|
||||
return p;
|
||||
}
|
||||
counted++;
|
||||
}
|
||||
|
||||
|
@ -398,38 +388,59 @@ static struct commit_list *find_bisection(struct commit_list *list,
|
|||
weight_set(p, weight(q));
|
||||
|
||||
/* Does it happen to be at exactly half-way? */
|
||||
distance = weight(p);
|
||||
if (halfway(p, distance, nr)) {
|
||||
p->next = NULL;
|
||||
*reaches = distance;
|
||||
free(weights);
|
||||
if (halfway(p, nr))
|
||||
return p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
show_list("bisection 2 counted all", counted, nr, list);
|
||||
|
||||
/* Then find the best one */
|
||||
counted = -1;
|
||||
best = list;
|
||||
for (p = list; p; p = p->next) {
|
||||
return best_bisection(list, nr);
|
||||
}
|
||||
|
||||
static struct commit_list *find_bisection(struct commit_list *list,
|
||||
int *reaches, int *all)
|
||||
{
|
||||
int nr, on_list;
|
||||
struct commit_list *p, *best, *next, *last;
|
||||
int *weights;
|
||||
|
||||
show_list("bisection 2 entry", 0, 0, list);
|
||||
|
||||
/*
|
||||
* Count the number of total and tree-changing items on the
|
||||
* list, while reversing the list.
|
||||
*/
|
||||
for (nr = on_list = 0, last = NULL, p = list;
|
||||
p;
|
||||
p = next) {
|
||||
unsigned flags = p->item->object.flags;
|
||||
|
||||
if (revs.prune_fn && !(flags & TREECHANGE))
|
||||
next = p->next;
|
||||
if (flags & UNINTERESTING)
|
||||
continue;
|
||||
distance = weight(p);
|
||||
if (nr - distance < distance)
|
||||
distance = nr - distance;
|
||||
if (distance > counted) {
|
||||
best = p;
|
||||
counted = distance;
|
||||
*reaches = weight(p);
|
||||
}
|
||||
p->next = last;
|
||||
last = p;
|
||||
if (!revs.prune_fn || (flags & TREECHANGE))
|
||||
nr++;
|
||||
on_list++;
|
||||
}
|
||||
list = last;
|
||||
show_list("bisection 2 sorted", 0, nr, list);
|
||||
|
||||
*all = nr;
|
||||
weights = xcalloc(on_list, sizeof(*weights));
|
||||
|
||||
/* Do the real work of finding bisection commit. */
|
||||
best = do_find_bisection(list, nr, weights);
|
||||
|
||||
if (best)
|
||||
best->next = NULL;
|
||||
|
||||
*reaches = weight(best);
|
||||
free(weights);
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
|
|||
|
||||
if (remove_file_from_cache(path))
|
||||
die("git-rm: unable to remove %s", path);
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
}
|
||||
|
||||
if (show_only)
|
||||
|
|
|
@ -194,11 +194,6 @@ static int process_path(const char *path)
|
|||
int len;
|
||||
struct stat st;
|
||||
|
||||
/* We probably want to do this in remove_file_from_cache() and
|
||||
* add_cache_entry() instead...
|
||||
*/
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
|
||||
/*
|
||||
* First things first: get the stat information, to decide
|
||||
* what to do about the pathname!
|
||||
|
@ -238,7 +233,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
|
|||
return error("%s: cannot add to the index - missing --add option?",
|
||||
path);
|
||||
report("add '%s'", path);
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -283,7 +277,6 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
|
|||
die("Unable to mark file %s", path);
|
||||
goto free_return;
|
||||
}
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
|
||||
if (force_remove) {
|
||||
if (remove_file_from_cache(p))
|
||||
|
@ -365,7 +358,6 @@ static void read_index_info(int line_termination)
|
|||
free(path_name);
|
||||
continue;
|
||||
}
|
||||
cache_tree_invalidate_path(active_cache_tree, path_name);
|
||||
|
||||
if (!mode) {
|
||||
/* mode == 0 means there is no such path -- remove */
|
||||
|
@ -473,7 +465,6 @@ static int unresolve_one(const char *path)
|
|||
goto free_return;
|
||||
}
|
||||
|
||||
cache_tree_invalidate_path(active_cache_tree, path);
|
||||
remove_file_from_cache(path);
|
||||
if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
|
||||
error("%s: cannot add our version to the index.", path);
|
||||
|
|
|
@ -35,7 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
|
|||
|
||||
/* find the length without signature */
|
||||
len = 0;
|
||||
while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) {
|
||||
while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
|
||||
eol = memchr(buf + len, '\n', size - len);
|
||||
len += eol ? eol - (buf + len) + 1 : size - len;
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
|
|||
extern int cmd_reflog(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_config(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rerere(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_reset(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
|
||||
extern int cmd_revert(int argc, const char **argv, const char *prefix);
|
||||
|
|
1
cache.h
1
cache.h
|
@ -265,6 +265,7 @@ extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int reall
|
|||
extern int remove_index_entry_at(struct index_state *, int pos);
|
||||
extern int remove_file_from_index(struct index_state *, const char *path);
|
||||
extern int add_file_to_index(struct index_state *, const char *path, int verbose);
|
||||
extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
|
||||
extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
|
||||
extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, int);
|
||||
extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
|
||||
|
|
|
@ -97,6 +97,21 @@ if there is already one that displays the same directory."
|
|||
:group 'git
|
||||
:type 'string)
|
||||
|
||||
(defcustom git-show-uptodate nil
|
||||
"Whether to display up-to-date files."
|
||||
:group 'git
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom git-show-ignored nil
|
||||
"Whether to display ignored files."
|
||||
:group 'git
|
||||
:type 'boolean)
|
||||
|
||||
(defcustom git-show-unknown t
|
||||
"Whether to display unknown files."
|
||||
:group 'git
|
||||
:type 'boolean)
|
||||
|
||||
|
||||
(defface git-status-face
|
||||
'((((class color) (background light)) (:foreground "purple"))
|
||||
|
@ -479,6 +494,27 @@ and returns the process output as a string."
|
|||
(setf (git-fileinfo->orig-name info) nil)
|
||||
(setf (git-fileinfo->needs-refresh info) t))))
|
||||
|
||||
(defun git-set-filenames-state (status files state)
|
||||
"Set the state of a list of named files."
|
||||
(when files
|
||||
(setq files (sort files #'string-lessp))
|
||||
(let ((file (pop files))
|
||||
(node (ewoc-nth status 0)))
|
||||
(while (and file node)
|
||||
(let ((info (ewoc-data node)))
|
||||
(cond ((string-lessp (git-fileinfo->name info) file)
|
||||
(setq node (ewoc-next status node)))
|
||||
((string-equal (git-fileinfo->name info) file)
|
||||
(unless (eq (git-fileinfo->state info) state)
|
||||
(setf (git-fileinfo->state info) state)
|
||||
(setf (git-fileinfo->rename-state info) nil)
|
||||
(setf (git-fileinfo->orig-name info) nil)
|
||||
(setf (git-fileinfo->needs-refresh info) t))
|
||||
(setq file (pop files)))
|
||||
(t (setq file (pop files)))))))
|
||||
(unless state ;; delete files whose state has been set to nil
|
||||
(ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
|
||||
|
||||
(defun git-state-code (code)
|
||||
"Convert from a string to a added/deleted/modified state."
|
||||
(case (string-to-char code)
|
||||
|
@ -532,19 +568,36 @@ and returns the process output as a string."
|
|||
" " (git-escape-file-name (git-fileinfo->name info))
|
||||
(git-rename-as-string info))))
|
||||
|
||||
(defun git-insert-fileinfo (status info &optional refresh)
|
||||
"Insert INFO in the status buffer, optionally refreshing an existing one."
|
||||
(let ((node (and refresh
|
||||
(git-find-status-file status (git-fileinfo->name info)))))
|
||||
(defun git-insert-info-list (status infolist)
|
||||
"Insert a list of file infos in the status buffer, replacing existing ones if any."
|
||||
(setq infolist (sort infolist
|
||||
(lambda (info1 info2)
|
||||
(string-lessp (git-fileinfo->name info1)
|
||||
(git-fileinfo->name info2)))))
|
||||
(let ((info (pop infolist))
|
||||
(node (ewoc-nth status 0)))
|
||||
(while info
|
||||
(setf (git-fileinfo->needs-refresh info) t)
|
||||
(when node ;preserve the marked flag
|
||||
(setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))))
|
||||
(if node (setf (ewoc-data node) info) (ewoc-enter-last status info))))
|
||||
(cond ((not node)
|
||||
(ewoc-enter-last status info)
|
||||
(setq info (pop infolist)))
|
||||
((string-lessp (git-fileinfo->name (ewoc-data node))
|
||||
(git-fileinfo->name info))
|
||||
(setq node (ewoc-next status node)))
|
||||
((string-equal (git-fileinfo->name (ewoc-data node))
|
||||
(git-fileinfo->name info))
|
||||
;; preserve the marked flag
|
||||
(setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node)))
|
||||
(setf (ewoc-data node) info)
|
||||
(setq info (pop infolist)))
|
||||
(t
|
||||
(ewoc-enter-before status node info)
|
||||
(setq info (pop infolist)))))))
|
||||
|
||||
(defun git-run-diff-index (status files)
|
||||
"Run git-diff-index on FILES and parse the results into STATUS.
|
||||
Return the list of files that haven't been handled."
|
||||
(let ((refresh files))
|
||||
(let (infolist)
|
||||
(with-temp-buffer
|
||||
(apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files)
|
||||
(goto-char (point-min))
|
||||
|
@ -558,13 +611,14 @@ Return the list of files that haven't been handled."
|
|||
(new-name (match-string 8)))
|
||||
(if new-name ; copy or rename
|
||||
(if (eq ?C (string-to-char state))
|
||||
(git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh)
|
||||
(git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh)
|
||||
(git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh)
|
||||
(git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh))
|
||||
(push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist)
|
||||
(push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
|
||||
(push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
|
||||
(push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist))
|
||||
(setq files (delete name files))
|
||||
(when new-name (setq files (delete new-name files)))))))
|
||||
files)
|
||||
(when new-name (setq files (delete new-name files))))))
|
||||
(git-insert-info-list status infolist)
|
||||
files))
|
||||
|
||||
(defun git-find-status-file (status file)
|
||||
"Find a given file in the status ewoc and return its node."
|
||||
|
@ -576,16 +630,16 @@ Return the list of files that haven't been handled."
|
|||
(defun git-run-ls-files (status files default-state &rest options)
|
||||
"Run git-ls-files on FILES and parse the results into STATUS.
|
||||
Return the list of files that haven't been handled."
|
||||
(let ((refresh files))
|
||||
(let (infolist)
|
||||
(with-temp-buffer
|
||||
(apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files))
|
||||
(apply #'git-run-command t nil "ls-files" "-z" (append options (list "--") files))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
|
||||
(let ((state (match-string 1))
|
||||
(name (match-string 2)))
|
||||
(git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh)
|
||||
(setq files (delete name files))))))
|
||||
files)
|
||||
(while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
|
||||
(let ((name (match-string 1)))
|
||||
(push (git-create-fileinfo default-state name) infolist)
|
||||
(setq files (delete name files)))))
|
||||
(git-insert-info-list status infolist)
|
||||
files))
|
||||
|
||||
(defun git-run-ls-unmerged (status files)
|
||||
"Run git-ls-files -u on FILES and parse the results into STATUS."
|
||||
|
@ -594,9 +648,8 @@ Return the list of files that haven't been handled."
|
|||
(goto-char (point-min))
|
||||
(let (unmerged-files)
|
||||
(while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
|
||||
(let ((node (git-find-status-file status (match-string 1))))
|
||||
(when node (push (ewoc-data node) unmerged-files))))
|
||||
(git-set-files-state unmerged-files 'unmerged))))
|
||||
(push (match-string 1) unmerged-files))
|
||||
(git-set-filenames-state status unmerged-files 'unmerged))))
|
||||
|
||||
(defun git-get-exclude-files ()
|
||||
"Get the list of exclude files to pass to git-ls-files."
|
||||
|
@ -608,34 +661,30 @@ Return the list of files that haven't been handled."
|
|||
(push config files))
|
||||
files))
|
||||
|
||||
(defun git-run-ls-files-with-excludes (status files default-state &rest options)
|
||||
"Run git-ls-files on FILES with appropriate --exclude-from options."
|
||||
(let ((exclude-files (git-get-exclude-files)))
|
||||
(apply #'git-run-ls-files status files default-state
|
||||
(concat "--exclude-per-directory=" git-per-dir-ignore-file)
|
||||
(append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
|
||||
|
||||
(defun git-update-status-files (files &optional default-state)
|
||||
"Update the status of FILES from the index."
|
||||
(unless git-status (error "Not in git-status buffer."))
|
||||
(let* ((status git-status)
|
||||
(remaining-files
|
||||
(unless files
|
||||
(when git-show-uptodate (git-run-ls-files git-status nil 'uptodate "-c")))
|
||||
(let* ((remaining-files
|
||||
(if (git-empty-db-p) ; we need some special handling for an empty db
|
||||
(git-run-ls-files status files 'added "-c")
|
||||
(git-run-diff-index status files))))
|
||||
(git-run-ls-unmerged status files)
|
||||
(when (or (not files) remaining-files)
|
||||
(let ((exclude-files (git-get-exclude-files)))
|
||||
(setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o"
|
||||
(concat "--exclude-per-directory=" git-per-dir-ignore-file)
|
||||
(mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
|
||||
; mark remaining files with the default state (or remove them if nil)
|
||||
(when remaining-files
|
||||
(if default-state
|
||||
(ewoc-map (lambda (info)
|
||||
(when (member (git-fileinfo->name info) remaining-files)
|
||||
(git-set-files-state (list info) default-state))
|
||||
nil)
|
||||
status)
|
||||
(ewoc-filter status
|
||||
(lambda (info files)
|
||||
(not (member (git-fileinfo->name info) files)))
|
||||
remaining-files)))
|
||||
(git-run-ls-files git-status files 'added "-c")
|
||||
(git-run-diff-index git-status files))))
|
||||
(git-run-ls-unmerged git-status files)
|
||||
(when (or remaining-files (and git-show-unknown (not files)))
|
||||
(setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o")))
|
||||
(when (or remaining-files (and git-show-ignored (not files)))
|
||||
(setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i")))
|
||||
(git-set-filenames-state git-status remaining-files default-state)
|
||||
(git-refresh-files)
|
||||
(git-refresh-ewoc-hf status)))
|
||||
(git-refresh-ewoc-hf git-status)))
|
||||
|
||||
(defun git-marked-files ()
|
||||
"Return a list of all marked files, or if none a list containing just the file at cursor position."
|
||||
|
@ -853,7 +902,7 @@ Return the list of files that haven't been handled."
|
|||
(defun git-add-file ()
|
||||
"Add marked file(s) to the index cache."
|
||||
(interactive)
|
||||
(let ((files (git-get-filenames (git-marked-files-state 'unknown))))
|
||||
(let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored))))
|
||||
(unless files
|
||||
(push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
|
||||
(apply #'git-run-command nil nil "update-index" "--add" "--" files)
|
||||
|
@ -871,7 +920,7 @@ Return the list of files that haven't been handled."
|
|||
(defun git-remove-file ()
|
||||
"Remove the marked file(s)."
|
||||
(interactive)
|
||||
(let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate))))
|
||||
(let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored))))
|
||||
(unless files
|
||||
(push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
|
||||
(if (yes-or-no-p
|
||||
|
@ -916,11 +965,41 @@ Return the list of files that haven't been handled."
|
|||
(interactive)
|
||||
(ewoc-filter git-status
|
||||
(lambda (info)
|
||||
(not (or (eq (git-fileinfo->state info) 'ignored)
|
||||
(eq (git-fileinfo->state info) 'uptodate)))))
|
||||
(case (git-fileinfo->state info)
|
||||
('ignored git-show-ignored)
|
||||
('uptodate git-show-uptodate)
|
||||
('unknown git-show-unknown)
|
||||
(t t))))
|
||||
(unless (ewoc-nth git-status 0) ; refresh header if list is empty
|
||||
(git-refresh-ewoc-hf git-status)))
|
||||
|
||||
(defun git-toggle-show-uptodate ()
|
||||
"Toogle the option for showing up-to-date files."
|
||||
(interactive)
|
||||
(if (setq git-show-uptodate (not git-show-uptodate))
|
||||
(git-refresh-status)
|
||||
(git-remove-handled)))
|
||||
|
||||
(defun git-toggle-show-ignored ()
|
||||
"Toogle the option for showing ignored files."
|
||||
(interactive)
|
||||
(if (setq git-show-ignored (not git-show-ignored))
|
||||
(progn
|
||||
(git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i")
|
||||
(git-refresh-files)
|
||||
(git-refresh-ewoc-hf git-status))
|
||||
(git-remove-handled)))
|
||||
|
||||
(defun git-toggle-show-unknown ()
|
||||
"Toogle the option for showing unknown files."
|
||||
(interactive)
|
||||
(if (setq git-show-unknown (not git-show-unknown))
|
||||
(progn
|
||||
(git-run-ls-files-with-excludes git-status nil 'unknown "-o")
|
||||
(git-refresh-files)
|
||||
(git-refresh-ewoc-hf git-status))
|
||||
(git-remove-handled)))
|
||||
|
||||
(defun git-setup-diff-buffer (buffer)
|
||||
"Setup a buffer for displaying a diff."
|
||||
(let ((dir default-directory))
|
||||
|
@ -1146,7 +1225,8 @@ Return the list of files that haven't been handled."
|
|||
|
||||
(unless git-status-mode-map
|
||||
(let ((map (make-keymap))
|
||||
(diff-map (make-sparse-keymap)))
|
||||
(diff-map (make-sparse-keymap))
|
||||
(toggle-map (make-sparse-keymap)))
|
||||
(suppress-keymap map)
|
||||
(define-key map "?" 'git-help)
|
||||
(define-key map "h" 'git-help)
|
||||
|
@ -1170,6 +1250,7 @@ Return the list of files that haven't been handled."
|
|||
(define-key map "q" 'git-status-quit)
|
||||
(define-key map "r" 'git-remove-file)
|
||||
(define-key map "R" 'git-resolve-file)
|
||||
(define-key map "t" toggle-map)
|
||||
(define-key map "T" 'git-toggle-all-marks)
|
||||
(define-key map "u" 'git-unmark-file)
|
||||
(define-key map "U" 'git-revert-file)
|
||||
|
@ -1186,6 +1267,11 @@ Return the list of files that haven't been handled."
|
|||
(define-key diff-map "h" 'git-diff-file-merge-head)
|
||||
(define-key diff-map "m" 'git-diff-file-mine)
|
||||
(define-key diff-map "o" 'git-diff-file-other)
|
||||
; the toggle submap
|
||||
(define-key toggle-map "u" 'git-toggle-show-uptodate)
|
||||
(define-key toggle-map "i" 'git-toggle-show-ignored)
|
||||
(define-key toggle-map "k" 'git-toggle-show-unknown)
|
||||
(define-key toggle-map "m" 'git-toggle-all-marks)
|
||||
(setq git-status-mode-map map)))
|
||||
|
||||
;; git mode should only run in the *git status* buffer
|
||||
|
@ -1207,6 +1293,9 @@ Commands:
|
|||
(let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
|
||||
(set (make-local-variable 'git-status) status))
|
||||
(set (make-local-variable 'list-buffers-directory) default-directory)
|
||||
(make-local-variable 'git-show-uptodate)
|
||||
(make-local-variable 'git-show-ignored)
|
||||
(make-local-variable 'git-show-unknown)
|
||||
(run-hooks 'git-status-mode-hook)))
|
||||
|
||||
(defun git-find-status-buffer (dir)
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
#!/usr/bin/perl
|
||||
#
|
||||
# Performs an initial import of a directory. This is the equivalent
|
||||
# of doing 'git init; git add .; git commit'. It's a little slower,
|
||||
# but is meant to be a simple fast-import example.
|
||||
|
||||
use strict;
|
||||
use File::Find;
|
||||
|
||||
my $USAGE = 'Usage: git-import branch import-message';
|
||||
my $branch = shift or die "$USAGE\n";
|
||||
my $message = shift or die "$USAGE\n";
|
||||
|
||||
chomp(my $username = `git config user.name`);
|
||||
chomp(my $email = `git config user.email`);
|
||||
die 'You need to set user name and email'
|
||||
unless $username && $email;
|
||||
|
||||
system('git init');
|
||||
open(my $fi, '|-', qw(git fast-import --date-format=now))
|
||||
or die "unable to spawn fast-import: $!";
|
||||
|
||||
print $fi <<EOF;
|
||||
commit refs/heads/$branch
|
||||
committer $username <$email> now
|
||||
data <<MSGEOF
|
||||
$message
|
||||
MSGEOF
|
||||
|
||||
EOF
|
||||
|
||||
find(
|
||||
sub {
|
||||
if($File::Find::name eq './.git') {
|
||||
$File::Find::prune = 1;
|
||||
return;
|
||||
}
|
||||
return unless -f $_;
|
||||
|
||||
my $fn = $File::Find::name;
|
||||
$fn =~ s#^.\/##;
|
||||
|
||||
open(my $in, '<', $_)
|
||||
or die "unable to open $fn: $!";
|
||||
my @st = stat($in)
|
||||
or die "unable to stat $fn: $!";
|
||||
my $len = $st[7];
|
||||
|
||||
print $fi "M 644 inline $fn\n";
|
||||
print $fi "data $len\n";
|
||||
while($len > 0) {
|
||||
my $r = read($in, my $buf, $len < 4096 ? $len : 4096);
|
||||
defined($r) or die "read error from $fn: $!";
|
||||
$r > 0 or die "premature EOF from $fn: $!";
|
||||
print $fi $buf;
|
||||
$len -= $r;
|
||||
}
|
||||
print $fi "\n";
|
||||
|
||||
}, '.'
|
||||
);
|
||||
|
||||
close($fi);
|
||||
exit $?;
|
|
@ -0,0 +1,38 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Performs an initial import of a directory. This is the equivalent
|
||||
# of doing 'git init; git add .; git commit'. It's a lot slower,
|
||||
# but is meant to be a simple fast-import example.
|
||||
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
echo "Usage: git-import branch import-message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USERNAME="$(git config user.name)"
|
||||
EMAIL="$(git config user.email)"
|
||||
|
||||
if [ -z "$USERNAME" -o -z "$EMAIL" ]; then
|
||||
echo "You need to set user name and email"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git init
|
||||
|
||||
(
|
||||
cat <<EOF
|
||||
commit refs/heads/$1
|
||||
committer $USERNAME <$EMAIL> now
|
||||
data <<MSGEOF
|
||||
$2
|
||||
MSGEOF
|
||||
|
||||
EOF
|
||||
find * -type f|while read i;do
|
||||
echo "M 100644 inline $i"
|
||||
echo data $(stat -c '%s' "$i")
|
||||
cat "$i"
|
||||
echo
|
||||
done
|
||||
echo
|
||||
) | git fast-import --date-format=now
|
103
diff-delta.c
103
diff-delta.c
|
@ -115,7 +115,11 @@ static const unsigned int U[256] = {
|
|||
struct index_entry {
|
||||
const unsigned char *ptr;
|
||||
unsigned int val;
|
||||
struct index_entry *next;
|
||||
};
|
||||
|
||||
struct unpacked_index_entry {
|
||||
struct index_entry entry;
|
||||
struct unpacked_index_entry *next;
|
||||
};
|
||||
|
||||
struct delta_index {
|
||||
|
@ -131,7 +135,8 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
|
|||
unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
|
||||
const unsigned char *data, *buffer = buf;
|
||||
struct delta_index *index;
|
||||
struct index_entry *entry, **hash;
|
||||
struct unpacked_index_entry *entry, **hash;
|
||||
struct index_entry *packed_entry, **packed_hash;
|
||||
void *mem;
|
||||
unsigned long memsize;
|
||||
|
||||
|
@ -148,28 +153,21 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
|
|||
hmask = hsize - 1;
|
||||
|
||||
/* allocate lookup index */
|
||||
memsize = sizeof(*index) +
|
||||
sizeof(*hash) * hsize +
|
||||
memsize = sizeof(*hash) * hsize +
|
||||
sizeof(*entry) * entries;
|
||||
mem = malloc(memsize);
|
||||
if (!mem)
|
||||
return NULL;
|
||||
index = mem;
|
||||
mem = index + 1;
|
||||
hash = mem;
|
||||
mem = hash + hsize;
|
||||
entry = mem;
|
||||
|
||||
index->memsize = memsize;
|
||||
index->src_buf = buf;
|
||||
index->src_size = bufsize;
|
||||
index->hash_mask = hmask;
|
||||
memset(hash, 0, hsize * sizeof(*hash));
|
||||
|
||||
/* allocate an array to count hash entries */
|
||||
hash_count = calloc(hsize, sizeof(*hash_count));
|
||||
if (!hash_count) {
|
||||
free(index);
|
||||
free(hash);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -183,12 +181,13 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
|
|||
val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
|
||||
if (val == prev_val) {
|
||||
/* keep the lowest of consecutive identical blocks */
|
||||
entry[-1].ptr = data + RABIN_WINDOW;
|
||||
entry[-1].entry.ptr = data + RABIN_WINDOW;
|
||||
--entries;
|
||||
} else {
|
||||
prev_val = val;
|
||||
i = val & hmask;
|
||||
entry->ptr = data + RABIN_WINDOW;
|
||||
entry->val = val;
|
||||
entry->entry.ptr = data + RABIN_WINDOW;
|
||||
entry->entry.val = val;
|
||||
entry->next = hash[i];
|
||||
hash[i] = entry++;
|
||||
hash_count[i]++;
|
||||
|
@ -208,20 +207,84 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
|
|||
* the reference buffer.
|
||||
*/
|
||||
for (i = 0; i < hsize; i++) {
|
||||
if (hash_count[i] < HASH_LIMIT)
|
||||
int acc;
|
||||
|
||||
if (hash_count[i] <= HASH_LIMIT)
|
||||
continue;
|
||||
|
||||
entries -= hash_count[i] - HASH_LIMIT;
|
||||
/* We leave exactly HASH_LIMIT entries in the bucket */
|
||||
|
||||
entry = hash[i];
|
||||
acc = 0;
|
||||
do {
|
||||
struct index_entry *keep = entry;
|
||||
int skip = hash_count[i] / HASH_LIMIT;
|
||||
acc += hash_count[i] - HASH_LIMIT;
|
||||
if (acc > 0) {
|
||||
struct unpacked_index_entry *keep = entry;
|
||||
do {
|
||||
entry = entry->next;
|
||||
} while(--skip && entry);
|
||||
keep->next = entry;
|
||||
acc -= HASH_LIMIT;
|
||||
} while (acc > 0);
|
||||
keep->next = entry->next;
|
||||
}
|
||||
entry = entry->next;
|
||||
} while (entry);
|
||||
|
||||
/* Assume that this loop is gone through exactly
|
||||
* HASH_LIMIT times and is entered and left with
|
||||
* acc==0. So the first statement in the loop
|
||||
* contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT
|
||||
* to the accumulator, and the inner loop consequently
|
||||
* is run (hash_count[i]-HASH_LIMIT) times, removing
|
||||
* one element from the list each time. Since acc
|
||||
* balances out to 0 at the final run, the inner loop
|
||||
* body can't be left with entry==NULL. So we indeed
|
||||
* encounter entry==NULL in the outer loop only.
|
||||
*/
|
||||
}
|
||||
free(hash_count);
|
||||
|
||||
/* Now create the packed index in array form rather than
|
||||
* linked lists */
|
||||
|
||||
memsize = sizeof(*index)
|
||||
+ sizeof(*packed_hash) * (hsize+1)
|
||||
+ sizeof(*packed_entry) * entries;
|
||||
|
||||
mem = malloc(memsize);
|
||||
|
||||
if (!mem) {
|
||||
free(hash);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
index = mem;
|
||||
index->memsize = memsize;
|
||||
index->src_buf = buf;
|
||||
index->src_size = bufsize;
|
||||
index->hash_mask = hmask;
|
||||
|
||||
mem = index + 1;
|
||||
packed_hash = mem;
|
||||
mem = packed_hash + (hsize+1);
|
||||
packed_entry = mem;
|
||||
|
||||
/* Coalesce all entries belonging to one linked list into
|
||||
* consecutive array entries */
|
||||
|
||||
for (i = 0; i < hsize; i++) {
|
||||
packed_hash[i] = packed_entry;
|
||||
for (entry = hash[i]; entry; entry = entry->next)
|
||||
*packed_entry++ = entry->entry;
|
||||
}
|
||||
|
||||
/* Sentinel value to indicate the length of the last hash
|
||||
* bucket */
|
||||
|
||||
packed_hash[hsize] = packed_entry;
|
||||
assert(packed_entry - (struct index_entry *)mem == entries);
|
||||
free(hash);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
@ -302,7 +365,7 @@ create_delta(const struct delta_index *index,
|
|||
val ^= U[data[-RABIN_WINDOW]];
|
||||
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
|
||||
i = val & index->hash_mask;
|
||||
for (entry = index->hash[i]; entry; entry = entry->next) {
|
||||
for (entry = index->hash[i]; entry < index->hash[i+1]; entry++) {
|
||||
const unsigned char *ref = entry->ptr;
|
||||
const unsigned char *src = data;
|
||||
unsigned int ref_size = ref_top - ref;
|
||||
|
|
|
@ -298,6 +298,8 @@ int setup_diff_no_index(struct rev_info *revs,
|
|||
revs->diffopt.nr_paths = 2;
|
||||
revs->diffopt.no_index = 1;
|
||||
revs->max_count = -2;
|
||||
if (diff_setup_done(&revs->diffopt) < 0)
|
||||
die("diff_setup_done failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
2
diff.c
2
diff.c
|
@ -17,7 +17,7 @@
|
|||
#endif
|
||||
|
||||
static int diff_detect_rename_default;
|
||||
static int diff_rename_limit_default = -1;
|
||||
static int diff_rename_limit_default = 100;
|
||||
static int diff_use_color_default;
|
||||
int diff_auto_refresh_index = 1;
|
||||
|
||||
|
|
|
@ -298,10 +298,25 @@ void diffcore_rename(struct diff_options *options)
|
|||
else if (detect_rename == DIFF_DETECT_COPY)
|
||||
register_rename_src(p->one, 1, p->score);
|
||||
}
|
||||
if (rename_dst_nr == 0 || rename_src_nr == 0 ||
|
||||
(0 < rename_limit && rename_limit < rename_dst_nr))
|
||||
if (rename_dst_nr == 0 || rename_src_nr == 0)
|
||||
goto cleanup; /* nothing to do */
|
||||
|
||||
/*
|
||||
* This basically does a test for the rename matrix not
|
||||
* growing larger than a "rename_limit" square matrix, ie:
|
||||
*
|
||||
* rename_dst_nr * rename_src_nr > rename_limit * rename_limit
|
||||
*
|
||||
* but handles the potential overflow case specially (and we
|
||||
* assume at least 32-bit integers)
|
||||
*/
|
||||
if (rename_limit <= 0 || rename_limit > 32767)
|
||||
rename_limit = 32767;
|
||||
if (rename_dst_nr > rename_limit && rename_src_nr > rename_limit)
|
||||
goto cleanup;
|
||||
if (rename_dst_nr * rename_src_nr > rename_limit * rename_limit)
|
||||
goto cleanup;
|
||||
|
||||
/* We really want to cull the candidates list early
|
||||
* with cheap tests in order to avoid doing deltas.
|
||||
* The first round matches up the up-to-date entries,
|
||||
|
|
|
@ -34,7 +34,11 @@ fi
|
|||
|
||||
http_fetch () {
|
||||
# $1 = Remote, $2 = Local
|
||||
curl -nsfL $curl_extra_args "$1" >"$2"
|
||||
curl -nsfL $curl_extra_args "$1" >"$2" ||
|
||||
case $? in
|
||||
126|127) exit ;;
|
||||
*) return $? ;;
|
||||
esac
|
||||
}
|
||||
|
||||
clone_dumb_http () {
|
||||
|
|
|
@ -98,101 +98,71 @@ do
|
|||
no_edit=t
|
||||
log_given=t$log_given
|
||||
logfile="$1"
|
||||
shift
|
||||
;;
|
||||
-F*|-f*)
|
||||
no_edit=t
|
||||
log_given=t$log_given
|
||||
logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
|
||||
shift
|
||||
logfile="${1#-[Ff]}"
|
||||
;;
|
||||
--F=*|--f=*|--fi=*|--fil=*|--file=*)
|
||||
no_edit=t
|
||||
log_given=t$log_given
|
||||
logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||
shift
|
||||
logfile="${1#*=}"
|
||||
;;
|
||||
-a|--a|--al|--all)
|
||||
all=t
|
||||
shift
|
||||
;;
|
||||
--au=*|--aut=*|--auth=*|--autho=*|--author=*)
|
||||
force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||
shift
|
||||
force_author="${1#*=}"
|
||||
;;
|
||||
--au|--aut|--auth|--autho|--author)
|
||||
case "$#" in 1) usage ;; esac
|
||||
shift
|
||||
force_author="$1"
|
||||
shift
|
||||
;;
|
||||
-e|--e|--ed|--edi|--edit)
|
||||
edit_flag=t
|
||||
shift
|
||||
;;
|
||||
-i|--i|--in|--inc|--incl|--inclu|--includ|--include)
|
||||
also=t
|
||||
shift
|
||||
;;
|
||||
--int|--inte|--inter|--intera|--interac|--interact|--interacti|\
|
||||
--interactiv|--interactive)
|
||||
interactive=t
|
||||
shift
|
||||
;;
|
||||
-o|--o|--on|--onl|--only)
|
||||
only=t
|
||||
shift
|
||||
;;
|
||||
-m|--m|--me|--mes|--mess|--messa|--messag|--message)
|
||||
case "$#" in 1) usage ;; esac
|
||||
shift
|
||||
log_given=m$log_given
|
||||
if test "$log_message" = ''
|
||||
then
|
||||
log_message="$1"
|
||||
else
|
||||
log_message="$log_message
|
||||
log_message="${log_message:+${log_message}
|
||||
|
||||
$1"
|
||||
fi
|
||||
}$1"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
-m*)
|
||||
log_given=m$log_given
|
||||
if test "$log_message" = ''
|
||||
then
|
||||
log_message=`expr "z$1" : 'z-m\(.*\)'`
|
||||
else
|
||||
log_message="$log_message
|
||||
log_message="${log_message:+${log_message}
|
||||
|
||||
`expr "z$1" : 'z-m\(.*\)'`"
|
||||
fi
|
||||
}${1#-m}"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
|
||||
log_given=m$log_given
|
||||
if test "$log_message" = ''
|
||||
then
|
||||
log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||
else
|
||||
log_message="$log_message
|
||||
log_message="${log_message:+${log_message}
|
||||
|
||||
`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
|
||||
fi
|
||||
}${1#*=}"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
-n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
|
||||
--no-verify)
|
||||
verify=
|
||||
shift
|
||||
;;
|
||||
--a|--am|--ame|--amen|--amend)
|
||||
amend=t
|
||||
use_commit=HEAD
|
||||
shift
|
||||
;;
|
||||
-c)
|
||||
case "$#" in 1) usage ;; esac
|
||||
|
@ -200,15 +170,13 @@ $1"
|
|||
log_given=t$log_given
|
||||
use_commit="$1"
|
||||
no_edit=
|
||||
shift
|
||||
;;
|
||||
--ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
|
||||
--reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
|
||||
--reedit-messag=*|--reedit-message=*)
|
||||
log_given=t$log_given
|
||||
use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||
use_commit="${1#*=}"
|
||||
no_edit=
|
||||
shift
|
||||
;;
|
||||
--ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
|
||||
--reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
|
||||
|
@ -218,7 +186,6 @@ $1"
|
|||
log_given=t$log_given
|
||||
use_commit="$1"
|
||||
no_edit=
|
||||
shift
|
||||
;;
|
||||
-C)
|
||||
case "$#" in 1) usage ;; esac
|
||||
|
@ -226,15 +193,13 @@ $1"
|
|||
log_given=t$log_given
|
||||
use_commit="$1"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
--reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
|
||||
--reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
|
||||
--reuse-message=*)
|
||||
log_given=t$log_given
|
||||
use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
|
||||
use_commit="${1#*=}"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
--reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
|
||||
--reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
|
||||
|
@ -243,32 +208,26 @@ $1"
|
|||
log_given=t$log_given
|
||||
use_commit="$1"
|
||||
no_edit=t
|
||||
shift
|
||||
;;
|
||||
-s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
|
||||
signoff=t
|
||||
shift
|
||||
;;
|
||||
-t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
|
||||
case "$#" in 1) usage ;; esac
|
||||
shift
|
||||
templatefile="$1"
|
||||
no_edit=
|
||||
shift
|
||||
;;
|
||||
-q|--q|--qu|--qui|--quie|--quiet)
|
||||
quiet=t
|
||||
shift
|
||||
;;
|
||||
-v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
|
||||
verbose=t
|
||||
shift
|
||||
;;
|
||||
-u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
|
||||
--untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
|
||||
--untracked-file|--untracked-files)
|
||||
untracked_files=t
|
||||
shift
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
|
@ -281,6 +240,7 @@ $1"
|
|||
break
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
case "$edit_flag" in t) no_edit= ;; esac
|
||||
|
||||
|
@ -379,8 +339,11 @@ t,)
|
|||
then
|
||||
refuse_partial "Cannot do a partial commit during a merge."
|
||||
fi
|
||||
|
||||
TMP_INDEX="$GIT_DIR/tmp-index$$"
|
||||
commit_only=`git ls-files --error-unmatch -- "$@"` || exit
|
||||
W=
|
||||
test -z "$initial_commit" && W=--with-tree=HEAD
|
||||
commit_only=`git ls-files --error-unmatch $W -- "$@"` || exit
|
||||
|
||||
# Build a temporary index and update the real index
|
||||
# the same way.
|
||||
|
@ -401,7 +364,7 @@ t,)
|
|||
(
|
||||
GIT_INDEX_FILE="$NEXT_INDEX"
|
||||
export GIT_INDEX_FILE
|
||||
git update-index --remove --stdin
|
||||
git update-index --add --remove --stdin
|
||||
) || exit
|
||||
;;
|
||||
esac
|
||||
|
@ -438,12 +401,8 @@ esac
|
|||
|
||||
if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
|
||||
then
|
||||
if test "$TMP_INDEX"
|
||||
then
|
||||
GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
|
||||
else
|
||||
GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
|
||||
fi || exit
|
||||
GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
|
||||
|| exit
|
||||
fi
|
||||
|
||||
if test "$log_message" != ''
|
||||
|
@ -554,7 +513,7 @@ else
|
|||
# we need to check if there is anything to commit
|
||||
run_status >/dev/null
|
||||
fi
|
||||
if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
|
||||
if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" ]
|
||||
then
|
||||
rm -f "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
|
||||
use_status_color=t
|
||||
|
|
|
@ -31,6 +31,9 @@ ifndef INSTALL
|
|||
INSTALL = install
|
||||
endif
|
||||
|
||||
RM_F ?= rm -f
|
||||
RMDIR ?= rmdir
|
||||
|
||||
INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
|
||||
INSTALL_D1 =
|
||||
INSTALL_R0 = $(INSTALL) -m644 # space is required here
|
||||
|
@ -42,6 +45,12 @@ INSTALL_L1 = && ln # space is required here
|
|||
INSTALL_L2 =
|
||||
INSTALL_L3 =
|
||||
|
||||
REMOVE_D0 = $(RMDIR) # space is required here
|
||||
REMOVE_D1 = || true
|
||||
REMOVE_F0 = $(RM_F) # space is required here
|
||||
REMOVE_F1 =
|
||||
CLEAN_DST = true
|
||||
|
||||
ifndef V
|
||||
QUIET = @
|
||||
QUIET_GEN = $(QUIET)echo ' ' GEN $@ &&
|
||||
|
@ -60,6 +69,12 @@ ifndef V
|
|||
INSTALL_L1 = && src=
|
||||
INSTALL_L2 = && dst=
|
||||
INSTALL_L3 = && echo ' ' 'LINK ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst"
|
||||
|
||||
CLEAN_DST = echo ' ' UNINSTALL
|
||||
REMOVE_D0 = dir=
|
||||
REMOVE_D1 = && echo ' ' REMOVE $$dir && test -d "$$dir" && $(RMDIR) "$$dir" || true
|
||||
REMOVE_F0 = dst=
|
||||
REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_F) "$$dst"
|
||||
endif
|
||||
|
||||
TCL_PATH ?= tclsh
|
||||
|
@ -76,8 +91,8 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
|||
TCL_PATH_SQ = $(subst ','\'',$(TCL_PATH))
|
||||
TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
|
||||
|
||||
libdir ?= $(sharedir)/git-gui/lib
|
||||
libdir_SQ = $(subst ','\'',$(libdir))
|
||||
gg_libdir ?= $(sharedir)/git-gui/lib
|
||||
libdir_SQ = $(subst ','\'',$(gg_libdir))
|
||||
|
||||
exedir = $(dir $(gitexecdir))share/git-gui/lib
|
||||
exedir_SQ = $(subst ','\'',$(exedir))
|
||||
|
@ -126,7 +141,7 @@ TRACK_VARS = \
|
|||
$(subst ','\'',TCL_PATH='$(TCL_PATH_SQ)') \
|
||||
$(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \
|
||||
$(subst ','\'',gitexecdir='$(gitexecdir_SQ)') \
|
||||
$(subst ','\'',libdir='$(libdir_SQ)') \
|
||||
$(subst ','\'',gg_libdir='$(libdir_SQ)') \
|
||||
#end TRACK_VARS
|
||||
|
||||
GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
|
||||
|
@ -146,6 +161,17 @@ install: all
|
|||
$(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
|
||||
$(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
|
||||
|
||||
uninstall:
|
||||
$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
|
||||
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1)
|
||||
$(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true
|
||||
$(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(libdir_SQ)'
|
||||
$(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/tclIndex $(REMOVE_F1)
|
||||
$(QUIET)$(foreach p,$(ALL_LIBFILES), $(REMOVE_F0)'$(DESTDIR_SQ)$(libdir_SQ)'/$(notdir $p) $(REMOVE_F1) &&) true
|
||||
$(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(REMOVE_D1)
|
||||
$(QUIET)$(REMOVE_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(REMOVE_D1)
|
||||
$(QUIET)$(REMOVE_D0)`dirname '$(DESTDIR_SQ)$(libdir_SQ)'` $(REMOVE_D1)
|
||||
|
||||
dist-version:
|
||||
@mkdir -p $(TARDIR)
|
||||
@echo $(GITGUI_VERSION) > $(TARDIR)/version
|
||||
|
@ -154,6 +180,6 @@ clean::
|
|||
rm -f $(ALL_PROGRAMS) lib/tclIndex
|
||||
rm -f GIT-VERSION-FILE GIT-GUI-VARS
|
||||
|
||||
.PHONY: all install dist-version clean
|
||||
.PHONY: all install uninstall dist-version clean
|
||||
.PHONY: .FORCE-GIT-VERSION-FILE
|
||||
.PHONY: .FORCE-GIT-GUI-VARS
|
||||
|
|
|
@ -42,6 +42,8 @@ if {[catch {package require Tcl 8.4} err]
|
|||
exit 1
|
||||
}
|
||||
|
||||
catch {rename send {}} ; # What an evil concept...
|
||||
|
||||
######################################################################
|
||||
##
|
||||
## enable verbose loading?
|
||||
|
@ -60,6 +62,18 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
|
|||
}
|
||||
}
|
||||
|
||||
######################################################################
|
||||
##
|
||||
## Fake internationalization to ease backporting of changes.
|
||||
|
||||
proc mc {fmt args} {
|
||||
set cmk [string first @@ $fmt]
|
||||
if {$cmk > 0} {
|
||||
set fmt [string range $fmt 0 [expr {$cmk - 1}]]
|
||||
}
|
||||
return [eval [list format $fmt] $args]
|
||||
}
|
||||
|
||||
######################################################################
|
||||
##
|
||||
## read only globals
|
||||
|
@ -261,7 +275,7 @@ proc _git_cmd {name} {
|
|||
set s [gets $f]
|
||||
close $f
|
||||
|
||||
switch -glob -- $s {
|
||||
switch -glob -- [lindex $s 0] {
|
||||
#!*sh { set i sh }
|
||||
#!*perl { set i perl }
|
||||
#!*python { set i python }
|
||||
|
@ -275,7 +289,7 @@ proc _git_cmd {name} {
|
|||
if {$interp eq {}} {
|
||||
error "git-$name requires $i (not in PATH)"
|
||||
}
|
||||
set v [list $interp $p]
|
||||
set v [concat [list $interp] [lrange $s 1 end] [list $p]]
|
||||
} else {
|
||||
# Assume it is builtin to git somehow and we
|
||||
# aren't actually able to see a file for it.
|
||||
|
@ -467,6 +481,16 @@ proc tk_optionMenu {w varName args} {
|
|||
return $m
|
||||
}
|
||||
|
||||
proc rmsel_tag {text} {
|
||||
$text tag conf sel \
|
||||
-background [$text cget -background] \
|
||||
-foreground [$text cget -foreground] \
|
||||
-borderwidth 0
|
||||
$text tag conf in_sel -background lightgray
|
||||
bind $text <Motion> break
|
||||
return $text
|
||||
}
|
||||
|
||||
######################################################################
|
||||
##
|
||||
## find git
|
||||
|
@ -1008,7 +1032,11 @@ proc read_ls_others {fd after} {
|
|||
set pck [split $buf_rlo "\0"]
|
||||
set buf_rlo [lindex $pck end]
|
||||
foreach p [lrange $pck 0 end-1] {
|
||||
merge_state [encoding convertfrom $p] ?O
|
||||
set p [encoding convertfrom $p]
|
||||
if {[string index $p end] eq {/}} {
|
||||
set p [string range $p 0 end-1]
|
||||
}
|
||||
merge_state $p ?O
|
||||
}
|
||||
rescan_done $fd buf_rlo $after
|
||||
}
|
||||
|
@ -2133,8 +2161,8 @@ pack $ui_workdir -side left -fill both -expand 1
|
|||
.vpane.files add .vpane.files.workdir -sticky nsew
|
||||
|
||||
foreach i [list $ui_index $ui_workdir] {
|
||||
$i tag conf in_diff -background lightgray
|
||||
$i tag conf in_sel -background lightgray
|
||||
rmsel_tag $i
|
||||
$i tag conf in_diff -background [$i tag cget in_sel -background]
|
||||
}
|
||||
unset i
|
||||
|
||||
|
@ -2441,21 +2469,18 @@ proc popup_diff_menu {ctxm x y X Y} {
|
|||
set ::cursorX $x
|
||||
set ::cursorY $y
|
||||
if {$::ui_index eq $::current_diff_side} {
|
||||
set s normal
|
||||
set l "Unstage Hunk From Commit"
|
||||
} else {
|
||||
if {$current_diff_path eq {}
|
||||
set l "Stage Hunk For Commit"
|
||||
}
|
||||
if {$::is_3way_diff
|
||||
|| $current_diff_path eq {}
|
||||
|| ![info exists file_states($current_diff_path)]
|
||||
|| {_O} eq [lindex $file_states($current_diff_path) 0]} {
|
||||
set s disabled
|
||||
} else {
|
||||
set s normal
|
||||
}
|
||||
set l "Stage Hunk For Commit"
|
||||
}
|
||||
if {$::is_3way_diff} {
|
||||
set s disabled
|
||||
}
|
||||
$ctxm entryconf $::ui_diff_applyhunk -state $s -label $l
|
||||
tk_popup $ctxm $X $Y
|
||||
}
|
||||
|
|
|
@ -47,9 +47,7 @@ constructor new {commit {path {}}} {
|
|||
-width 70 \
|
||||
-xscrollcommand [list $w.list.sbx set] \
|
||||
-yscrollcommand [list $w.list.sby set]
|
||||
$w_list tag conf in_sel \
|
||||
-background [$w_list cget -foreground] \
|
||||
-foreground [$w_list cget -background]
|
||||
rmsel_tag $w_list
|
||||
scrollbar $w.list.sbx -orient h -command [list $w_list xview]
|
||||
scrollbar $w.list.sby -orient v -command [list $w_list yview]
|
||||
pack $w.list.sbx -side bottom -fill x
|
||||
|
|
|
@ -396,7 +396,7 @@ method _after_readtree {} {
|
|||
set is_detached 0
|
||||
}
|
||||
} else {
|
||||
if {$new_hash ne $HEAD} {
|
||||
if {!$is_detached || $new_hash ne $HEAD} {
|
||||
append log " to $new_expr"
|
||||
if {[catch {
|
||||
_detach_HEAD $log $new_hash
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
# git-gui font chooser
|
||||
# Copyright (C) 2007 Shawn Pearce
|
||||
|
||||
class choose_font {
|
||||
|
||||
field w
|
||||
field w_family ; # UI widget of all known family names
|
||||
field w_example ; # Example to showcase the chosen font
|
||||
|
||||
field f_family ; # Currently chosen family name
|
||||
field f_size ; # Currently chosen point size
|
||||
|
||||
field v_family ; # Name of global variable for family
|
||||
field v_size ; # Name of global variable for size
|
||||
|
||||
variable all_families [list] ; # All fonts known to Tk
|
||||
|
||||
constructor pick {path title a_family a_size} {
|
||||
variable all_families
|
||||
|
||||
set v_family $a_family
|
||||
set v_size $a_size
|
||||
|
||||
upvar #0 $v_family pv_family
|
||||
upvar #0 $v_size pv_size
|
||||
|
||||
set f_family $pv_family
|
||||
set f_size $pv_size
|
||||
|
||||
make_toplevel top w
|
||||
wm title $top "[appname] ([reponame]): $title"
|
||||
wm geometry $top "+[winfo rootx $path]+[winfo rooty $path]"
|
||||
|
||||
label $w.header -text $title -font font_uibold
|
||||
pack $w.header -side top -fill x
|
||||
|
||||
frame $w.buttons
|
||||
button $w.buttons.select \
|
||||
-text [mc Select] \
|
||||
-default active \
|
||||
-command [cb _select]
|
||||
button $w.buttons.cancel \
|
||||
-text [mc Cancel] \
|
||||
-command [list destroy $w]
|
||||
pack $w.buttons.select -side right
|
||||
pack $w.buttons.cancel -side right -padx 5
|
||||
pack $w.buttons -side bottom -fill x -pady 10 -padx 10
|
||||
|
||||
frame $w.inner
|
||||
|
||||
frame $w.inner.family
|
||||
label $w.inner.family.l \
|
||||
-text [mc "Font Family"] \
|
||||
-anchor w
|
||||
set w_family $w.inner.family.v
|
||||
text $w_family \
|
||||
-background white \
|
||||
-borderwidth 1 \
|
||||
-relief sunken \
|
||||
-cursor $::cursor_ptr \
|
||||
-wrap none \
|
||||
-width 30 \
|
||||
-height 10 \
|
||||
-yscrollcommand [list $w.inner.family.sby set]
|
||||
rmsel_tag $w_family
|
||||
scrollbar $w.inner.family.sby -command [list $w_family yview]
|
||||
pack $w.inner.family.l -side top -fill x
|
||||
pack $w.inner.family.sby -side right -fill y
|
||||
pack $w_family -fill both -expand 1
|
||||
|
||||
frame $w.inner.size
|
||||
label $w.inner.size.l \
|
||||
-text [mc "Font Size"] \
|
||||
-anchor w
|
||||
spinbox $w.inner.size.v \
|
||||
-textvariable @f_size \
|
||||
-from 2 -to 80 -increment 1 \
|
||||
-width 3
|
||||
bind $w.inner.size.v <FocusIn> {%W selection range 0 end}
|
||||
pack $w.inner.size.l -fill x -side top
|
||||
pack $w.inner.size.v -fill x -padx 2
|
||||
|
||||
grid configure $w.inner.family $w.inner.size -sticky nsew
|
||||
grid rowconfigure $w.inner 0 -weight 1
|
||||
grid columnconfigure $w.inner 0 -weight 1
|
||||
pack $w.inner -fill both -expand 1 -padx 5 -pady 5
|
||||
|
||||
frame $w.example
|
||||
label $w.example.l \
|
||||
-text [mc "Font Example"] \
|
||||
-anchor w
|
||||
set w_example $w.example.t
|
||||
text $w_example \
|
||||
-background white \
|
||||
-borderwidth 1 \
|
||||
-relief sunken \
|
||||
-height 3 \
|
||||
-width 40
|
||||
rmsel_tag $w_example
|
||||
$w_example tag conf example -justify center
|
||||
$w_example insert end [mc "This is example text.\nIf you like this text, it can be your font."] example
|
||||
$w_example conf -state disabled
|
||||
pack $w.example.l -fill x
|
||||
pack $w_example -fill x
|
||||
pack $w.example -fill x -padx 5
|
||||
|
||||
if {$all_families eq {}} {
|
||||
set all_families [lsort [font families]]
|
||||
}
|
||||
|
||||
$w_family tag conf pick
|
||||
$w_family tag bind pick <Button-1> [cb _pick_family %x %y]\;break
|
||||
foreach f $all_families {
|
||||
set sel [list pick]
|
||||
if {$f eq $f_family} {
|
||||
lappend sel in_sel
|
||||
}
|
||||
$w_family insert end "$f\n" $sel
|
||||
}
|
||||
$w_family conf -state disabled
|
||||
_update $this
|
||||
|
||||
trace add variable @f_size write [cb _update]
|
||||
bind $w <Key-Escape> [list destroy $w]
|
||||
bind $w <Key-Return> [cb _select]\;break
|
||||
bind $w <Visibility> "
|
||||
grab $w
|
||||
focus $w
|
||||
"
|
||||
tkwait window $w
|
||||
}
|
||||
|
||||
method _select {} {
|
||||
upvar #0 $v_family pv_family
|
||||
upvar #0 $v_size pv_size
|
||||
|
||||
set pv_family $f_family
|
||||
set pv_size $f_size
|
||||
|
||||
destroy $w
|
||||
}
|
||||
|
||||
method _pick_family {x y} {
|
||||
variable all_families
|
||||
|
||||
set i [lindex [split [$w_family index @$x,$y] .] 0]
|
||||
set n [lindex $all_families [expr {$i - 1}]]
|
||||
if {$n ne {}} {
|
||||
$w_family tag remove in_sel 0.0 end
|
||||
$w_family tag add in_sel $i.0 [expr {$i + 1}].0
|
||||
set f_family $n
|
||||
_update $this
|
||||
}
|
||||
}
|
||||
|
||||
method _update {args} {
|
||||
variable all_families
|
||||
|
||||
set i [lsearch -exact $all_families $f_family]
|
||||
if {$i < 0} return
|
||||
|
||||
$w_example tag conf example -font [list $f_family $f_size]
|
||||
$w_family see [expr {$i + 1}].0
|
||||
}
|
||||
|
||||
}
|
|
@ -84,12 +84,30 @@ proc show_diff {path w {lno {}}} {
|
|||
#
|
||||
if {$m eq {_O}} {
|
||||
set max_sz [expr {128 * 1024}]
|
||||
set type unknown
|
||||
if {[catch {
|
||||
set type [file type $path]
|
||||
switch -- $type {
|
||||
directory {
|
||||
set type submodule
|
||||
set content {}
|
||||
set sz 0
|
||||
}
|
||||
link {
|
||||
set content [file readlink $path]
|
||||
set sz [string length $content]
|
||||
}
|
||||
file {
|
||||
set fd [open $path r]
|
||||
fconfigure $fd -eofchar {}
|
||||
set content [read $fd $max_sz]
|
||||
close $fd
|
||||
set sz [file size $path]
|
||||
}
|
||||
default {
|
||||
error "'$type' not supported"
|
||||
}
|
||||
}
|
||||
} err ]} {
|
||||
set diff_active 0
|
||||
unlock_index
|
||||
|
@ -98,7 +116,9 @@ proc show_diff {path w {lno {}}} {
|
|||
return
|
||||
}
|
||||
$ui_diff conf -state normal
|
||||
if {![catch {set type [exec file $path]}]} {
|
||||
if {$type eq {submodule}} {
|
||||
$ui_diff insert end "* Git Repository (subproject)\n" d_@
|
||||
} elseif {![catch {set type [exec file $path]}]} {
|
||||
set n [string length $path]
|
||||
if {[string equal -length $n $path $type]} {
|
||||
set type [string range $type $n end]
|
||||
|
@ -198,6 +218,7 @@ proc read_diff {fd} {
|
|||
if {[string match {mode *} $line]
|
||||
|| [string match {new file *} $line]
|
||||
|| [string match {deleted file *} $line]
|
||||
|| [string match {deleted symlink} $line]
|
||||
|| [string match {Binary files * and * differ} $line]
|
||||
|| $line eq {\ No newline at end of file}
|
||||
|| [regexp {^\* Unmerged path } $line]} {
|
||||
|
|
|
@ -13,7 +13,8 @@ proc update_indexinfo {msg pathList after} {
|
|||
if {$batch > 25} {set batch 25}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
0.0]
|
||||
|
@ -68,7 +69,8 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
|
|||
}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
[expr {100.0 * $update_index_cp / $totalCnt}]]
|
||||
|
@ -86,7 +88,8 @@ proc update_index {msg pathList after} {
|
|||
if {$batch > 25} {set batch 25}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
0.0]
|
||||
|
@ -145,7 +148,8 @@ proc write_update_index {fd pathList totalCnt batch msg after} {
|
|||
}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
[expr {100.0 * $update_index_cp / $totalCnt}]]
|
||||
|
@ -163,7 +167,8 @@ proc checkout_index {msg pathList after} {
|
|||
if {$batch > 25} {set batch 25}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
0.0]
|
||||
|
@ -218,7 +223,8 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} {
|
|||
}
|
||||
|
||||
ui_status [format \
|
||||
"$msg... %i/%i files (%.2f%%)" \
|
||||
"%s... %i/%i files (%.2f%%)" \
|
||||
$msg \
|
||||
$update_index_cp \
|
||||
$totalCnt \
|
||||
[expr {100.0 * $update_index_cp / $totalCnt}]]
|
||||
|
|
|
@ -255,17 +255,23 @@ proc do_options {} {
|
|||
|
||||
frame $w.global.$name
|
||||
label $w.global.$name.l -text "$text:"
|
||||
pack $w.global.$name.l -side left -anchor w -fill x
|
||||
eval tk_optionMenu $w.global.$name.family \
|
||||
button $w.global.$name.b \
|
||||
-text [mc "Change Font"] \
|
||||
-command [list \
|
||||
choose_font::pick \
|
||||
$w \
|
||||
[mc "Choose %s" $text] \
|
||||
global_config_new(gui.$font^^family) \
|
||||
$all_fonts
|
||||
spinbox $w.global.$name.size \
|
||||
-textvariable global_config_new(gui.$font^^size) \
|
||||
-from 2 -to 80 -increment 1 \
|
||||
-width 3
|
||||
bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
|
||||
pack $w.global.$name.size -side right -anchor e
|
||||
pack $w.global.$name.family -side right -anchor e
|
||||
global_config_new(gui.$font^^size) \
|
||||
]
|
||||
label $w.global.$name.f -textvariable global_config_new(gui.$font^^family)
|
||||
label $w.global.$name.s -textvariable global_config_new(gui.$font^^size)
|
||||
label $w.global.$name.pt -text [mc "pt."]
|
||||
pack $w.global.$name.l -side left -anchor w
|
||||
pack $w.global.$name.b -side right -anchor e
|
||||
pack $w.global.$name.pt -side right -anchor w
|
||||
pack $w.global.$name.s -side right -anchor w
|
||||
pack $w.global.$name.f -side right -anchor w
|
||||
pack $w.global.$name -side top -anchor w -fill x
|
||||
}
|
||||
|
||||
|
|
|
@ -278,6 +278,8 @@ sub add_remote {
|
|||
|
||||
for (@$track) {
|
||||
$git->command('config', '--add', "remote.$name.fetch",
|
||||
$opts->{'mirror'} ?
|
||||
"+refs/$_:refs/$_" :
|
||||
"+refs/heads/$_:refs/remotes/$name/$_");
|
||||
}
|
||||
if ($opts->{'fetch'}) {
|
||||
|
@ -409,6 +411,10 @@ elsif ($ARGV[0] eq 'add') {
|
|||
shift @ARGV;
|
||||
next;
|
||||
}
|
||||
if ($opt eq '--mirror') {
|
||||
$opts{'mirror'} = 1;
|
||||
next;
|
||||
}
|
||||
add_usage();
|
||||
}
|
||||
if (@ARGV != 3) {
|
||||
|
|
|
@ -361,7 +361,8 @@ if ($thread && !defined $initial_reply_to && $prompting) {
|
|||
} while (!defined $_);
|
||||
|
||||
$initial_reply_to = $_;
|
||||
$initial_reply_to =~ s/(^\s+|\s+$)//g;
|
||||
$initial_reply_to =~ s/^\s+<?/</;
|
||||
$initial_reply_to =~ s/>?\s+$/>/;
|
||||
}
|
||||
|
||||
if (!defined $smtp_server) {
|
||||
|
@ -477,10 +478,17 @@ sub extract_valid_address {
|
|||
|
||||
# We'll setup a template for the message id, using the "from" address:
|
||||
|
||||
my ($message_id_stamp, $message_id_serial);
|
||||
sub make_message_id
|
||||
{
|
||||
my $date = time;
|
||||
my $pseudo_rand = int (rand(4200));
|
||||
my $uniq;
|
||||
if (!defined $message_id_stamp) {
|
||||
$message_id_stamp = sprintf("%s-%s", time, $$);
|
||||
$message_id_serial = 0;
|
||||
}
|
||||
$message_id_serial++;
|
||||
$uniq = "$message_id_stamp-$message_id_serial";
|
||||
|
||||
my $du_part;
|
||||
for ($sender, $repocommitter, $repoauthor) {
|
||||
$du_part = extract_valid_address(sanitize_address($_));
|
||||
|
@ -490,8 +498,8 @@ sub make_message_id
|
|||
use Sys::Hostname qw();
|
||||
$du_part = 'user@' . Sys::Hostname::hostname();
|
||||
}
|
||||
my $message_id_template = "<%s-git-send-email-$du_part>";
|
||||
$message_id = sprintf $message_id_template, "$date$pseudo_rand";
|
||||
my $message_id_template = "<%s-git-send-email-%s>";
|
||||
$message_id = sprintf($message_id_template, $uniq, $du_part);
|
||||
#print "new message id = $message_id\n"; # Was useful for debugging
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
# it dies.
|
||||
|
||||
# Having this variable in your environment would break scripts because
|
||||
# you would cause "cd" to be be taken to unexpected places. If you
|
||||
# you would cause "cd" to be taken to unexpected places. If you
|
||||
# like CDPATH, define it for your interactive shell sessions without
|
||||
# exporting it.
|
||||
unset CDPATH
|
||||
|
|
|
@ -57,7 +57,7 @@ save_stash () {
|
|||
|
||||
# state of the index
|
||||
i_tree=$(git write-tree) &&
|
||||
i_commit=$(printf 'index on %s' "$msg" |
|
||||
i_commit=$(printf 'index on %s\n' "$msg" |
|
||||
git commit-tree $i_tree -p $b_commit) ||
|
||||
die "Cannot save the current index state"
|
||||
|
||||
|
|
|
@ -124,7 +124,8 @@ my %cmd = (
|
|||
"Set an SVN repository to a git tree-ish",
|
||||
{ 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
|
||||
'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
|
||||
{ 'revision|r=i' => \$_revision } ],
|
||||
{ 'revision|r=i' => \$_revision
|
||||
} ],
|
||||
'multi-fetch' => [ \&cmd_multi_fetch,
|
||||
"Deprecated alias for $0 fetch --all",
|
||||
{ 'revision|r=s' => \$_revision, %fc_opts } ],
|
||||
|
@ -144,7 +145,7 @@ my %cmd = (
|
|||
'non-recursive' => \$Git::SVN::Log::non_recursive,
|
||||
'authors-file|A=s' => \$_authors,
|
||||
'color' => \$Git::SVN::Log::color,
|
||||
'pager=s' => \$Git::SVN::Log::pager,
|
||||
'pager=s' => \$Git::SVN::Log::pager
|
||||
} ],
|
||||
'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish",
|
||||
{} ],
|
||||
|
@ -811,7 +812,8 @@ sub cmt_metadata {
|
|||
|
||||
sub working_head_info {
|
||||
my ($head, $refs) = @_;
|
||||
my ($fh, $ctx) = command_output_pipe('log', '--no-color', $head);
|
||||
my @args = ('log', '--no-color', '--first-parent');
|
||||
my ($fh, $ctx) = command_output_pipe(@args, $head);
|
||||
my $hash;
|
||||
my %max;
|
||||
while (<$fh>) {
|
||||
|
|
|
@ -633,7 +633,7 @@ sub commit {
|
|||
|
||||
my $rev;
|
||||
if($revision > $opt_s and defined $parent) {
|
||||
open(H,"git-rev-parse --verify $parent |");
|
||||
open(H,'-|',"git-rev-parse","--verify",$parent);
|
||||
$rev = <H>;
|
||||
close(H) or do {
|
||||
print STDERR "$revision: cannot find commit '$parent'!\n";
|
||||
|
|
1
git.c
1
git.c
|
@ -364,6 +364,7 @@ static void handle_internal_command(int argc, const char **argv)
|
|||
{ "reflog", cmd_reflog, RUN_SETUP },
|
||||
{ "repo-config", cmd_config },
|
||||
{ "rerere", cmd_rerere, RUN_SETUP },
|
||||
{ "reset", cmd_reset, RUN_SETUP },
|
||||
{ "rev-list", cmd_rev_list, RUN_SETUP },
|
||||
{ "rev-parse", cmd_rev_parse, RUN_SETUP },
|
||||
{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
|
||||
|
|
|
@ -171,30 +171,6 @@ static void output_commit_title(struct commit *commit)
|
|||
}
|
||||
}
|
||||
|
||||
static struct cache_entry *make_cache_entry(unsigned int mode,
|
||||
const unsigned char *sha1, const char *path, int stage, int refresh)
|
||||
{
|
||||
int size, len;
|
||||
struct cache_entry *ce;
|
||||
|
||||
if (!verify_path(path))
|
||||
return NULL;
|
||||
|
||||
len = strlen(path);
|
||||
size = cache_entry_size(len);
|
||||
ce = xcalloc(1, size);
|
||||
|
||||
hashcpy(ce->sha1, sha1);
|
||||
memcpy(ce->name, path, len);
|
||||
ce->ce_flags = create_ce_flags(len, stage);
|
||||
ce->ce_mode = create_ce_mode(mode);
|
||||
|
||||
if (refresh)
|
||||
return refresh_cache_entry(ce, 0);
|
||||
|
||||
return ce;
|
||||
}
|
||||
|
||||
static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
|
||||
const char *path, int stage, int refresh, int options)
|
||||
{
|
||||
|
|
28
read-cache.c
28
read-cache.c
|
@ -346,6 +346,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)
|
|||
int pos = index_name_pos(istate, path, strlen(path));
|
||||
if (pos < 0)
|
||||
pos = -pos-1;
|
||||
cache_tree_invalidate_path(istate->cache_tree, path);
|
||||
while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
|
||||
remove_index_entry_at(istate, pos);
|
||||
return 0;
|
||||
|
@ -430,10 +431,34 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
|
|||
die("unable to add %s to index",path);
|
||||
if (verbose)
|
||||
printf("add '%s'\n", path);
|
||||
cache_tree_invalidate_path(istate->cache_tree, path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct cache_entry *make_cache_entry(unsigned int mode,
|
||||
const unsigned char *sha1, const char *path, int stage,
|
||||
int refresh)
|
||||
{
|
||||
int size, len;
|
||||
struct cache_entry *ce;
|
||||
|
||||
if (!verify_path(path))
|
||||
return NULL;
|
||||
|
||||
len = strlen(path);
|
||||
size = cache_entry_size(len);
|
||||
ce = xcalloc(1, size);
|
||||
|
||||
hashcpy(ce->sha1, sha1);
|
||||
memcpy(ce->name, path, len);
|
||||
ce->ce_flags = create_ce_flags(len, stage);
|
||||
ce->ce_mode = create_ce_mode(mode);
|
||||
|
||||
if (refresh)
|
||||
return refresh_cache_entry(ce, 0);
|
||||
|
||||
return ce;
|
||||
}
|
||||
|
||||
int ce_same_name(struct cache_entry *a, struct cache_entry *b)
|
||||
{
|
||||
int len = ce_namelen(a);
|
||||
|
@ -673,6 +698,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
|
|||
int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
|
||||
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
|
||||
|
||||
cache_tree_invalidate_path(istate->cache_tree, ce->name);
|
||||
pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
|
||||
|
||||
/* existing match? Just replace it. */
|
||||
|
|
|
@ -1024,6 +1024,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
|
|||
}
|
||||
if (!strcmp(arg, "--cherry-pick")) {
|
||||
revs->cherry_pick = 1;
|
||||
revs->limited = 1;
|
||||
continue;
|
||||
}
|
||||
if (!strcmp(arg, "--objects")) {
|
||||
|
|
|
@ -16,11 +16,12 @@ only the updates to dir/sub.'
|
|||
test_expect_success setup '
|
||||
echo initial >check &&
|
||||
echo initial >top &&
|
||||
echo initial >foo &&
|
||||
mkdir dir1 dir2 &&
|
||||
echo initial >dir1/sub1 &&
|
||||
echo initial >dir1/sub2 &&
|
||||
echo initial >dir2/sub3 &&
|
||||
git add check dir1 dir2 top &&
|
||||
git add check dir1 dir2 top foo &&
|
||||
test_tick
|
||||
git-commit -m initial &&
|
||||
|
||||
|
@ -76,4 +77,12 @@ test_expect_success 'change gets noticed' '
|
|||
|
||||
'
|
||||
|
||||
test_expect_success 'replace a file with a symlink' '
|
||||
|
||||
rm foo &&
|
||||
ln -s top foo &&
|
||||
git add -u -- foo
|
||||
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -68,4 +68,19 @@ test_expect_success \
|
|||
test 3 = $(git rev-list master.. | wc -l)
|
||||
'
|
||||
|
||||
test_expect_success 'rebase a single mode change' '
|
||||
git checkout master &&
|
||||
echo 1 > X &&
|
||||
git add X &&
|
||||
test_tick &&
|
||||
git commit -m prepare &&
|
||||
git checkout -b modechange HEAD^ &&
|
||||
echo 1 > X &&
|
||||
git add X &&
|
||||
chmod a+x A &&
|
||||
test_tick &&
|
||||
git commit -m modechange A X &&
|
||||
GIT_TRACE=1 git rebase master
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -10,12 +10,15 @@ test_description='Format-patch skipping already incorporated patches'
|
|||
test_expect_success setup '
|
||||
|
||||
for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file &&
|
||||
git add file &&
|
||||
cat file >elif &&
|
||||
git add file elif &&
|
||||
git commit -m Initial &&
|
||||
git checkout -b side &&
|
||||
|
||||
for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
|
||||
git update-index file &&
|
||||
chmod +x elif &&
|
||||
git update-index file elif &&
|
||||
git update-index --chmod=+x elif &&
|
||||
git commit -m "Side changes #1" &&
|
||||
|
||||
for i in D E F; do echo "$i"; done >>file &&
|
||||
|
|
|
@ -113,4 +113,14 @@ test_expect_success \
|
|||
! git diff .git/refs/heads/master victim/.git/refs/heads/master
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'pushing does not include non-head refs' '
|
||||
mkdir parent && cd parent &&
|
||||
git-init && touch file && git-add file && git-commit -m add &&
|
||||
cd .. &&
|
||||
git-clone parent child && cd child && git-push --all &&
|
||||
cd ../parent &&
|
||||
git-branch -a >branches && ! grep -q origin/master branches
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -40,4 +40,18 @@ test_expect_success '--cherry-pick bar does not come up empty' '
|
|||
! test -z "$(git rev-list --left-right --cherry-pick B...C -- bar)"
|
||||
'
|
||||
|
||||
test_expect_success '--cherry-pick with independent, but identical branches' '
|
||||
git symbolic-ref HEAD refs/heads/independent &&
|
||||
rm .git/index &&
|
||||
echo Hallo > foo &&
|
||||
git add foo &&
|
||||
test_tick &&
|
||||
git commit -m "independent" &&
|
||||
echo Bello > foo &&
|
||||
test_tick &&
|
||||
git commit -m "independent, too" foo &&
|
||||
test -z "$(git rev-list --left-right --cherry-pick \
|
||||
HEAD...master -- foo)"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -107,6 +107,10 @@ do
|
|||
diff expected actual
|
||||
'
|
||||
|
||||
test_expect_failure "grep -c $L (no /dev/null)" '
|
||||
git grep -c test $H | grep -q "/dev/null"
|
||||
'
|
||||
|
||||
done
|
||||
|
||||
test_done
|
||||
|
|
|
@ -0,0 +1,405 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# Copyright (c) 2007 Carlos Rica
|
||||
#
|
||||
|
||||
test_description='git-reset
|
||||
|
||||
Documented tests for git-reset'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'creating initial files and commits' '
|
||||
test_tick &&
|
||||
echo "1st file" >first &&
|
||||
git add first &&
|
||||
git commit -m "create 1st file" &&
|
||||
|
||||
echo "2nd file" >second &&
|
||||
git add second &&
|
||||
git commit -m "create 2nd file" &&
|
||||
|
||||
echo "2nd line 1st file" >>first &&
|
||||
git commit -a -m "modify 1st file" &&
|
||||
|
||||
git rm first &&
|
||||
git mv second secondfile &&
|
||||
git commit -a -m "remove 1st and rename 2nd" &&
|
||||
|
||||
echo "1st line 2nd file" >secondfile &&
|
||||
echo "2nd line 2nd file" >>secondfile &&
|
||||
git commit -a -m "modify 2nd file"
|
||||
'
|
||||
# git log --pretty=oneline # to see those SHA1 involved
|
||||
|
||||
check_changes () {
|
||||
test "$(git rev-parse HEAD)" = "$1" &&
|
||||
git diff | git diff .diff_expect - &&
|
||||
git diff --cached | git diff .cached_expect - &&
|
||||
for FILE in *
|
||||
do
|
||||
echo $FILE':'
|
||||
cat $FILE || return
|
||||
done | git diff .cat_expect -
|
||||
}
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
|
||||
test_expect_success 'giving a non existing revision should fail' '
|
||||
! git reset aaaaaa &&
|
||||
! git reset --mixed aaaaaa &&
|
||||
! git reset --soft aaaaaa &&
|
||||
! git reset --hard aaaaaa &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'giving paths with options different than --mixed should fail' '
|
||||
! git reset --soft -- first &&
|
||||
! git reset --hard -- first &&
|
||||
! git reset --soft HEAD^ -- first &&
|
||||
! git reset --hard HEAD^ -- first &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
test_expect_success 'giving unrecognized options should fail' '
|
||||
! git reset --other &&
|
||||
! git reset -o &&
|
||||
! git reset --mixed --other &&
|
||||
! git reset --mixed -o &&
|
||||
! git reset --soft --other &&
|
||||
! git reset --soft -o &&
|
||||
! git reset --hard --other &&
|
||||
! git reset --hard -o &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'trying to do reset --soft with pending merge should fail' '
|
||||
git branch branch1 &&
|
||||
git branch branch2 &&
|
||||
|
||||
git checkout branch1 &&
|
||||
echo "3rd line in branch1" >>secondfile &&
|
||||
git commit -a -m "change in branch1" &&
|
||||
|
||||
git checkout branch2 &&
|
||||
echo "3rd line in branch2" >>secondfile &&
|
||||
git commit -a -m "change in branch2" &&
|
||||
|
||||
! git merge branch1 &&
|
||||
! git reset --soft &&
|
||||
|
||||
printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
|
||||
git commit -a -m "the change in branch2" &&
|
||||
|
||||
git checkout master &&
|
||||
git branch -D branch1 branch2 &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'trying to do reset --soft with pending checkout merge should fail' '
|
||||
git branch branch3 &&
|
||||
git branch branch4 &&
|
||||
|
||||
git checkout branch3 &&
|
||||
echo "3rd line in branch3" >>secondfile &&
|
||||
git commit -a -m "line in branch3" &&
|
||||
|
||||
git checkout branch4 &&
|
||||
echo "3rd line in branch4" >>secondfile &&
|
||||
|
||||
git checkout -m branch3 &&
|
||||
! git reset --soft &&
|
||||
|
||||
printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
|
||||
git commit -a -m "the line in branch3" &&
|
||||
|
||||
git checkout master &&
|
||||
git branch -D branch3 branch4 &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
test_expect_success \
|
||||
'resetting to HEAD with no changes should succeed and do nothing' '
|
||||
git reset --hard &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset --hard HEAD &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset --soft &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset --soft HEAD &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset --mixed &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset --mixed HEAD &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
git reset HEAD &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
cat >.cached_expect <<EOF
|
||||
diff --git a/secondfile b/secondfile
|
||||
index 1bbba79..44c5b58 100644
|
||||
--- a/secondfile
|
||||
+++ b/secondfile
|
||||
@@ -1 +1,2 @@
|
||||
-2nd file
|
||||
+1st line 2nd file
|
||||
+2nd line 2nd file
|
||||
EOF
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
test_expect_success '--soft reset only should show changes in diff --cached' '
|
||||
git reset --soft HEAD^ &&
|
||||
check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 &&
|
||||
test "$(git rev-parse ORIG_HEAD)" = \
|
||||
3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
3rd line 2nd file
|
||||
EOF
|
||||
test_expect_success \
|
||||
'changing files and redo the last commit should succeed' '
|
||||
echo "3rd line 2nd file" >>secondfile &&
|
||||
git commit -a -C ORIG_HEAD &&
|
||||
check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d &&
|
||||
test "$(git rev-parse ORIG_HEAD)" = \
|
||||
3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
first:
|
||||
1st file
|
||||
2nd line 1st file
|
||||
second:
|
||||
2nd file
|
||||
EOF
|
||||
test_expect_success \
|
||||
'--hard reset should change the files and undo commits permanently' '
|
||||
git reset --hard HEAD~2 &&
|
||||
check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
|
||||
test "$(git rev-parse ORIG_HEAD)" = \
|
||||
3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
cat >.cached_expect <<EOF
|
||||
diff --git a/first b/first
|
||||
deleted file mode 100644
|
||||
index 8206c22..0000000
|
||||
--- a/first
|
||||
+++ /dev/null
|
||||
@@ -1,2 +0,0 @@
|
||||
-1st file
|
||||
-2nd line 1st file
|
||||
diff --git a/second b/second
|
||||
deleted file mode 100644
|
||||
index 1bbba79..0000000
|
||||
--- a/second
|
||||
+++ /dev/null
|
||||
@@ -1 +0,0 @@
|
||||
-2nd file
|
||||
diff --git a/secondfile b/secondfile
|
||||
new file mode 100644
|
||||
index 0000000..44c5b58
|
||||
--- /dev/null
|
||||
+++ b/secondfile
|
||||
@@ -0,0 +1,2 @@
|
||||
+1st line 2nd file
|
||||
+2nd line 2nd file
|
||||
EOF
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
test_expect_success \
|
||||
'redoing changes adding them without commit them should succeed' '
|
||||
git rm first &&
|
||||
git mv second secondfile &&
|
||||
|
||||
echo "1st line 2nd file" >secondfile &&
|
||||
echo "2nd line 2nd file" >>secondfile &&
|
||||
git add secondfile &&
|
||||
check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
|
||||
'
|
||||
|
||||
cat >.diff_expect <<EOF
|
||||
diff --git a/first b/first
|
||||
deleted file mode 100644
|
||||
index 8206c22..0000000
|
||||
--- a/first
|
||||
+++ /dev/null
|
||||
@@ -1,2 +0,0 @@
|
||||
-1st file
|
||||
-2nd line 1st file
|
||||
diff --git a/second b/second
|
||||
deleted file mode 100644
|
||||
index 1bbba79..0000000
|
||||
--- a/second
|
||||
+++ /dev/null
|
||||
@@ -1 +0,0 @@
|
||||
-2nd file
|
||||
EOF
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
test_expect_success '--mixed reset to HEAD should unadd the files' '
|
||||
git reset &&
|
||||
check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
|
||||
test "$(git rev-parse ORIG_HEAD)" = \
|
||||
ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
test_expect_success 'redoing the last two commits should succeed' '
|
||||
git add secondfile &&
|
||||
git reset --hard ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
|
||||
|
||||
git rm first &&
|
||||
git mv second secondfile &&
|
||||
git commit -a -m "remove 1st and rename 2nd" &&
|
||||
|
||||
echo "1st line 2nd file" >secondfile &&
|
||||
echo "2nd line 2nd file" >>secondfile &&
|
||||
git commit -a -m "modify 2nd file" &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
3rd line in branch2
|
||||
EOF
|
||||
test_expect_success '--hard reset to HEAD should clear a failed merge' '
|
||||
git branch branch1 &&
|
||||
git branch branch2 &&
|
||||
|
||||
git checkout branch1 &&
|
||||
echo "3rd line in branch1" >>secondfile &&
|
||||
git commit -a -m "change in branch1" &&
|
||||
|
||||
git checkout branch2 &&
|
||||
echo "3rd line in branch2" >>secondfile &&
|
||||
git commit -a -m "change in branch2" &&
|
||||
|
||||
! git pull . branch1 &&
|
||||
git reset --hard &&
|
||||
check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb
|
||||
'
|
||||
|
||||
>.diff_expect
|
||||
>.cached_expect
|
||||
cat >.cat_expect <<EOF
|
||||
secondfile:
|
||||
1st line 2nd file
|
||||
2nd line 2nd file
|
||||
EOF
|
||||
test_expect_success \
|
||||
'--hard reset to ORIG_HEAD should clear a fast-forward merge' '
|
||||
git reset --hard HEAD^ &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
|
||||
|
||||
git pull . branch1 &&
|
||||
git reset --hard ORIG_HEAD &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
|
||||
|
||||
git checkout master &&
|
||||
git branch -D branch1 branch2 &&
|
||||
check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
|
||||
'
|
||||
|
||||
cat > expect << EOF
|
||||
diff --git a/file1 b/file1
|
||||
index d00491f..7ed6ff8 100644
|
||||
--- a/file1
|
||||
+++ b/file1
|
||||
@@ -1 +1 @@
|
||||
-1
|
||||
+5
|
||||
diff --git a/file2 b/file2
|
||||
deleted file mode 100644
|
||||
index 0cfbf08..0000000
|
||||
--- a/file2
|
||||
+++ /dev/null
|
||||
@@ -1 +0,0 @@
|
||||
-2
|
||||
EOF
|
||||
cat > cached_expect << EOF
|
||||
diff --git a/file4 b/file4
|
||||
new file mode 100644
|
||||
index 0000000..b8626c4
|
||||
--- /dev/null
|
||||
+++ b/file4
|
||||
@@ -0,0 +1 @@
|
||||
+4
|
||||
EOF
|
||||
test_expect_success 'test --mixed <paths>' '
|
||||
echo 1 > file1 &&
|
||||
echo 2 > file2 &&
|
||||
git add file1 file2 &&
|
||||
test_tick &&
|
||||
git commit -m files &&
|
||||
git rm file2 &&
|
||||
echo 3 > file3 &&
|
||||
echo 4 > file4 &&
|
||||
echo 5 > file1 &&
|
||||
git add file1 file3 file4 &&
|
||||
! git reset HEAD -- file1 file2 file3 &&
|
||||
git diff > output &&
|
||||
git diff output expect &&
|
||||
git diff --cached > output &&
|
||||
git diff output cached_expect
|
||||
'
|
||||
|
||||
test_expect_success 'test resetting the index at give paths' '
|
||||
|
||||
mkdir sub &&
|
||||
>sub/file1 &&
|
||||
>sub/file2 &&
|
||||
git update-index --add sub/file1 sub/file2 &&
|
||||
T=$(git write-tree) &&
|
||||
! git reset HEAD sub/file2 &&
|
||||
U=$(git write-tree) &&
|
||||
echo "$T" &&
|
||||
echo "$U" &&
|
||||
! git diff-index --cached --exit-code "$T" &&
|
||||
test "$T" != "$U"
|
||||
|
||||
'
|
||||
|
||||
test_done
|
|
@ -131,4 +131,36 @@ test_expect_success \
|
|||
'validate git-rev-list output.' \
|
||||
'diff current expected'
|
||||
|
||||
test_expect_success 'partial commit that involves removal (1)' '
|
||||
|
||||
git rm --cached file &&
|
||||
mv file elif &&
|
||||
git add elif &&
|
||||
git commit -m "Partial: add elif" elif &&
|
||||
git diff-tree --name-status HEAD^ HEAD >current &&
|
||||
echo "A elif" >expected &&
|
||||
diff expected current
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'partial commit that involves removal (2)' '
|
||||
|
||||
git commit -m "Partial: remove file" file &&
|
||||
git diff-tree --name-status HEAD^ HEAD >current &&
|
||||
echo "D file" >expected &&
|
||||
diff expected current
|
||||
|
||||
'
|
||||
|
||||
test_expect_success 'partial commit that involves removal (3)' '
|
||||
|
||||
git rm --cached elif &&
|
||||
echo elif >elif &&
|
||||
git commit -m "Partial: modify elif" elif &&
|
||||
git diff-tree --name-status HEAD^ HEAD >current &&
|
||||
echo "M elif" >expected &&
|
||||
diff expected current
|
||||
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -58,6 +58,14 @@ gitweb_run () {
|
|||
# gitweb.log is left for debugging
|
||||
}
|
||||
|
||||
safe_chmod () {
|
||||
chmod "$1" "$2" &&
|
||||
if [ "$(git config --get core.filemode)" = false ]
|
||||
then
|
||||
git update-index --chmod="$1" "$2"
|
||||
fi
|
||||
}
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
|
||||
|
@ -229,7 +237,7 @@ test_debug 'cat gitweb.log'
|
|||
|
||||
test_expect_success \
|
||||
'commitdiff(0): mode change' \
|
||||
'chmod a+x new_file &&
|
||||
'safe_chmod +x new_file &&
|
||||
git commit -a -m "Mode changed." &&
|
||||
gitweb_run "p=.git;a=commitdiff"'
|
||||
test_debug 'cat gitweb.log'
|
||||
|
@ -268,7 +276,7 @@ test_debug 'cat gitweb.log'
|
|||
test_expect_success \
|
||||
'commitdiff(0): mode change and modified' \
|
||||
'echo "New line" >> file2 &&
|
||||
chmod a+x file2 &&
|
||||
safe_chmod +x file2 &&
|
||||
git commit -a -m "Mode change and modification." &&
|
||||
gitweb_run "p=.git;a=commitdiff"'
|
||||
test_debug 'cat gitweb.log'
|
||||
|
@ -295,7 +303,7 @@ test_expect_success \
|
|||
'commitdiff(0): renamed, mode change and modified' \
|
||||
'git mv file3 file2 &&
|
||||
echo "Propter nomen suum." >> file2 &&
|
||||
chmod a+x file2 &&
|
||||
safe_chmod +x file2 &&
|
||||
git commit -a -m "File rename, mode change and modification." &&
|
||||
gitweb_run "p=.git;a=commitdiff"'
|
||||
test_debug 'cat gitweb.log'
|
||||
|
@ -412,10 +420,10 @@ test_expect_success \
|
|||
git add 03-new &&
|
||||
git mv 04-rename-from 04-rename-to &&
|
||||
echo "Changed" >> 04-rename-to &&
|
||||
chmod a+x 05-mode-change &&
|
||||
safe_chmod +x 05-mode-change &&
|
||||
rm -f 06-file-or-symlink && ln -s 01-change 06-file-or-symlink &&
|
||||
echo "Changed and have mode changed" > 07-change-mode-change &&
|
||||
chmod a+x 07-change-mode-change &&
|
||||
safe_chmod +x 07-change-mode-change &&
|
||||
git commit -a -m "Large commit" &&
|
||||
git checkout master'
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ fi
|
|||
|
||||
# --- Check types
|
||||
# if $newrev is 0000...0000, it's a commit to delete a branch
|
||||
if [ -z "${newrev##0*}" ]; then
|
||||
if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
|
||||
newrev_type=commit
|
||||
else
|
||||
newrev_type=$(git-cat-file -t $newrev)
|
||||
|
|
|
@ -227,6 +227,7 @@ static void wt_status_print_updated(struct wt_status *s)
|
|||
rev.diffopt.format_callback = wt_status_print_updated_cb;
|
||||
rev.diffopt.format_callback_data = s;
|
||||
rev.diffopt.detect_rename = 1;
|
||||
rev.diffopt.rename_limit = 100;
|
||||
wt_read_cache(s);
|
||||
run_diff_index(&rev, 1);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче