git: got git?
Add a snapshot of git9 to 9front.
This commit is contained in:
parent
013b2cad19
commit
1ee1bfaa8c
36 changed files with 9308 additions and 0 deletions
643
sys/man/1/git
Normal file
643
sys/man/1/git
Normal file
|
@ -0,0 +1,643 @@
|
|||
.TH GIT 1
|
||||
.SH NAME
|
||||
git, git/conf, git/query, git/walk, git/clone, git/branch,
|
||||
git/commit, git/diff, git/init, git/log, git/merge, git/push,
|
||||
git/pull, git/rm, git/serve
|
||||
\- Manage git repositories.
|
||||
|
||||
.SH SYNOPSIS
|
||||
.PP
|
||||
.B git/add
|
||||
[
|
||||
.B -r
|
||||
]
|
||||
.I path...
|
||||
.PP
|
||||
.B git/rm
|
||||
.I path...
|
||||
.PP
|
||||
.B git/branch
|
||||
[
|
||||
.B -adns
|
||||
]
|
||||
[
|
||||
.B -b
|
||||
.I base
|
||||
]
|
||||
.I newbranch
|
||||
.PP
|
||||
.B git/clone
|
||||
[
|
||||
.I remote
|
||||
[
|
||||
.I local
|
||||
]
|
||||
]
|
||||
.PP
|
||||
.B git/commit
|
||||
[
|
||||
.B -re
|
||||
]
|
||||
[
|
||||
.B -m msg
|
||||
]
|
||||
[
|
||||
.I file...
|
||||
]
|
||||
.PP
|
||||
.B git/compat
|
||||
.PP
|
||||
.B git/conf
|
||||
[
|
||||
.B -r
|
||||
]
|
||||
[
|
||||
.B -f
|
||||
.I file
|
||||
]
|
||||
.I keys...
|
||||
.PP
|
||||
.B git/diff
|
||||
[
|
||||
.B -c
|
||||
.I branch
|
||||
]
|
||||
[
|
||||
.B -s
|
||||
]
|
||||
[
|
||||
.I file...
|
||||
]
|
||||
.PP
|
||||
.B git/revert
|
||||
[
|
||||
.B -c
|
||||
.I commit
|
||||
]
|
||||
.I file...
|
||||
.PP
|
||||
.B git/export
|
||||
[
|
||||
.I commits...
|
||||
]
|
||||
.PP
|
||||
.B git/import
|
||||
[
|
||||
.I commits...
|
||||
]
|
||||
.PP
|
||||
.B git/init
|
||||
[
|
||||
.B -b
|
||||
]
|
||||
[
|
||||
.I dir
|
||||
]
|
||||
[
|
||||
.B -u
|
||||
.I upstream
|
||||
]
|
||||
.PP
|
||||
.B git/log
|
||||
[
|
||||
.B -c
|
||||
.I commit
|
||||
.B | -e
|
||||
.I expr
|
||||
]
|
||||
[
|
||||
.B -s
|
||||
]
|
||||
[
|
||||
.I files...
|
||||
]
|
||||
.PP
|
||||
.B git/merge
|
||||
.I theirs
|
||||
.PP
|
||||
.B git/rebase
|
||||
[
|
||||
.B -ari
|
||||
]
|
||||
[
|
||||
.B onto
|
||||
]
|
||||
.PP
|
||||
.B git/pull
|
||||
[
|
||||
.B -f
|
||||
]
|
||||
[
|
||||
.B -q
|
||||
]
|
||||
[
|
||||
.B -a
|
||||
]
|
||||
[
|
||||
.B -u
|
||||
.I upstream
|
||||
]
|
||||
.PP
|
||||
.B git/push
|
||||
[
|
||||
.B -a
|
||||
]
|
||||
[
|
||||
.B -u
|
||||
.I upstream
|
||||
]
|
||||
[
|
||||
.B -b
|
||||
.I branch
|
||||
]
|
||||
[
|
||||
.B -r
|
||||
.I branch
|
||||
]
|
||||
.PP
|
||||
.B git/serve
|
||||
[
|
||||
.B -w
|
||||
]
|
||||
[
|
||||
.B -r
|
||||
.I path
|
||||
]
|
||||
.PP
|
||||
.B git/query
|
||||
[
|
||||
.B -pcr
|
||||
]
|
||||
.I query
|
||||
.PP
|
||||
.B git/walk
|
||||
[
|
||||
.B -qc
|
||||
]
|
||||
[
|
||||
.B -b
|
||||
.I branch
|
||||
]
|
||||
[
|
||||
.B -f
|
||||
.I filters
|
||||
]
|
||||
[
|
||||
.I [files...]
|
||||
]
|
||||
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
Git is a distributed version control system.
|
||||
This means that each repository contains a full copy of the history.
|
||||
This history is then synced between computers as needed.
|
||||
|
||||
.PP
|
||||
These programs provide tools to manage and interoperate with
|
||||
repositories hosted in git.
|
||||
|
||||
.SH CONCEPTS
|
||||
|
||||
Git stores snapshots of the working directory.
|
||||
Files can either be in a tracked or untracked state.
|
||||
Each commit takes the current version of all tracked files and
|
||||
adds them to a new commit.
|
||||
|
||||
This history is stored in the
|
||||
.I .git
|
||||
directory.
|
||||
This suite of
|
||||
.I git
|
||||
tools provides a file interface to the
|
||||
.I .git
|
||||
directory mounted on
|
||||
.I /mnt/git.
|
||||
Modifications to the repository are done directly to the
|
||||
.I .git
|
||||
directory, and are reflected in the file system interface.
|
||||
This allows for easy scripting, without excessive complexity
|
||||
in the file API.
|
||||
|
||||
.SH COMMANDS
|
||||
|
||||
.PP
|
||||
.B Git/init
|
||||
is used to create a new git repository, with no code or commits.
|
||||
The repository is created in the current directory by default.
|
||||
Passing a directory name will cause the repository to be created
|
||||
there instead.
|
||||
Passing the
|
||||
.B -b
|
||||
option will cause the repository to be initialized as a bare repository.
|
||||
Passing the
|
||||
.B -u
|
||||
.I upstream
|
||||
option will cause the upstream to be configured to
|
||||
.I upstream.
|
||||
|
||||
.PP
|
||||
.B Git/clone
|
||||
will take an existing repository, served over either the
|
||||
.I git://
|
||||
or
|
||||
.I ssh://
|
||||
protocols.
|
||||
The first argument is the repository to clone.
|
||||
The second argument, optionally, specifies the location to clone into.
|
||||
If not specified, the repository will be cloned into the last path component
|
||||
of the clone source, with the
|
||||
.I .git
|
||||
stripped off if present.
|
||||
|
||||
.PP
|
||||
.B Git/push
|
||||
is used to push the current changes to a remote repository.
|
||||
When no arguments are provided, the remote repository is taken from
|
||||
the origin configured in
|
||||
.I .git/config,
|
||||
and only the changes on the current branch are pushed.
|
||||
When passed the
|
||||
.I -a
|
||||
option, all branches are pushed.
|
||||
When passed the
|
||||
.I -u upstream
|
||||
option, the changed are pushed to
|
||||
.I upstream
|
||||
instead of the configured origin.
|
||||
When given the
|
||||
.I -r
|
||||
option, the branch is deleted from origin, instead of updated.
|
||||
|
||||
.PP
|
||||
.B Git/revert
|
||||
restores the named files from HEAD. When passed the -c flag, restores files from
|
||||
the named commit.
|
||||
|
||||
.PP
|
||||
.B Git/pull
|
||||
behaves in a similar manner to git/push, however it gets changes from
|
||||
the upstream repository.
|
||||
After fetching, it checks out the changes into the working directory.
|
||||
When passed the
|
||||
.I -f
|
||||
option, the update of the working copy is suppressed.
|
||||
When passed the
|
||||
.I -u upstream
|
||||
option, the changes are pulled from
|
||||
.I upstream
|
||||
instead of the configured origin.
|
||||
|
||||
.PP
|
||||
.B Git/serve
|
||||
serves repositories using the
|
||||
.I git://
|
||||
protocol over stdin.
|
||||
By default, it serves them read-only.
|
||||
The
|
||||
.I -w
|
||||
flag, it allows pushing into repositories.
|
||||
The
|
||||
.I -r
|
||||
.B path
|
||||
flag serves repositories relative to
|
||||
.BR path .
|
||||
|
||||
.PP
|
||||
.B Git/fs
|
||||
serves a file system on /mnt/git.
|
||||
For full documentation, see
|
||||
.IR gitfs (4)
|
||||
|
||||
.PP
|
||||
.B Git/add
|
||||
adds a file to the list of tracked files. When passed the
|
||||
.I -r
|
||||
flag, the file is removed from the list of tracked files.
|
||||
The copy of the file in the repository is left untouched.
|
||||
.PP
|
||||
.B Git/rm
|
||||
is an alias for
|
||||
.IR git/add -r .
|
||||
|
||||
.PP
|
||||
.B Git/commit
|
||||
creates a new commit consisting of all changes to the specified files.
|
||||
By default, an editor is opened to prepare the commit message.
|
||||
The
|
||||
.I -m
|
||||
flag supplies the commit message directly.
|
||||
The
|
||||
.I -r
|
||||
flag revises the contents of the previous commit, reusing the message.
|
||||
The
|
||||
.I -e
|
||||
flag opens an editor to finalize the commit message, regardless of
|
||||
whether or not it was specified explicitly or reused.
|
||||
To amend a commit message,
|
||||
.I -r
|
||||
can be used in conjuction with
|
||||
.I -m
|
||||
or
|
||||
.IR -e .
|
||||
|
||||
.PP
|
||||
.B Git/branch
|
||||
is used to list or switch branches.
|
||||
When invoked with no arguments, it lists the current branch.
|
||||
To list all branches, pass the
|
||||
.I -a
|
||||
option.
|
||||
To switch between branches, pass a branch name.
|
||||
When passed the
|
||||
.I -n
|
||||
option, the branch will be created, overwriting existing branch.
|
||||
When passed the
|
||||
.I -b base
|
||||
option, the branch created is based off of
|
||||
.I base
|
||||
instead of
|
||||
.I HEAD.
|
||||
When passed the
|
||||
.I -s
|
||||
option, the branch is created but the files are not checked out.
|
||||
When passed the
|
||||
.I -d
|
||||
option, the branch is deleted.
|
||||
|
||||
.PP
|
||||
.B Git/log
|
||||
shows a history of the current branch.
|
||||
When passed a list of files, only commits affecting
|
||||
those files are shown.
|
||||
The
|
||||
.I -c commit
|
||||
option logs starting from the provided commit, instead of HEAD.
|
||||
The
|
||||
.I -s
|
||||
option shows a summary of the commit, instead of the full message.
|
||||
The
|
||||
.I -e expr
|
||||
option shows commits matching the query expression provided.
|
||||
The expression is in the syntax of
|
||||
.B git/query.
|
||||
|
||||
.PP
|
||||
.B Git/diff
|
||||
shows the differences between the currently checked out code and
|
||||
the
|
||||
.I HEAD
|
||||
commit.
|
||||
When passed the
|
||||
.I -c base
|
||||
option, the diff is computed against
|
||||
.I base
|
||||
instead of
|
||||
.I HEAD.
|
||||
When passed the
|
||||
.I -s
|
||||
option, only the file statuses are
|
||||
printed.
|
||||
|
||||
.PP
|
||||
.B Git/export
|
||||
exports a list of commits in a format that
|
||||
.B git/import
|
||||
can apply.
|
||||
|
||||
.PP
|
||||
.B Git/import
|
||||
imports a commit with message, author, and
|
||||
date information.
|
||||
|
||||
.PP
|
||||
.B Git/merge
|
||||
takes two branches and merges them filewise using
|
||||
.I ape/diff3.
|
||||
The next commit made will be a merge commmit.
|
||||
|
||||
.PP
|
||||
.B Git/rebase
|
||||
takes one branch and moves it onto another.
|
||||
On error, the remaining commits to rebase are
|
||||
saved, and can be resumed once the conflict is
|
||||
resolved using the
|
||||
.I -r
|
||||
option.
|
||||
If the rebase is to be aborted, the
|
||||
.I -a
|
||||
option will clean up the in progress rebase
|
||||
and reset the state of the branch.
|
||||
The
|
||||
.I -i
|
||||
option will open an editor to modify the todo-list before the rebase
|
||||
begins.
|
||||
|
||||
.PP
|
||||
The following rebase commands are supported:
|
||||
.TP 10
|
||||
.B pick
|
||||
Apply the commit.
|
||||
.TP
|
||||
.B reword
|
||||
Apply the commit, then edit its commit message.
|
||||
.TP
|
||||
.B edit
|
||||
Apply the commit, then exit to allow further changes.
|
||||
.TP
|
||||
.B squash
|
||||
Fold the commit into the previous commit, then edit the combined
|
||||
commit message.
|
||||
.TP
|
||||
.B fixup
|
||||
Fold the commit into the previous commit, discarding its commit
|
||||
message.
|
||||
.TP
|
||||
.B break
|
||||
Exit to allow for manual edits or inspection before continuing.
|
||||
|
||||
.PP
|
||||
.B Git/conf
|
||||
is a tool for querying the git configuration.
|
||||
The configuration key is provided as a dotted string. Spaces
|
||||
are accepted. For example, to find the URL of the origin
|
||||
repository, one might pass
|
||||
.I 'remote "origin".url".
|
||||
When given the
|
||||
.I -r
|
||||
option, the root of the current repository is printed.
|
||||
|
||||
.B Git/query
|
||||
takes an expression describing a commit, or set of commits,
|
||||
and resolves it to a list of commits.
|
||||
The
|
||||
.I -r
|
||||
option reverses the order of the commit list.
|
||||
With the
|
||||
.I -p
|
||||
option, instead of printing the commit hashes, the full
|
||||
path to their
|
||||
.B git/fs
|
||||
path is printed. With the
|
||||
.I -c
|
||||
option, the query must resolve to two commits. The blobs
|
||||
that have changed in the commits are printed.
|
||||
|
||||
.PP
|
||||
.B Git/walk
|
||||
provides a tool for walking the list of tracked objects and printing their status.
|
||||
With no arguments, it prints a list of paths prefixed with the status character.
|
||||
When given the
|
||||
.I -c
|
||||
character, only the paths are printed.
|
||||
When given the
|
||||
.I -q
|
||||
option, all output is suppressed, and only the status is printed.
|
||||
When given the
|
||||
.I -f
|
||||
option, the output is filtered by status code, and only matching items are printed.
|
||||
|
||||
.PP
|
||||
The status characters are as follows:
|
||||
.TP
|
||||
T
|
||||
Tracked, not modified since last commit.
|
||||
.TP
|
||||
M
|
||||
Modified since last commit.
|
||||
.TP
|
||||
R
|
||||
Removed from either working directory tracking list.
|
||||
.TP
|
||||
A
|
||||
Added, does not yet exist in a commit.
|
||||
|
||||
.PP
|
||||
.B Git/compat
|
||||
spawns an rc subshell with a compatibility stub in
|
||||
.IR $path .
|
||||
This compatibility stub provides enough of the unix
|
||||
.I git
|
||||
commands to run tools like
|
||||
.I go get
|
||||
but not much more.
|
||||
|
||||
.SH REF SYNTAX
|
||||
|
||||
.PP
|
||||
Refs are specified with a simple query syntax.
|
||||
A bare hash always evaluates to itself.
|
||||
Ref names are resolved to their hashes.
|
||||
The
|
||||
.B a ^
|
||||
suffix operator finds the parent of a commit.
|
||||
The
|
||||
.B a b @
|
||||
suffix operator finds the common ancestor of the previous two commits.
|
||||
The
|
||||
.B a .. b
|
||||
or
|
||||
.B a : b
|
||||
operator finds all commits between
|
||||
.B a
|
||||
and
|
||||
.B b.
|
||||
Between is defined as the set of all commits which are reachable from
|
||||
.B b
|
||||
but not reachable from
|
||||
.B a.
|
||||
|
||||
.SH PROTOCOLS
|
||||
.PP
|
||||
Git9 supports URL schemes of the format
|
||||
.BR transport://dial/repo/path .
|
||||
The transport portion specifies the protocol to use.
|
||||
If the transport portion is omitted, then the transport used is
|
||||
.BR ssh .
|
||||
The
|
||||
.I dial
|
||||
portion is either a plan 9 dial string, or a conventional
|
||||
.I host:port
|
||||
pair.
|
||||
For the ssh protocol, it may also include a
|
||||
.I user@
|
||||
prefix.
|
||||
.I repo/path
|
||||
portion is the path of the repository on the server.
|
||||
|
||||
The supported transports are
|
||||
.B ssh://, git://, hjgit://, gits://, http://,
|
||||
and
|
||||
.BR https .
|
||||
Two of these are specific to git9:
|
||||
.I gits://
|
||||
and
|
||||
.IR hjgit:// .
|
||||
Both are the
|
||||
.I git://
|
||||
protocol, tunnelled over tls.
|
||||
.I Hjgit://
|
||||
authenticates with the server using Plan 9 authentication,
|
||||
using
|
||||
.IR tlsclient\ -a .
|
||||
Any of these protocol names may be prefixed with
|
||||
.IR git+ ,
|
||||
for copy-paste compatibility with Unix git.
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
.PP
|
||||
In order to create a new repository, run
|
||||
.B git/init:
|
||||
.PP
|
||||
.EX
|
||||
git/init myrepo
|
||||
.EE
|
||||
|
||||
.PP
|
||||
To clone an existing repository from a git server, run:
|
||||
.PP
|
||||
.EX
|
||||
git/clone git://github.com/Harvey-OS/harvey
|
||||
cd harvey
|
||||
# edit files
|
||||
git/commit foo.c
|
||||
git/push
|
||||
.EE
|
||||
|
||||
.PP
|
||||
To set a user and email for commits, run:
|
||||
.PP
|
||||
.EX
|
||||
% mkdir $home/lib/git
|
||||
% >$home/lib/git/config echo '
|
||||
[user]
|
||||
name = Ori Bernstein
|
||||
email = ori@eigenstate.org'
|
||||
.EE
|
||||
|
||||
.SH FILES
|
||||
.TP
|
||||
$repo/.git
|
||||
The full git repository.
|
||||
.TP
|
||||
$repo/.git/config
|
||||
The configuration file for a repository.
|
||||
.TP
|
||||
$home/lib/git/config
|
||||
The global configuration for git.
|
||||
The contents of this file are used as fallbacks for the per-repository config.
|
||||
|
||||
.SH SEE ALSO
|
||||
.IR hg (1)
|
||||
.IR replica (1)
|
||||
.IR patch (1)
|
||||
.IR gitfs (4)
|
||||
.IR diff3
|
||||
|
||||
.SH BUGS
|
||||
.PP
|
||||
Repositories with submodules are effectively read-only.
|
||||
.PP
|
||||
There are a some of missing commands, features, and tools, such as git/rebase
|
||||
.PP
|
||||
git/compat only works within a git repository.
|
112
sys/man/4/gitfs
Normal file
112
sys/man/4/gitfs
Normal file
|
@ -0,0 +1,112 @@
|
|||
.TH GITFS 4
|
||||
.SH NAME
|
||||
git/fs \- git file server
|
||||
|
||||
.SH SYNOPSIS
|
||||
|
||||
git/fs
|
||||
[
|
||||
.B -d
|
||||
]
|
||||
[
|
||||
.B -m
|
||||
.I mtpt
|
||||
]
|
||||
|
||||
.SH DESCRIPTION
|
||||
|
||||
.PP
|
||||
Git/fs serves a file system interface to a git repository in the
|
||||
current directory.
|
||||
This file system provides a read-only view into the repository contents.
|
||||
By default, it is mounted on
|
||||
.B /mnt/git.
|
||||
It does not cache mutable data, so any changes to the git repository will immediately be reflected in git/fs.
|
||||
|
||||
.PP
|
||||
Git/fs serves a few levels of hierarchy.
|
||||
The top level contains the following files and directories:
|
||||
|
||||
.TP
|
||||
.B branch
|
||||
Exposes branches. Branches are aliases for commit objects.
|
||||
|
||||
.TP
|
||||
.B object
|
||||
Exposes all objects in the git repository.
|
||||
Objects may be commits, trees, or blobs.
|
||||
|
||||
.TP
|
||||
.B HEAD
|
||||
This is an alias for the current commit.
|
||||
|
||||
.PP
|
||||
Commits are also represented as small hierarchies. They contain
|
||||
the following files:
|
||||
|
||||
.TP
|
||||
.B author
|
||||
This is the author of the commit.
|
||||
The contents of this file are free-form text, but conventionally
|
||||
they take the form
|
||||
.B Full Name <email@domain.here>
|
||||
|
||||
.TP
|
||||
.B hash
|
||||
The commit id of the current branch
|
||||
|
||||
.TP
|
||||
.B msg
|
||||
The full text of the commit message.
|
||||
|
||||
.TP
|
||||
.B parent
|
||||
The list of parent commit ids of the current commit.
|
||||
One parent is listed per line.
|
||||
|
||||
.TP
|
||||
.B tree
|
||||
A directory containing the tree associated with the
|
||||
commit.
|
||||
The timestamp of the files contained within this
|
||||
hierarchy are the same as the date of the commit.
|
||||
|
||||
.PP
|
||||
Trees are presented as directory listings, and blobs
|
||||
as files.
|
||||
|
||||
.SH FILES
|
||||
.TP
|
||||
.B .git
|
||||
The git repository being expected.
|
||||
.TP
|
||||
.B .git/HEAD
|
||||
A reference to the current HEAD.
|
||||
Used to populate
|
||||
.B /mnt/git/HEAD
|
||||
.TP
|
||||
.git/config
|
||||
The per-repository configuation for git tools.
|
||||
.TP
|
||||
.B $home/lib/git/config
|
||||
The global configuration for git tools.
|
||||
|
||||
.SH SOURCE
|
||||
.TP
|
||||
.B /sys/src/cmd/git/fs.c
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.IR git (1)
|
||||
.IR hg (1)
|
||||
.IR hgfs (4)
|
||||
|
||||
.SH BUGS
|
||||
Symlinks are only partially supported.
|
||||
Symlinks are treated as regular files when reading.
|
||||
Modifying symlinks is unsupported.
|
||||
|
||||
.PP
|
||||
There is no way to inspect the raw objects. This is
|
||||
a feature that would be useful for debugging.
|
||||
|
||||
|
39
sys/src/cmd/git/add
Executable file
39
sys/src/cmd/git/add
Executable file
|
@ -0,0 +1,39 @@
|
|||
#!/bin/rc -e
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='r:remove'; args='file ...'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
add='tracked'
|
||||
del='removed'
|
||||
if(~ $remove 1){
|
||||
add='removed'
|
||||
del='tracked'
|
||||
}
|
||||
if(~ $#* 0)
|
||||
exec aux/usage
|
||||
|
||||
if(~ $add tracked)
|
||||
files=`$nl{walk -f $gitrel/$*}
|
||||
if not
|
||||
files=`$nl{cd .git/index9/tracked/ && walk -f $gitrel/$*}
|
||||
|
||||
for(f in $files){
|
||||
if(! ~ `{cleanname $f} .git/*){
|
||||
addpath=.git/index9/$add/$f
|
||||
delpath=.git/index9/$del/$f
|
||||
mkdir -p `{basename -d $addpath}
|
||||
mkdir -p `{basename -d $delpath}
|
||||
# We don't want a matching qid, so that
|
||||
# git/walk doesn't think this came from
|
||||
# a checkout.
|
||||
if(! test -e $addpath)
|
||||
if(~ $add 'tracked' || test -e /mnt/git/HEAD/tree/$f)
|
||||
touch $addpath
|
||||
rm -f $delpath
|
||||
}
|
||||
}
|
||||
exit ''
|
109
sys/src/cmd/git/branch
Executable file
109
sys/src/cmd/git/branch
Executable file
|
@ -0,0 +1,109 @@
|
|||
#!/bin/rc -e
|
||||
rfork en
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='a:listall, b:baseref ref, d:delete, n:newbr, s:stay, m:merge'
|
||||
args='[branch]'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
modified=()
|
||||
deleted=()
|
||||
|
||||
if(~ $#* 0){
|
||||
if(~ $#listall 0)
|
||||
awk '$1=="branch"{print $2}' < /mnt/git/ctl
|
||||
if not
|
||||
cd .git/refs/ && walk -f heads remotes
|
||||
exit
|
||||
}
|
||||
if(! ~ $#* 1)
|
||||
exec aux/usage
|
||||
|
||||
branch=$1
|
||||
if(~ $branch refs/heads/*)
|
||||
new=$name
|
||||
if not if(~ $branch heads/*)
|
||||
new=refs/$branch
|
||||
if not
|
||||
new=refs/heads/$branch
|
||||
|
||||
orig=`{git/query HEAD}
|
||||
if (~ $#baseref 1)
|
||||
base=`{git/query $baseref} || exit 'bad base'
|
||||
if not if(test -e .git/$new)
|
||||
base=`{git/query $new}
|
||||
if not
|
||||
base=`{git/query HEAD}
|
||||
|
||||
modified=`$nl{git/query -c HEAD $base | grep '^[^-]' | subst '^..'}
|
||||
deleted=`$nl{git/query -c HEAD $base | grep '^-' | subst '^..'}
|
||||
|
||||
if(! ~ $#modified 0 || ! ~ $#deleted 0 && ~ $#merge 0){
|
||||
git/walk -fRMA $modified $deleted ||
|
||||
die 'uncommited changes would be clobbered'
|
||||
}
|
||||
if(~ $delete 1){
|
||||
rm -f .git/$new
|
||||
echo 'deleted branch' $new
|
||||
exit
|
||||
}
|
||||
if(~ $#newbr 0){
|
||||
if(! ~ $#baseref 0)
|
||||
die update would clobber $branch with $baseref
|
||||
baseref=`$nl{echo -n $new | sed s@refs/heads/@refs/remotes/origin/@}
|
||||
if(! test -e .git/$new)
|
||||
if(! base=`{git/query $baseref})
|
||||
die could not find branch $branch
|
||||
}
|
||||
commit=`{git/query $base} || die 'branch does not exist:' $base
|
||||
if(~ $new */*)
|
||||
mkdir -p .git/`{basename -d $new}
|
||||
echo $commit > .git/$new
|
||||
if(! ~ $#stay 0)
|
||||
exit
|
||||
|
||||
basedir=`{git/query -p $base}
|
||||
dirtypaths=()
|
||||
cleanpaths=($modified $deleted)
|
||||
if(! ~ $#modified 0 || ! ~ $#deleted 0)
|
||||
dirtypaths=`$nl{git/walk -cfRMA $modified $deleted}
|
||||
if(! ~ $#dirtypaths 0){
|
||||
x=$nl^$cleanpaths
|
||||
y=$nl^$dirtypaths
|
||||
cleanpaths=`$nl{echo $"x$nl$"y | sort | uniq -u}
|
||||
}
|
||||
for(m in $cleanpaths){
|
||||
d=`{basename -d $m}
|
||||
mkdir -p $d
|
||||
mkdir -p .git/index9/tracked/$d
|
||||
# Modifications can turn a file into
|
||||
# a directory, or vice versa, so we
|
||||
# need to delete and copy the files
|
||||
# over.
|
||||
a=`{test -f $m && echo file || echo dir}
|
||||
b=`{test -f $basedir/tree/$m && echo file || echo dir}
|
||||
if(! ~ $a $b){
|
||||
rm -rf $m
|
||||
rm -rf .git/index9/tracked/$m
|
||||
}
|
||||
if(test -f $basedir/tree/$m){
|
||||
cp $basedir/tree/$m $m
|
||||
walk -eq $m > .git/index9/tracked/$m
|
||||
}
|
||||
}
|
||||
|
||||
for(ours in $dirtypaths){
|
||||
common=/mnt/git/object/$orig/tree/$ours
|
||||
theirs=/mnt/git/object/$base/tree/$ours
|
||||
merge1 $ours $ours $common $theirs
|
||||
}
|
||||
|
||||
if(! ~ $#deleted 0){
|
||||
rm -f $deleted
|
||||
rm -f .git/index9/tracked/$deleted
|
||||
}
|
||||
|
||||
echo ref: $new > .git/HEAD
|
||||
exit ''
|
115
sys/src/cmd/git/clone
Executable file
115
sys/src/cmd/git/clone
Executable file
|
@ -0,0 +1,115 @@
|
|||
#!/bin/rc
|
||||
rfork en
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
flagfmt='d:debug, b:branch branch'; args='remote [local]'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
if(~ $debug 1)
|
||||
debug=(-d)
|
||||
|
||||
remote=`{echo $1 | subst -g '/*$'}
|
||||
local=$2
|
||||
|
||||
if(~ $#remote 0)
|
||||
exec aux/usage
|
||||
if(~ $#local 0)
|
||||
local=`{basename $remote .git}
|
||||
if(~ $#branch 1)
|
||||
branchflag=(-b $branch)
|
||||
|
||||
if(test -e $local)
|
||||
die 'repository already exists:' $local
|
||||
|
||||
fn clone{
|
||||
flag +e
|
||||
mkdir -p $local/.git
|
||||
mkdir -p $local/.git/objects/pack/
|
||||
mkdir -p $local/.git/refs/heads/
|
||||
|
||||
cd $local
|
||||
|
||||
>>.git/config {
|
||||
echo '[remote "origin"]'
|
||||
echo ' url='$remote
|
||||
}
|
||||
{git/fetch $debug $branchflag $remote >[2=3] | awk '
|
||||
BEGIN{
|
||||
headref=""
|
||||
if(ENVIRON["branch"] != "")
|
||||
headref="refs/remotes/origin/"ENVIRON["branch"]
|
||||
headhash=""
|
||||
}
|
||||
/^symref / && headref == "" {
|
||||
if($2 == "HEAD"){
|
||||
gsub("^refs/heads", "refs/remotes/origin", $3)
|
||||
gsub("^refs/tags", "refs/remotes/origin/tags", $3)
|
||||
}
|
||||
}
|
||||
/^remote /{
|
||||
if($2=="HEAD"){
|
||||
headhash=$3
|
||||
}else if(match($2, "^refs/(heads|tags)/")){
|
||||
gsub("^refs/heads", "refs/remotes/origin", $2)
|
||||
if($2 == headref || (headref == "" && $3 == headhash))
|
||||
headref=$2
|
||||
outfile = ".git/" $2
|
||||
outdir = outfile
|
||||
gsub("/?[^/]*/?$", "", outdir)
|
||||
system("mkdir -p "outdir)
|
||||
print $3 > outfile
|
||||
close(outfile)
|
||||
}
|
||||
}
|
||||
END{
|
||||
if(headref != ""){
|
||||
remote = headref;
|
||||
refdir = headref;
|
||||
gsub("/?[^/]*/?$", "", refdir)
|
||||
gsub("^refs/remotes/origin", "refs/heads", headref)
|
||||
system("mkdir -p .git/"refdir);
|
||||
system("cp .git/" remote " .git/" headref)
|
||||
print "ref: " headref > ".git/HEAD"
|
||||
}else if(headhash != ""){
|
||||
print "warning: detached head "headhash > "/fd/2"
|
||||
print headhash > ".git/HEAD"
|
||||
}
|
||||
}
|
||||
'} |[3] tr '\x0d' '\x0a' || die 'could not clone repository'
|
||||
|
||||
tree=/mnt/git/HEAD/tree
|
||||
lbranch=`{git/branch}
|
||||
rbranch=`{echo $lbranch | subst '^heads' 'remotes/origin'}
|
||||
echo checking out repository...
|
||||
if(test -f .git/refs/$rbranch){
|
||||
cp .git/refs/$rbranch .git/refs/$lbranch
|
||||
git/fs
|
||||
@ {builtin cd $tree && tar cif /fd/1 .} | @ {tar xf /fd/0} \
|
||||
|| die 'checkout failed:' $status
|
||||
for(f in `$nl{walk -f $tree | subst '^'$tree'/*'}){
|
||||
if(! ~ $#f 0){
|
||||
idx=.git/index9/tracked/$f
|
||||
mkdir -p `$nl{basename -d $idx}
|
||||
walk -eq $f > $idx
|
||||
}
|
||||
}
|
||||
}
|
||||
if not{
|
||||
echo no default branch >[1=2]
|
||||
echo check out your code with git/branch >[1=2]
|
||||
}
|
||||
}
|
||||
|
||||
fn sigint {
|
||||
echo cancelled clone $remote: cleaning $local >[1=2]
|
||||
rm -rf $local
|
||||
exit interrupted
|
||||
}
|
||||
|
||||
@{clone}
|
||||
st=$status
|
||||
if(! ~ $st ''){
|
||||
echo failed to clone $remote: cleaning $local >[1=2]
|
||||
rm -rf $local
|
||||
exit $st
|
||||
}
|
||||
exit ''
|
150
sys/src/cmd/git/commit
Executable file
150
sys/src/cmd/git/commit
Executable file
|
@ -0,0 +1,150 @@
|
|||
#!/bin/rc -e
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
fn whoami{
|
||||
name=`{git/conf user.name}
|
||||
email=`{git/conf user.email}
|
||||
if(test -f /adm/keys.who){
|
||||
if(~ $name '')
|
||||
name=`{awk -F'|' '$1=="'$user'" {x=$3} END{print x}' </adm/keys.who}
|
||||
if(~ $email '')
|
||||
email=`{awk -F'|' '$1=="'$user'" {x=$5} END{print x}' </adm/keys.who}
|
||||
}
|
||||
if(~ $name '')
|
||||
name=glenda
|
||||
if(~ $email '')
|
||||
email=glenda@9front.local
|
||||
}
|
||||
|
||||
fn findbranch{
|
||||
branch=`{git/branch}
|
||||
if(test -e /mnt/git/branch/$branch/tree){
|
||||
refpath=.git/refs/$branch
|
||||
initial=false
|
||||
}
|
||||
if not if(test -e /mnt/git/object/$branch/tree){
|
||||
refpath=.git/HEAD
|
||||
initial=false
|
||||
}
|
||||
if not if(! test -e /mnt/git/HEAD/tree){
|
||||
refpath=.git/refs/$branch
|
||||
initial=true
|
||||
}
|
||||
if not
|
||||
die 'invalid branch:' $branch
|
||||
}
|
||||
|
||||
# Remove commentary lines.
|
||||
# Remove leading and trailing empty lines.
|
||||
# Combine consecutive empty lines between paragraphs.
|
||||
# Remove trailing spaces from lines.
|
||||
# Ensure there's trailing newline.
|
||||
fn cleanmsg{
|
||||
awk '
|
||||
/^[ ]*#/ {next}
|
||||
/^[ ]*$/ {empty = 1; next}
|
||||
|
||||
wet && empty {printf "\n"}
|
||||
{wet = 1; empty = 0}
|
||||
{sub(/[ ]+$/, ""); print $0}
|
||||
'
|
||||
}
|
||||
|
||||
fn editmsg{
|
||||
if(! test -s $msgfile.tmp){
|
||||
>$msgfile.tmp {
|
||||
echo '# Author:' $name '<'$email'>'
|
||||
echo '#'
|
||||
for(p in $parents)
|
||||
echo '# parent:' $p
|
||||
git/walk -fAMR $files | subst -g '^' '# '
|
||||
echo '#'
|
||||
echo '# Commit message:'
|
||||
}
|
||||
edit=1
|
||||
}
|
||||
if(! ~ $#edit 0){
|
||||
giteditor=`{git/conf core.editor}
|
||||
if(~ $#editor 0)
|
||||
editor=$giteditor
|
||||
if(~ $#editor 0)
|
||||
editor=hold
|
||||
$editor $msgfile.tmp
|
||||
}
|
||||
cleanmsg < $msgfile.tmp > $msgfile
|
||||
if(! test -s $msgfile)
|
||||
die 'empty commit message'
|
||||
}
|
||||
|
||||
fn parents{
|
||||
if(! ~ $#revise 0)
|
||||
parents=`{cat /mnt/git/HEAD/parent}
|
||||
if not if(test -f .git/index9/merge-parents)
|
||||
parents=`{cat .git/index9/merge-parents | sort | uniq}
|
||||
if not if(~ $initial true)
|
||||
parents=()
|
||||
if not
|
||||
parents=`{git/query $branch}
|
||||
}
|
||||
|
||||
fn commit{
|
||||
msg=`''{cat $msgfile}
|
||||
if(! ~ $#parents 0)
|
||||
pflags='-p'^$parents
|
||||
hash=`{git/save -n $"name -e $"email -m $"msg $pflags $files || die $status}
|
||||
rm -f .git/index9/merge-parents
|
||||
}
|
||||
|
||||
fn update{
|
||||
mkdir -p `{basename -d $refpath}
|
||||
# Paranoia: let's not mangle the repo.
|
||||
if(~ $#hash 0)
|
||||
die 'botched commit'
|
||||
echo $branch: $hash
|
||||
echo $hash > $refpath
|
||||
for(f in $files){
|
||||
if(test -e .git/index9/removed/$f || ! test -e $f){
|
||||
rm -f .git/index9/removed/$f
|
||||
rm -f .git/index9/tracked/$f
|
||||
}
|
||||
if not{
|
||||
mkdir -p `{basename -d $f}
|
||||
walk -eq $f > .git/index9/tracked/$f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sigexit{
|
||||
rm -f $msgfile $msgfile.tmp
|
||||
}
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='m:msg message, r:revise, e:edit'; args='[file ...]'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
msgfile=/tmp/git-msg.$pid
|
||||
if(~ $#msg 1)
|
||||
echo $msg >$msgfile.tmp
|
||||
if not if(~ $#revise 1){
|
||||
msg=1
|
||||
echo revising commit `{cat /mnt/git/HEAD/hash}
|
||||
cat /mnt/git/HEAD/msg >$msgfile.tmp
|
||||
}
|
||||
|
||||
files=()
|
||||
if(! ~ $#* 0)
|
||||
files=`$nl{git/walk -c `$nl{cleanname $gitrel/$*}}
|
||||
if(~ $status '' || ~ $#files 0 && ! test -f .git/index9/merge-parents && ~ $#revise 0)
|
||||
die 'nothing to commit' $status
|
||||
@{
|
||||
flag e +
|
||||
whoami
|
||||
findbranch
|
||||
parents
|
||||
editmsg
|
||||
commit
|
||||
update
|
||||
} || die 'could not commit:' $status
|
||||
exit ''
|
158
sys/src/cmd/git/compat
Executable file
158
sys/src/cmd/git/compat
Executable file
|
@ -0,0 +1,158 @@
|
|||
#!/bin/rc
|
||||
|
||||
rfork e
|
||||
|
||||
opts=()
|
||||
args=()
|
||||
|
||||
fn cmd_init{
|
||||
while(~ $#* 0){
|
||||
switch($1){
|
||||
case --bare
|
||||
opts=(-b)
|
||||
case --
|
||||
# go likes to use these
|
||||
case -*
|
||||
die unknown command init $*
|
||||
case *
|
||||
args=($args $1)
|
||||
}
|
||||
shift
|
||||
}
|
||||
ls >[1=2]
|
||||
git/init $opts $args
|
||||
}
|
||||
|
||||
fn cmd_clone{
|
||||
branch=()
|
||||
while( ! ~ $#* 0){
|
||||
switch($1){
|
||||
case -b
|
||||
branch=$2
|
||||
shift
|
||||
case --
|
||||
# go likes to use these
|
||||
case -*
|
||||
die unknown command clone $*
|
||||
case *
|
||||
args=($args $1)
|
||||
}
|
||||
shift
|
||||
}
|
||||
git/clone $opts $args
|
||||
if(~ $#branch 1)
|
||||
git/branch -n -b $1 origin/$1
|
||||
}
|
||||
|
||||
fn cmd_pull{
|
||||
if(~ $1 -*)
|
||||
die unknown options for pull $*
|
||||
git/pull
|
||||
}
|
||||
|
||||
fn cmd_fetch{
|
||||
while(~ $#* 0){
|
||||
switch($1){
|
||||
case --all
|
||||
opts=($opts -a)
|
||||
case -f
|
||||
opts=($opts -u $2)
|
||||
shift
|
||||
case --
|
||||
# go likes to use these
|
||||
case -*
|
||||
die unknown command clone $*
|
||||
case *
|
||||
args=($args $1)
|
||||
}
|
||||
shift
|
||||
}
|
||||
git/pull -f $opts
|
||||
}
|
||||
|
||||
|
||||
fn cmd_checkout{
|
||||
if(~ $1 -*)
|
||||
die unknown command pull $*
|
||||
if(~ $#* 0)
|
||||
die git checkout branch
|
||||
git/branch $b
|
||||
}
|
||||
|
||||
fn cmd_submodule {
|
||||
if(test -f .gitmodules)
|
||||
die 'submodules unsupported'
|
||||
}
|
||||
|
||||
fn cmd_rev-parse{
|
||||
while(~ $1 -*){
|
||||
switch($1){
|
||||
case --git-dir
|
||||
echo $gitroot/.git
|
||||
shift
|
||||
case --abbrev-ref
|
||||
echo `{dcmd git9/branch | sed s@^heads/@@g}
|
||||
shift
|
||||
case *
|
||||
dprint option $opt
|
||||
}
|
||||
shift
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_show-ref{
|
||||
if(~ $1 -*)
|
||||
die unknown command pull $*
|
||||
filter=cat
|
||||
if(~ $#* 0)
|
||||
filter=cat
|
||||
if not
|
||||
filter='-e(^|/)'^$*^'$'
|
||||
for(b in `$nl{cd $gitroot/.git/refs/ && walk -f})
|
||||
echo `{cat $gitroot/.git/refs/$b} refs/$b
|
||||
}
|
||||
|
||||
fn cmd_remote{
|
||||
if({! ~ $#* 3 && ! ~ $#* 4} || ! ~ $1 add)
|
||||
die unimplemented remote cmd $*
|
||||
name=$2
|
||||
url=$3
|
||||
if(~ $3 '--')
|
||||
url=$4
|
||||
>>$gitroot/.git/config{
|
||||
echo '[remote "'$name'"]'
|
||||
echo ' url='$url
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_version{
|
||||
echo git version 2.2.0
|
||||
}
|
||||
|
||||
|
||||
fn usage{
|
||||
echo 'git <command> <args>' >[1=2]
|
||||
exit usage
|
||||
}
|
||||
|
||||
fn die {
|
||||
>[1=2] echo git $_cmdname: $*
|
||||
exit $_cmdname: $*
|
||||
}
|
||||
|
||||
_cmdname=$1
|
||||
if(~ $0 *compat){
|
||||
ramfs -m /n/gitcompat
|
||||
touch /n/gitcompat/git
|
||||
bind $0 /n/gitcompat/git
|
||||
path=( /n/gitcompat $path )
|
||||
exec rc
|
||||
}
|
||||
|
||||
if(! test -f '/env/fn#cmd_'$1)
|
||||
die git $1: commmand not implemented
|
||||
if(! ~ $1 init && ! ~ $1 clone)
|
||||
gitroot=`{git/conf -r} || die repo
|
||||
|
||||
cmd_$1 $*(2-)
|
||||
exit ''
|
97
sys/src/cmd/git/conf.c
Normal file
97
sys/src/cmd/git/conf.c
Normal file
|
@ -0,0 +1,97 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
int findroot;
|
||||
int showall;
|
||||
int nfile;
|
||||
char *file[32];
|
||||
|
||||
static int
|
||||
showconf(char *cfg, char *sect, char *key)
|
||||
{
|
||||
char *ln, *p;
|
||||
Biobuf *f;
|
||||
int foundsect, nsect, nkey, found;
|
||||
|
||||
if((f = Bopen(cfg, OREAD)) == nil)
|
||||
return 0;
|
||||
|
||||
found = 0;
|
||||
nsect = sect ? strlen(sect) : 0;
|
||||
nkey = strlen(key);
|
||||
foundsect = (sect == nil);
|
||||
while((ln = Brdstr(f, '\n', 1)) != nil){
|
||||
p = strip(ln);
|
||||
if(*p == '[' && sect){
|
||||
foundsect = strncmp(sect, ln, nsect) == 0;
|
||||
}else if(foundsect && strncmp(p, key, nkey) == 0){
|
||||
p = strip(p + nkey);
|
||||
if(*p != '=')
|
||||
continue;
|
||||
p = strip(p + 1);
|
||||
print("%s\n", p);
|
||||
found = 1;
|
||||
if(!showall){
|
||||
free(ln);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
free(ln);
|
||||
}
|
||||
done:
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-f file] [-r] keys..\n", argv0);
|
||||
fprint(2, "\t-f: use file 'file' (default: .git/config)\n");
|
||||
fprint(2, "\t r: print repository root\n");
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char repo[512], *p, *s;
|
||||
int i, j;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'f': file[nfile++]=EARGF(usage()); break;
|
||||
case 'r': findroot++; break;
|
||||
case 'a': showall++; break;
|
||||
default: usage(); break;
|
||||
}ARGEND;
|
||||
|
||||
if(findroot){
|
||||
if(findrepo(repo, sizeof(repo)) == -1)
|
||||
sysfatal("%r");
|
||||
print("%s\n", repo);
|
||||
exits(nil);
|
||||
}
|
||||
if(nfile == 0){
|
||||
file[nfile++] = ".git/config";
|
||||
if((p = getenv("home")) != nil)
|
||||
file[nfile++] = smprint("%s/lib/git/config", p);
|
||||
}
|
||||
|
||||
for(i = 0; i < argc; i++){
|
||||
if((p = strchr(argv[i], '.')) == nil){
|
||||
s = nil;
|
||||
p = argv[i];
|
||||
}else{
|
||||
*p = 0;
|
||||
p++;
|
||||
s = smprint("[%s]", argv[i]);
|
||||
}
|
||||
for(j = 0; j < nfile; j++)
|
||||
if(showconf(file[j], s, p))
|
||||
break;
|
||||
}
|
||||
exits(nil);
|
||||
}
|
219
sys/src/cmd/git/delta.c
Normal file
219
sys/src/cmd/git/delta.c
Normal file
|
@ -0,0 +1,219 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
enum {
|
||||
Minchunk = 128,
|
||||
Maxchunk = 8192,
|
||||
Splitmask = (1<<8)-1,
|
||||
|
||||
};
|
||||
|
||||
static u32int geartab[] = {
|
||||
0x67ed26b7, 0x32da500c, 0x53d0fee0, 0xce387dc7, 0xcd406d90, 0x2e83a4d4, 0x9fc9a38d, 0xb67259dc,
|
||||
0xca6b1722, 0x6d2ea08c, 0x235cea2e, 0x3149bb5f, 0x1beda787, 0x2a6b77d5, 0x2f22d9ac, 0x91fc0544,
|
||||
0xe413acfa, 0x5a30ff7a, 0xad6fdde0, 0x444fd0f5, 0x7ad87864, 0x58c5ff05, 0x8d2ec336, 0x2371f853,
|
||||
0x550f8572, 0x6aa448dd, 0x7c9ddbcf, 0x95221e14, 0x2a82ec33, 0xcbec5a78, 0xc6795a0d, 0x243995b7,
|
||||
0x1c909a2f, 0x4fded51c, 0x635d334b, 0x0e2b9999, 0x2702968d, 0x856de1d5, 0x3325d60e, 0xeb6a7502,
|
||||
0xec2a9844, 0x0905835a, 0xa1820375, 0xa4be5cab, 0x96a6c058, 0x2c2ccd70, 0xba40fce3, 0xd794c46b,
|
||||
0x8fbae83e, 0xc3aa7899, 0x3d3ff8ed, 0xa0d42b5b, 0x571c0c97, 0xd2811516, 0xf7e7b96c, 0x4fd2fcbd,
|
||||
0xe2fdec94, 0x282cc436, 0x78e8e95c, 0x80a3b613, 0xcfbee20c, 0xd4a32d1c, 0x2a12ff13, 0x6af82936,
|
||||
0xe5630258, 0x8efa6a98, 0x294fb2d1, 0xdeb57086, 0x5f0fddb3, 0xeceda7ce, 0x4c87305f, 0x3a6d3307,
|
||||
0xe22d2942, 0x9d060217, 0x1e42ed02, 0xb6f63b52, 0x4367f39f, 0x055cf262, 0x03a461b2, 0x5ef9e382,
|
||||
0x386bc03a, 0x2a1e79c7, 0xf1a0058b, 0xd4d2dea9, 0x56baf37d, 0x5daff6cc, 0xf03a951d, 0xaef7de45,
|
||||
0xa8f4581e, 0x3960b555, 0xffbfff6d, 0xbe702a23, 0x8f5b6d6f, 0x061739fb, 0x98696f47, 0x3fd596d4,
|
||||
0x151eac6b, 0xa9fcc4f5, 0x69181a12, 0x3ac5a107, 0xb5198fe7, 0x96bcb1da, 0x1b5ddf8e, 0xc757d650,
|
||||
0x65865c3a, 0x8fc0a41a, 0x87435536, 0x99eda6f2, 0x41874794, 0x29cff4e8, 0xb70efd9a, 0x3103f6e7,
|
||||
0x84d2453b, 0x15a450bd, 0x74f49af1, 0x60f664b1, 0xa1c86935, 0xfdafbce1, 0xe36353e3, 0x5d9ba739,
|
||||
0xbc0559ba, 0x708b0054, 0xd41d808c, 0xb2f31723, 0x9027c41f, 0xf136d165, 0xb5374b12, 0x9420a6ac,
|
||||
0x273958b6, 0xe6c2fad0, 0xebdc1f21, 0xfb33af8b, 0xc71c25cd, 0xe9a2d8e5, 0xbeb38a50, 0xbceb7cc2,
|
||||
0x4e4e73f0, 0xcd6c251d, 0xde4c032c, 0x4b04ac30, 0x725b8b21, 0x4eb8c33b, 0x20d07b75, 0x0567aa63,
|
||||
0xb56b2bb7, 0xc1f5fd3a, 0xcafd35ca, 0x470dd4da, 0xfe4f94cd, 0xfb8de424, 0xe8dbcf40, 0xfe50a37a,
|
||||
0x62db5b5d, 0xf32f4ab6, 0x2c4a8a51, 0x18473dc0, 0xfe0cbb6e, 0xfe399efd, 0xdf34ecc9, 0x6ccd5055,
|
||||
0x46097073, 0x139135c2, 0x721c76f6, 0x1c6a94b4, 0x6eee014d, 0x8a508e02, 0x3da538f5, 0x280d394f,
|
||||
0x5248a0c4, 0x3ce94c6c, 0x9a71ad3a, 0x8493dd05, 0xe43f0ab6, 0x18e4ed42, 0x6c5c0e09, 0x42b06ec9,
|
||||
0x8d330343, 0xa45b6f59, 0x2a573c0c, 0xd7fd3de6, 0xeedeab68, 0x5c84dafc, 0xbbd1b1a8, 0xa3ce1ad1,
|
||||
0x85b70bed, 0xb6add07f, 0xa531309c, 0x8f8ab852, 0x564de332, 0xeac9ed0c, 0x73da402c, 0x3ec52761,
|
||||
0x43af2f4d, 0xd6ff45c8, 0x4c367462, 0xd553bd6a, 0x44724855, 0x3b2aa728, 0x56e5eb65, 0xeaf16173,
|
||||
0x33fa42ff, 0xd714bb5d, 0xfbd0a3b9, 0xaf517134, 0x9416c8cd, 0x534cf94f, 0x548947c2, 0x34193569,
|
||||
0x32f4389a, 0xfe7028bc, 0xed73b1ed, 0x9db95770, 0x468e3922, 0x0440c3cd, 0x60059a62, 0x33504562,
|
||||
0x2b229fbd, 0x5174dca5, 0xf7028752, 0xd63c6aa8, 0x31276f38, 0x0646721c, 0xb0191da8, 0xe00e6de0,
|
||||
0x9eac1a6e, 0x9f7628a5, 0xed6c06ea, 0x0bb8af15, 0xf119fb12, 0x38693c1c, 0x732bc0fe, 0x84953275,
|
||||
0xb82ec888, 0x33a4f1b3, 0x3099835e, 0x028a8782, 0x5fdd51d7, 0xc6c717b3, 0xb06caf71, 0x17c8c111,
|
||||
0x61bad754, 0x9fd03061, 0xe09df1af, 0x3bc9eb73, 0x85878413, 0x9889aaf2, 0x3f5a9e46, 0x42c9f01f,
|
||||
0x9984a4f4, 0xd5de43cc, 0xd294daed, 0xbecba2d2, 0xf1f6e72c, 0x5551128a, 0x83af87e2, 0x6f0342ba,
|
||||
};
|
||||
|
||||
static u64int
|
||||
hash(void *p, int n)
|
||||
{
|
||||
uchar buf[SHA1dlen];
|
||||
sha1((uchar*)p, n, buf, nil);
|
||||
return GETBE64(buf);
|
||||
}
|
||||
|
||||
static void
|
||||
addblk(Dtab *dt, void *buf, int len, int off, u64int h)
|
||||
{
|
||||
int i, sz, probe;
|
||||
Dblock *db;
|
||||
|
||||
probe = h % dt->sz;
|
||||
while(dt->b[probe].buf != nil){
|
||||
if(len == dt->b[probe].len && memcmp(buf, dt->b[probe].buf, len) == 0)
|
||||
return;
|
||||
probe = (probe + 1) % dt->sz;
|
||||
}
|
||||
assert(dt->b[probe].buf == nil);
|
||||
dt->b[probe].buf = buf;
|
||||
dt->b[probe].len = len;
|
||||
dt->b[probe].off = off;
|
||||
dt->b[probe].hash = h;
|
||||
dt->nb++;
|
||||
if(dt->sz < 2*dt->nb){
|
||||
sz = dt->sz;
|
||||
db = dt->b;
|
||||
dt->sz *= 2;
|
||||
dt->nb = 0;
|
||||
dt->b = eamalloc(dt->sz, sizeof(Dblock));
|
||||
for(i = 0; i < sz; i++)
|
||||
if(db[i].buf != nil)
|
||||
addblk(dt, db[i].buf, db[i].len, db[i].off, db[i].hash);
|
||||
free(db);
|
||||
}
|
||||
}
|
||||
|
||||
static Dblock*
|
||||
lookup(Dtab *dt, uchar *p, int n)
|
||||
{
|
||||
int probe;
|
||||
u64int h;
|
||||
|
||||
h = hash(p, n);
|
||||
for(probe = h % dt->sz; dt->b[probe].buf != nil; probe = (probe + 1) % dt->sz){
|
||||
if(dt->b[probe].hash != h)
|
||||
continue;
|
||||
if(n != dt->b[probe].len)
|
||||
continue;
|
||||
if(memcmp(p, dt->b[probe].buf, n) != 0)
|
||||
continue;
|
||||
return &dt->b[probe];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
static int
|
||||
nextblk(uchar *s, uchar *e)
|
||||
{
|
||||
u32int gh;
|
||||
uchar *p;
|
||||
|
||||
if((e - s) < Minchunk)
|
||||
return e - s;
|
||||
p = s + Minchunk;
|
||||
if((e - s) > Maxchunk)
|
||||
e = s + Maxchunk;
|
||||
gh = 0;
|
||||
while(p != e){
|
||||
gh = (gh<<1) + geartab[*p++];
|
||||
if((gh & Splitmask) == 0)
|
||||
break;
|
||||
}
|
||||
return p - s;
|
||||
}
|
||||
|
||||
void
|
||||
dtinit(Dtab *dt, Object *obj)
|
||||
{
|
||||
uchar *s, *e;
|
||||
u64int h;
|
||||
vlong n, o;
|
||||
|
||||
o = 0;
|
||||
s = (uchar*)obj->data;
|
||||
e = s + obj->size;
|
||||
dt->o = ref(obj);
|
||||
dt->nb = 0;
|
||||
dt->sz = 128;
|
||||
dt->b = eamalloc(dt->sz, sizeof(Dblock));
|
||||
dt->base = (uchar*)obj->data;
|
||||
dt->nbase = obj->size;
|
||||
while(s != e){
|
||||
n = nextblk(s, e);
|
||||
h = hash(s, n);
|
||||
addblk(dt, s, n, o, h);
|
||||
s += n;
|
||||
o += n;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dtclear(Dtab *dt)
|
||||
{
|
||||
unref(dt->o);
|
||||
free(dt->b);
|
||||
}
|
||||
|
||||
static int
|
||||
emitdelta(Delta **pd, int *nd, int cpy, int off, int len)
|
||||
{
|
||||
Delta *d;
|
||||
|
||||
*nd += 1;
|
||||
*pd = earealloc(*pd, *nd, sizeof(Delta));
|
||||
d = &(*pd)[*nd - 1];
|
||||
d->cpy = cpy;
|
||||
d->off = off;
|
||||
d->len = len;
|
||||
return len;
|
||||
}
|
||||
|
||||
static int
|
||||
stretch(Dtab *dt, Dblock *b, uchar *s, uchar *e, int n)
|
||||
{
|
||||
uchar *p, *q, *eb;
|
||||
|
||||
if(b == nil)
|
||||
return n;
|
||||
p = s + n;
|
||||
q = dt->base + b->off + n;
|
||||
eb = dt->base + dt->nbase;
|
||||
while(n < (1<<24)-1){
|
||||
if(p == e || q == eb)
|
||||
break;
|
||||
if(*p != *q)
|
||||
break;
|
||||
p++;
|
||||
q++;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
Delta*
|
||||
deltify(Object *obj, Dtab *dt, int *pnd)
|
||||
{
|
||||
Delta *d;
|
||||
Dblock *b;
|
||||
uchar *s, *e;
|
||||
vlong n, o;
|
||||
|
||||
o = 0;
|
||||
d = nil;
|
||||
s = (uchar*)obj->data;
|
||||
e = s + obj->size;
|
||||
*pnd = 0;
|
||||
while(s != e){
|
||||
n = nextblk(s, e);
|
||||
b = lookup(dt, s, n);
|
||||
n = stretch(dt, b, s, e, n);
|
||||
if(b != nil)
|
||||
emitdelta(&d, pnd, 1, b->off, n);
|
||||
else
|
||||
emitdelta(&d, pnd, 0, o, n);
|
||||
s += n;
|
||||
o += n;
|
||||
}
|
||||
return d;
|
||||
}
|
37
sys/src/cmd/git/diff
Executable file
37
sys/src/cmd/git/diff
Executable file
|
@ -0,0 +1,37 @@
|
|||
#!/bin/rc
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='c:commit branch, s:summarize'; args='[file ...]'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
if(~ $#commit 0)
|
||||
commit=HEAD
|
||||
|
||||
files=()
|
||||
if(! ~ $#* 0)
|
||||
files=`{cleanname $gitrel/$*}
|
||||
|
||||
branch=`{git/query -p $commit}
|
||||
if(~ $summarize 1){
|
||||
git/walk -fMAR $files
|
||||
exit
|
||||
}
|
||||
|
||||
fn lsdirty {
|
||||
git/walk -c -fRMA $files
|
||||
if(! ~ $commit HEAD)
|
||||
git/query -c $commit HEAD | subst '^..'
|
||||
}
|
||||
|
||||
for(f in `$nl{lsdirty | sort | uniq}){
|
||||
orig=$branch/tree/$f
|
||||
if(! test -f $orig)
|
||||
orig=/dev/null
|
||||
if(! test -f $f)
|
||||
f=/dev/null
|
||||
diff -u $orig $f
|
||||
}
|
||||
exit ''
|
89
sys/src/cmd/git/export
Executable file
89
sys/src/cmd/git/export
Executable file
|
@ -0,0 +1,89 @@
|
|||
#!/bin/rc
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
patchname=/tmp/git.patchname.$pid
|
||||
patchfile=/tmp/git.patchfile.$pid
|
||||
fn sigexit{
|
||||
rm -f $patchname $patchfile
|
||||
}
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='o:patchdir patchdir'; args='[query]'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
if(~ $#patchdir 1 && ! test -d $patchdir)
|
||||
mkdir -p $patchdir
|
||||
|
||||
q=$*
|
||||
if(~ $#q 0)
|
||||
q=HEAD
|
||||
commits=`{git/query $q || die $status}
|
||||
n=1
|
||||
m=$#commits
|
||||
|
||||
|
||||
# sleazy hack: we want to run
|
||||
# under rfork m for the web ui,
|
||||
# so don't error if we can't mount
|
||||
mntgen /mnt/scratch >[2]/dev/null || status=''
|
||||
for(c in $commits){
|
||||
cp=`{git/query -p $c}
|
||||
pp=`{git/query -p $c'~'}
|
||||
fc=`$nl{git/query -c $c~ $c | sed 's/^..//'}
|
||||
|
||||
@{
|
||||
rfork n
|
||||
cd /mnt/scratch
|
||||
if(test -d $pp/tree)
|
||||
bind $pp/tree a
|
||||
if(test -d $cp/tree)
|
||||
bind $cp/tree b
|
||||
|
||||
echo From $c
|
||||
echo From: `{cat $cp/author}
|
||||
echo Date: `{date -um `{mtime $cp/author | awk '{print $1}'}}
|
||||
<$cp/msg awk '
|
||||
NR == 1 {
|
||||
n = ENVIRON["n"]
|
||||
m = ENVIRON["m"]
|
||||
msg=$0
|
||||
if(m > 1)
|
||||
patch = sprintf("[PATCH %d/%d]", n, m)
|
||||
else
|
||||
patch = "[PATCH]"
|
||||
printf "Subject: %s %s\n\n", patch, msg
|
||||
|
||||
gsub("^[ ]|[ ]$", "", msg)
|
||||
gsub("[^a-zA-Z0-9_]+", "-", msg)
|
||||
printf "%.4d-%s.patch", n, msg >ENVIRON["patchname"]
|
||||
next
|
||||
}
|
||||
{
|
||||
print
|
||||
}'
|
||||
echo '---'
|
||||
echo diff `{basename $pp} `{basename $cp}
|
||||
for(f in $fc){
|
||||
a=a/$f
|
||||
if(! test -e $a)
|
||||
a=/dev/null
|
||||
b=b/$f
|
||||
if(! test -e $b)
|
||||
b=/dev/null
|
||||
ape/diff -urN $a $b
|
||||
}
|
||||
} >$patchfile
|
||||
if(~ $#patchdir 0){
|
||||
cat $patchfile
|
||||
! ~ $n $m && echo
|
||||
}
|
||||
if not{
|
||||
f=$patchdir/`{cat $patchname}
|
||||
mv $patchfile $f
|
||||
echo $f
|
||||
}
|
||||
n=`{echo $n + 1 | bc}
|
||||
}
|
||||
exit ''
|
316
sys/src/cmd/git/fetch.c
Normal file
316
sys/src/cmd/git/fetch.c
Normal file
|
@ -0,0 +1,316 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
char *fetchbranch;
|
||||
char *upstream = "origin";
|
||||
char *packtmp = ".git/objects/pack/fetch.tmp";
|
||||
int listonly;
|
||||
|
||||
int
|
||||
resolveremote(Hash *h, char *ref)
|
||||
{
|
||||
char buf[128], *s;
|
||||
int r, f;
|
||||
|
||||
ref = strip(ref);
|
||||
if((r = hparse(h, ref)) != -1)
|
||||
return r;
|
||||
/* Slightly special handling: translate remote refs to local ones. */
|
||||
if(strcmp(ref, "HEAD") == 0){
|
||||
snprint(buf, sizeof(buf), ".git/HEAD");
|
||||
}else if(strstr(ref, "refs/heads") == ref){
|
||||
ref += strlen("refs/heads");
|
||||
snprint(buf, sizeof(buf), ".git/refs/remotes/%s/%s", upstream, ref);
|
||||
}else if(strstr(ref, "refs/tags") == ref){
|
||||
ref += strlen("refs/tags");
|
||||
snprint(buf, sizeof(buf), ".git/refs/tags/%s/%s", upstream, ref);
|
||||
}else{
|
||||
return -1;
|
||||
}
|
||||
|
||||
r = -1;
|
||||
s = strip(buf);
|
||||
if((f = open(s, OREAD)) == -1)
|
||||
return -1;
|
||||
if(readn(f, buf, sizeof(buf)) >= 40)
|
||||
r = hparse(h, buf);
|
||||
close(f);
|
||||
|
||||
if(r == -1 && strstr(buf, "ref:") == buf)
|
||||
return resolveremote(h, buf + strlen("ref:"));
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
rename(char *pack, char *idx, Hash h)
|
||||
{
|
||||
char name[128];
|
||||
Dir st;
|
||||
|
||||
nulldir(&st);
|
||||
st.name = name;
|
||||
snprint(name, sizeof(name), "%H.pack", h);
|
||||
if(access(name, AEXIST) == 0)
|
||||
fprint(2, "warning, pack %s already fetched\n", name);
|
||||
else if(dirwstat(pack, &st) == -1)
|
||||
return -1;
|
||||
snprint(name, sizeof(name), "%H.idx", h);
|
||||
if(access(name, AEXIST) == 0)
|
||||
fprint(2, "warning, pack %s already indexed\n", name);
|
||||
else if(dirwstat(idx, &st) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
checkhash(int fd, vlong sz, Hash *hcomp)
|
||||
{
|
||||
DigestState *st;
|
||||
Hash hexpect;
|
||||
char buf[Pktmax];
|
||||
vlong n, r;
|
||||
int nr;
|
||||
|
||||
if(sz < 28){
|
||||
werrstr("undersize packfile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
st = nil;
|
||||
n = 0;
|
||||
while(n != sz - 20){
|
||||
nr = sizeof(buf);
|
||||
if(sz - n - 20 < sizeof(buf))
|
||||
nr = sz - n - 20;
|
||||
r = readn(fd, buf, nr);
|
||||
if(r != nr)
|
||||
return -1;
|
||||
st = sha1((uchar*)buf, nr, nil, st);
|
||||
n += r;
|
||||
}
|
||||
sha1(nil, 0, hcomp->h, st);
|
||||
if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))
|
||||
sysfatal("truncated packfile");
|
||||
if(!hasheq(hcomp, &hexpect)){
|
||||
werrstr("bad hash: %H != %H", *hcomp, hexpect);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mkoutpath(char *path)
|
||||
{
|
||||
char s[128];
|
||||
char *p;
|
||||
int fd;
|
||||
|
||||
snprint(s, sizeof(s), "%s", path);
|
||||
for(p=strchr(s+1, '/'); p; p=strchr(p+1, '/')){
|
||||
*p = 0;
|
||||
if(access(s, AEXIST) != 0){
|
||||
fd = create(s, OREAD, DMDIR | 0755);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
close(fd);
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
branchmatch(char *br, char *pat)
|
||||
{
|
||||
char name[128];
|
||||
|
||||
if(strstr(pat, "refs/heads") == pat)
|
||||
snprint(name, sizeof(name), "%s", pat);
|
||||
else if(strstr(pat, "heads"))
|
||||
snprint(name, sizeof(name), "refs/%s", pat);
|
||||
else
|
||||
snprint(name, sizeof(name), "refs/heads/%s", pat);
|
||||
return strcmp(br, name) == 0;
|
||||
}
|
||||
|
||||
char *
|
||||
matchcap(char *s, char *cap, int full)
|
||||
{
|
||||
if(strncmp(s, cap, strlen(cap)) == 0)
|
||||
if(!full || strlen(s) == strlen(cap))
|
||||
return s + strlen(cap);
|
||||
return nil;
|
||||
}
|
||||
|
||||
void
|
||||
handlecaps(char *caps)
|
||||
{
|
||||
char *p, *n, *c, *r;
|
||||
|
||||
for(p = caps; p != nil; p = n){
|
||||
n = strchr(p, ' ');
|
||||
if(n != nil)
|
||||
*n++ = 0;
|
||||
if((c = matchcap(p, "symref=", 0)) != nil){
|
||||
if((r = strchr(c, ':')) != nil){
|
||||
*r++ = '\0';
|
||||
print("symref %s %s\n", c, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
fetchpack(Conn *c, int pfd, char *packtmp)
|
||||
{
|
||||
char buf[Pktmax], idxtmp[256], *sp[3];
|
||||
Hash h, *have, *want;
|
||||
int nref, refsz, first;
|
||||
int i, n, req;
|
||||
vlong packsz;
|
||||
Object *o;
|
||||
|
||||
nref = 0;
|
||||
refsz = 16;
|
||||
first = 1;
|
||||
have = eamalloc(refsz, sizeof(have[0]));
|
||||
want = eamalloc(refsz, sizeof(want[0]));
|
||||
while(1){
|
||||
n = readpkt(c, buf, sizeof(buf));
|
||||
if(n == -1)
|
||||
return -1;
|
||||
if(n == 0)
|
||||
break;
|
||||
if(strncmp(buf, "ERR ", 4) == 0)
|
||||
sysfatal("%s", buf + 4);
|
||||
|
||||
if(first && n > strlen(buf))
|
||||
handlecaps(buf + strlen(buf) + 1);
|
||||
first = 0;
|
||||
|
||||
getfields(buf, sp, nelem(sp), 1, " \t\n\r");
|
||||
if(strstr(sp[1], "^{}"))
|
||||
continue;
|
||||
if(fetchbranch && !branchmatch(sp[1], fetchbranch))
|
||||
continue;
|
||||
if(refsz == nref + 1){
|
||||
refsz *= 2;
|
||||
have = erealloc(have, refsz * sizeof(have[0]));
|
||||
want = erealloc(want, refsz * sizeof(want[0]));
|
||||
}
|
||||
if(hparse(&want[nref], sp[0]) == -1)
|
||||
sysfatal("invalid hash %s", sp[0]);
|
||||
if (resolveremote(&have[nref], sp[1]) == -1)
|
||||
memset(&have[nref], 0, sizeof(have[nref]));
|
||||
print("remote %s %H local %H\n", sp[1], want[nref], have[nref]);
|
||||
nref++;
|
||||
}
|
||||
if(listonly){
|
||||
flushpkt(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(writephase(c) == -1)
|
||||
sysfatal("write: %r");
|
||||
req = 0;
|
||||
for(i = 0; i < nref; i++){
|
||||
if(hasheq(&have[i], &want[i]))
|
||||
continue;
|
||||
if((o = readobject(want[i])) != nil){
|
||||
unref(o);
|
||||
continue;
|
||||
}
|
||||
n = snprint(buf, sizeof(buf), "want %H\n", want[i]);
|
||||
if(writepkt(c, buf, n) == -1)
|
||||
sysfatal("could not send want for %H", want[i]);
|
||||
req = 1;
|
||||
}
|
||||
flushpkt(c);
|
||||
for(i = 0; i < nref; i++){
|
||||
if(hasheq(&have[i], &Zhash))
|
||||
continue;
|
||||
n = snprint(buf, sizeof(buf), "have %H\n", have[i]);
|
||||
if(writepkt(c, buf, n + 1) == -1)
|
||||
sysfatal("could not send have for %H", have[i]);
|
||||
}
|
||||
if(!req)
|
||||
flushpkt(c);
|
||||
|
||||
n = snprint(buf, sizeof(buf), "done\n");
|
||||
if(writepkt(c, buf, n) == -1)
|
||||
sysfatal("write: %r");
|
||||
if(!req)
|
||||
return 0;
|
||||
if(readphase(c) == -1)
|
||||
sysfatal("read: %r");
|
||||
if((n = readpkt(c, buf, sizeof(buf))) == -1)
|
||||
sysfatal("read: %r");
|
||||
buf[n] = 0;
|
||||
|
||||
fprint(2, "fetching...\n");
|
||||
packsz = 0;
|
||||
while(1){
|
||||
n = readn(c->rfd, buf, sizeof buf);
|
||||
if(n == 0)
|
||||
break;
|
||||
if(n == -1 || write(pfd, buf, n) != n)
|
||||
sysfatal("fetch packfile: %r");
|
||||
packsz += n;
|
||||
}
|
||||
closeconn(c);
|
||||
if(seek(pfd, 0, 0) == -1)
|
||||
sysfatal("packfile seek: %r");
|
||||
if(checkhash(pfd, packsz, &h) == -1)
|
||||
sysfatal("corrupt packfile: %r");
|
||||
close(pfd);
|
||||
n = strlen(packtmp) - strlen(".tmp");
|
||||
memcpy(idxtmp, packtmp, n);
|
||||
memcpy(idxtmp + n, ".idx", strlen(".idx") + 1);
|
||||
if(indexpack(packtmp, idxtmp, h) == -1)
|
||||
sysfatal("could not index fetched pack: %r");
|
||||
if(rename(packtmp, idxtmp, h) == -1)
|
||||
sysfatal("could not rename indexed pack: %r");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-dl] [-b br] [-u upstream] remote\n", argv0);
|
||||
fprint(2, "\t-b br: only fetch matching branch 'br'\n");
|
||||
fprint(2, "remote: fetch from this repository\n");
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int pfd;
|
||||
Conn c;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'b': fetchbranch=EARGF(usage()); break;
|
||||
case 'u': upstream=EARGF(usage()); break;
|
||||
case 'd': chattygit++; break;
|
||||
case 'l': listonly++; break;
|
||||
default: usage(); break;
|
||||
}ARGEND;
|
||||
|
||||
gitinit();
|
||||
if(argc != 1)
|
||||
usage();
|
||||
|
||||
if(mkoutpath(packtmp) == -1)
|
||||
sysfatal("could not create %s: %r", packtmp);
|
||||
if((pfd = create(packtmp, ORDWR, 0644)) == -1)
|
||||
sysfatal("could not create %s: %r", packtmp);
|
||||
|
||||
if(gitconnect(&c, argv[0], "upload") == -1)
|
||||
sysfatal("could not dial %s: %r", argv[0]);
|
||||
if(fetchpack(&c, pfd, packtmp) == -1)
|
||||
sysfatal("fetch failed: %r");
|
||||
closeconn(&c);
|
||||
exits(nil);
|
||||
}
|
853
sys/src/cmd/git/fs.c
Normal file
853
sys/src/cmd/git/fs.c
Normal file
|
@ -0,0 +1,853 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
#include <fcall.h>
|
||||
#include <thread.h>
|
||||
#include <9p.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
enum {
|
||||
Qroot,
|
||||
Qhead,
|
||||
Qbranch,
|
||||
Qcommit,
|
||||
Qcommitmsg,
|
||||
Qcommitparent,
|
||||
Qcommittree,
|
||||
Qcommitdata,
|
||||
Qcommithash,
|
||||
Qcommitauthor,
|
||||
Qobject,
|
||||
Qctl,
|
||||
Qmax,
|
||||
Internal=1<<7,
|
||||
};
|
||||
|
||||
typedef struct Gitaux Gitaux;
|
||||
typedef struct Crumb Crumb;
|
||||
typedef struct Cache Cache;
|
||||
typedef struct Uqid Uqid;
|
||||
struct Crumb {
|
||||
char *name;
|
||||
Object *obj;
|
||||
Qid qid;
|
||||
int mode;
|
||||
vlong mtime;
|
||||
};
|
||||
|
||||
struct Gitaux {
|
||||
int ncrumb;
|
||||
Crumb *crumb;
|
||||
char *refpath;
|
||||
int qdir;
|
||||
|
||||
/* For listing object dir */
|
||||
Objlist *ols;
|
||||
Object *olslast;
|
||||
};
|
||||
|
||||
struct Uqid {
|
||||
vlong uqid;
|
||||
|
||||
vlong ppath;
|
||||
vlong oid;
|
||||
int t;
|
||||
int idx;
|
||||
};
|
||||
|
||||
struct Cache {
|
||||
Uqid *cache;
|
||||
int n;
|
||||
int max;
|
||||
};
|
||||
|
||||
char *qroot[] = {
|
||||
"HEAD",
|
||||
"branch",
|
||||
"object",
|
||||
"ctl",
|
||||
};
|
||||
|
||||
#define Eperm "permission denied";
|
||||
#define Eexist "does not exist";
|
||||
#define E2long "path too long";
|
||||
#define Enodir "not a directory";
|
||||
#define Erepo "unable to read repo";
|
||||
#define Egreg "wat";
|
||||
#define Ebadobj "invalid object";
|
||||
|
||||
char gitdir[512];
|
||||
char *username;
|
||||
char *mtpt = "/mnt/git";
|
||||
char **branches = nil;
|
||||
Cache uqidcache[512];
|
||||
vlong nextqid = Qmax;
|
||||
|
||||
static Object* walklink(Gitaux *, char *, int, int, int*);
|
||||
|
||||
vlong
|
||||
qpath(Crumb *p, int idx, vlong id, vlong t)
|
||||
{
|
||||
int h, i;
|
||||
vlong pp;
|
||||
Cache *c;
|
||||
Uqid *u;
|
||||
|
||||
pp = p ? p->qid.path : 0;
|
||||
h = (pp*333 + id*7 + t) & (nelem(uqidcache) - 1);
|
||||
c = &uqidcache[h];
|
||||
u = c->cache;
|
||||
for(i=0; i <c->n ; i++){
|
||||
if(u->ppath == pp && u->oid == id && u->t == t && u->idx == idx)
|
||||
return (u->uqid << 8) | t;
|
||||
u++;
|
||||
}
|
||||
if(c->n == c->max){
|
||||
c->max += c->max/2 + 1;
|
||||
c->cache = erealloc(c->cache, c->max*sizeof(Uqid));
|
||||
}
|
||||
nextqid++;
|
||||
c->cache[c->n] = (Uqid){nextqid, pp, id, t, idx};
|
||||
c->n++;
|
||||
return (nextqid << 8) | t;
|
||||
}
|
||||
|
||||
static Crumb*
|
||||
crumb(Gitaux *aux, int n)
|
||||
{
|
||||
if(n < aux->ncrumb)
|
||||
return &aux->crumb[aux->ncrumb - n - 1];
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void
|
||||
popcrumb(Gitaux *aux)
|
||||
{
|
||||
Crumb *c;
|
||||
|
||||
if(aux->ncrumb > 1){
|
||||
c = crumb(aux, 0);
|
||||
free(c->name);
|
||||
unref(c->obj);
|
||||
aux->ncrumb--;
|
||||
}
|
||||
}
|
||||
|
||||
static vlong
|
||||
branchid(Gitaux *aux, char *path)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; branches[i]; i++)
|
||||
if(strcmp(path, branches[i]) == 0)
|
||||
goto found;
|
||||
branches = realloc(branches, sizeof(char *)*(i + 2));
|
||||
branches[i] = estrdup(path);
|
||||
branches[i + 1] = nil;
|
||||
|
||||
found:
|
||||
if(aux){
|
||||
if(aux->refpath)
|
||||
free(aux->refpath);
|
||||
aux->refpath = estrdup(branches[i]);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void
|
||||
obj2dir(Dir *d, Crumb *c, Object *o, char *name)
|
||||
{
|
||||
d->qid = c->qid;
|
||||
d->atime = c->mtime;
|
||||
d->mtime = c->mtime;
|
||||
d->mode = c->mode;
|
||||
d->name = estrdup9p(name);
|
||||
d->uid = estrdup9p(username);
|
||||
d->gid = estrdup9p(username);
|
||||
d->muid = estrdup9p(username);
|
||||
if(o->type == GBlob || o->type == GTag){
|
||||
d->qid.type = 0;
|
||||
d->mode &= 0777;
|
||||
d->length = o->size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int
|
||||
rootgen(int i, Dir *d, void *p)
|
||||
{
|
||||
Crumb *c;
|
||||
|
||||
c = crumb(p, 0);
|
||||
if (i >= nelem(qroot))
|
||||
return -1;
|
||||
d->mode = 0555 | DMDIR;
|
||||
d->name = estrdup9p(qroot[i]);
|
||||
d->qid.vers = 0;
|
||||
d->qid.type = strcmp(qroot[i], "ctl") == 0 ? 0 : QTDIR;
|
||||
d->qid.path = qpath(nil, i, i, Qroot);
|
||||
d->uid = estrdup9p(username);
|
||||
d->gid = estrdup9p(username);
|
||||
d->muid = estrdup9p(username);
|
||||
d->mtime = c->mtime;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
branchgen(int i, Dir *d, void *p)
|
||||
{
|
||||
Gitaux *aux;
|
||||
Dir *refs;
|
||||
Crumb *c;
|
||||
int n;
|
||||
|
||||
aux = p;
|
||||
c = crumb(aux, 0);
|
||||
refs = nil;
|
||||
d->qid.vers = 0;
|
||||
d->qid.type = QTDIR;
|
||||
d->qid.path = qpath(c, i, branchid(aux, aux->refpath), Qbranch | Internal);
|
||||
d->mode = 0555 | DMDIR;
|
||||
d->uid = estrdup9p(username);
|
||||
d->gid = estrdup9p(username);
|
||||
d->muid = estrdup9p(username);
|
||||
d->mtime = c->mtime;
|
||||
d->atime = c->mtime;
|
||||
if((n = slurpdir(aux->refpath, &refs)) < 0)
|
||||
return -1;
|
||||
if(i < n){
|
||||
d->name = estrdup9p(refs[i].name);
|
||||
free(refs);
|
||||
return 0;
|
||||
}else{
|
||||
free(refs);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
gtreegen(int i, Dir *d, void *p)
|
||||
{
|
||||
Object *o, *l, *e;
|
||||
Gitaux *aux;
|
||||
Crumb *c;
|
||||
int m;
|
||||
|
||||
aux = p;
|
||||
c = crumb(aux, 0);
|
||||
e = c->obj;
|
||||
if(i >= e->tree->nent)
|
||||
return -1;
|
||||
m = e->tree->ent[i].mode;
|
||||
if(e->tree->ent[i].ismod)
|
||||
o = emptydir();
|
||||
else if((o = readobject(e->tree->ent[i].h)) == nil)
|
||||
sysfatal("could not read object %H: %r", e->tree->ent[i].h);
|
||||
if(e->tree->ent[i].islink)
|
||||
if((l = walklink(aux, o->data, o->size, 0, &m)) != nil)
|
||||
o = l;
|
||||
d->qid.vers = 0;
|
||||
d->qid.type = o->type == GTree ? QTDIR : 0;
|
||||
d->qid.path = qpath(c, i, o->id, aux->qdir);
|
||||
d->mode = m;
|
||||
d->mode |= (o->type == GTree) ? 0755 : 0644;
|
||||
d->atime = c->mtime;
|
||||
d->mtime = c->mtime;
|
||||
d->uid = estrdup9p(username);
|
||||
d->gid = estrdup9p(username);
|
||||
d->muid = estrdup9p(username);
|
||||
d->name = estrdup9p(e->tree->ent[i].name);
|
||||
d->length = o->size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
gcommitgen(int i, Dir *d, void *p)
|
||||
{
|
||||
Object *o;
|
||||
Crumb *c;
|
||||
|
||||
c = crumb(p, 0);
|
||||
o = c->obj;
|
||||
d->uid = estrdup9p(username);
|
||||
d->gid = estrdup9p(username);
|
||||
d->muid = estrdup9p(username);
|
||||
d->mode = 0444;
|
||||
d->atime = o->commit->ctime;
|
||||
d->mtime = o->commit->ctime;
|
||||
d->qid.type = 0;
|
||||
d->qid.vers = 0;
|
||||
|
||||
switch(i){
|
||||
case 0:
|
||||
d->mode = 0755 | DMDIR;
|
||||
d->name = estrdup9p("tree");
|
||||
d->qid.type = QTDIR;
|
||||
d->qid.path = qpath(c, i, o->id, Qcommittree);
|
||||
break;
|
||||
case 1:
|
||||
d->name = estrdup9p("parent");
|
||||
d->qid.path = qpath(c, i, o->id, Qcommitparent);
|
||||
break;
|
||||
case 2:
|
||||
d->name = estrdup9p("msg");
|
||||
d->qid.path = qpath(c, i, o->id, Qcommitmsg);
|
||||
break;
|
||||
case 3:
|
||||
d->name = estrdup9p("hash");
|
||||
d->qid.path = qpath(c, i, o->id, Qcommithash);
|
||||
break;
|
||||
case 4:
|
||||
d->name = estrdup9p("author");
|
||||
d->qid.path = qpath(c, i, o->id, Qcommitauthor);
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
objgen(int i, Dir *d, void *p)
|
||||
{
|
||||
Gitaux *aux;
|
||||
Object *o;
|
||||
Crumb *c;
|
||||
char name[64];
|
||||
Objlist *ols;
|
||||
Hash h;
|
||||
|
||||
aux = p;
|
||||
c = crumb(aux, 0);
|
||||
if(!aux->ols)
|
||||
aux->ols = mkols();
|
||||
ols = aux->ols;
|
||||
o = nil;
|
||||
/* We tried to sent it, but it didn't fit */
|
||||
if(aux->olslast && ols->idx == i + 1){
|
||||
snprint(name, sizeof(name), "%H", aux->olslast->hash);
|
||||
obj2dir(d, c, aux->olslast, name);
|
||||
return 0;
|
||||
}
|
||||
while(ols->idx <= i){
|
||||
if(olsnext(ols, &h) == -1)
|
||||
return -1;
|
||||
if((o = readobject(h)) == nil){
|
||||
fprint(2, "corrupt object %H\n", h);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if(o != nil){
|
||||
snprint(name, sizeof(name), "%H", o->hash);
|
||||
obj2dir(d, c, o, name);
|
||||
unref(aux->olslast);
|
||||
aux->olslast = ref(o);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
objread(Req *r, Gitaux *aux)
|
||||
{
|
||||
Object *o;
|
||||
|
||||
o = crumb(aux, 0)->obj;
|
||||
switch(o->type){
|
||||
case GBlob:
|
||||
readbuf(r, o->data, o->size);
|
||||
break;
|
||||
case GTag:
|
||||
readbuf(r, o->data, o->size);
|
||||
break;
|
||||
case GTree:
|
||||
dirread9p(r, gtreegen, aux);
|
||||
break;
|
||||
case GCommit:
|
||||
dirread9p(r, gcommitgen, aux);
|
||||
break;
|
||||
default:
|
||||
sysfatal("invalid object type %d", o->type);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
readcommitparent(Req *r, Object *o)
|
||||
{
|
||||
char *buf, *p;
|
||||
int i, n;
|
||||
|
||||
n = o->commit->nparent * (40 + 2);
|
||||
buf = emalloc(n);
|
||||
p = buf;
|
||||
for (i = 0; i < o->commit->nparent; i++)
|
||||
p += sprint(p, "%H\n", o->commit->parent[i]);
|
||||
readbuf(r, buf, n);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gitattach(Req *r)
|
||||
{
|
||||
Gitaux *aux;
|
||||
Dir *d;
|
||||
|
||||
if((d = dirstat(".git")) == nil)
|
||||
sysfatal("git/fs: %r");
|
||||
if(getwd(gitdir, sizeof(gitdir)) == nil)
|
||||
sysfatal("getwd: %r");
|
||||
aux = emalloc(sizeof(Gitaux));
|
||||
aux->crumb = emalloc(sizeof(Crumb));
|
||||
aux->crumb[0].qid = (Qid){Qroot, 0, QTDIR};
|
||||
aux->crumb[0].obj = nil;
|
||||
aux->crumb[0].mode = DMDIR | 0555;
|
||||
aux->crumb[0].mtime = d->mtime;
|
||||
aux->crumb[0].name = estrdup("/");
|
||||
aux->ncrumb = 1;
|
||||
r->ofcall.qid = (Qid){Qroot, 0, QTDIR};
|
||||
r->fid->qid = r->ofcall.qid;
|
||||
r->fid->aux = aux;
|
||||
respond(r, nil);
|
||||
}
|
||||
|
||||
static Object*
|
||||
walklink(Gitaux *aux, char *link, int nlink, int ndotdot, int *mode)
|
||||
{
|
||||
char *p, *e, *path;
|
||||
Object *o, *n;
|
||||
int i;
|
||||
|
||||
path = emalloc(nlink + 1);
|
||||
memcpy(path, link, nlink);
|
||||
cleanname(path);
|
||||
|
||||
o = crumb(aux, ndotdot)->obj;
|
||||
assert(o->type == GTree);
|
||||
for(p = path; *p; p = e){
|
||||
n = nil;
|
||||
e = p + strcspn(p, "/");
|
||||
if(*e == '/')
|
||||
*e++ = '\0';
|
||||
/*
|
||||
* cleanname guarantees these show up at the start of the name,
|
||||
* which allows trimming them from the end of the trail of crumbs
|
||||
* instead of needing to keep track of full parentage.
|
||||
*/
|
||||
if(strcmp(p, "..") == 0)
|
||||
n = crumb(aux, ++ndotdot)->obj;
|
||||
else if(o->type == GTree)
|
||||
for(i = 0; i < o->tree->nent; i++)
|
||||
if(strcmp(o->tree->ent[i].name, p) == 0){
|
||||
*mode = o->tree->ent[i].mode;
|
||||
n = readobject(o->tree->ent[i].h);
|
||||
break;
|
||||
}
|
||||
o = n;
|
||||
if(o == nil)
|
||||
break;
|
||||
}
|
||||
free(path);
|
||||
return o;
|
||||
}
|
||||
|
||||
static char *
|
||||
objwalk1(Qid *q, Object *o, Crumb *p, Crumb *c, char *name, vlong qdir, Gitaux *aux)
|
||||
{
|
||||
Object *w, *l;
|
||||
char *e;
|
||||
int i, m;
|
||||
|
||||
w = nil;
|
||||
e = nil;
|
||||
if(!o)
|
||||
return Eexist;
|
||||
if(o->type == GTree){
|
||||
q->type = 0;
|
||||
for(i = 0; i < o->tree->nent; i++){
|
||||
if(strcmp(o->tree->ent[i].name, name) != 0)
|
||||
continue;
|
||||
m = o->tree->ent[i].mode;
|
||||
w = readobject(o->tree->ent[i].h);
|
||||
if(!w && o->tree->ent[i].ismod)
|
||||
w = emptydir();
|
||||
if(w && o->tree->ent[i].islink)
|
||||
if((l = walklink(aux, w->data, w->size, 1, &m)) != nil)
|
||||
w = l;
|
||||
if(!w)
|
||||
return Ebadobj;
|
||||
q->type = (w->type == GTree) ? QTDIR : 0;
|
||||
q->path = qpath(c, i, w->id, qdir);
|
||||
c->mode = m;
|
||||
c->mode |= (w->type == GTree) ? DMDIR|0755 : 0644;
|
||||
c->obj = w;
|
||||
break;
|
||||
}
|
||||
if(!w)
|
||||
e = Eexist;
|
||||
}else if(o->type == GCommit){
|
||||
q->type = 0;
|
||||
c->mtime = o->commit->mtime;
|
||||
c->mode = 0444;
|
||||
assert(qdir == Qcommit || qdir == Qobject || qdir == Qcommittree || qdir == Qhead);
|
||||
if(strcmp(name, "msg") == 0)
|
||||
q->path = qpath(p, 0, o->id, Qcommitmsg);
|
||||
else if(strcmp(name, "parent") == 0)
|
||||
q->path = qpath(p, 1, o->id, Qcommitparent);
|
||||
else if(strcmp(name, "hash") == 0)
|
||||
q->path = qpath(p, 2, o->id, Qcommithash);
|
||||
else if(strcmp(name, "author") == 0)
|
||||
q->path = qpath(p, 3, o->id, Qcommitauthor);
|
||||
else if(strcmp(name, "tree") == 0){
|
||||
q->type = QTDIR;
|
||||
q->path = qpath(p, 4, o->id, Qcommittree);
|
||||
unref(c->obj);
|
||||
c->mode = DMDIR | 0755;
|
||||
c->obj = readobject(o->commit->tree);
|
||||
if(c->obj == nil)
|
||||
sysfatal("could not read object %H: %r", o->commit->tree);
|
||||
}
|
||||
else
|
||||
e = Eexist;
|
||||
}else if(o->type == GTag){
|
||||
e = "tag walk unimplemented";
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static Object *
|
||||
readref(char *pathstr)
|
||||
{
|
||||
char buf[128], path[128], *p, *e;
|
||||
Hash h;
|
||||
int n, f;
|
||||
|
||||
snprint(path, sizeof(path), "%s", pathstr);
|
||||
while(1){
|
||||
if((f = open(path, OREAD)) == -1)
|
||||
return nil;
|
||||
if((n = readn(f, buf, sizeof(buf) - 1)) == -1)
|
||||
return nil;
|
||||
close(f);
|
||||
buf[n] = 0;
|
||||
if(strncmp(buf, "ref:", 4) != 0)
|
||||
break;
|
||||
|
||||
p = buf + 4;
|
||||
while(isspace(*p))
|
||||
p++;
|
||||
if((e = strchr(p, '\n')) != nil)
|
||||
*e = 0;
|
||||
snprint(path, sizeof(path), ".git/%s", p);
|
||||
}
|
||||
|
||||
if(hparse(&h, buf) == -1)
|
||||
return nil;
|
||||
|
||||
return readobject(h);
|
||||
}
|
||||
|
||||
static char*
|
||||
gitwalk1(Fid *fid, char *name, Qid *q)
|
||||
{
|
||||
char path[128];
|
||||
Gitaux *aux;
|
||||
Crumb *c, *o;
|
||||
char *e;
|
||||
Dir *d;
|
||||
Hash h;
|
||||
|
||||
e = nil;
|
||||
aux = fid->aux;
|
||||
|
||||
q->vers = 0;
|
||||
if(strcmp(name, "..") == 0){
|
||||
popcrumb(aux);
|
||||
c = crumb(aux, 0);
|
||||
*q = c->qid;
|
||||
fid->qid = *q;
|
||||
return nil;
|
||||
}
|
||||
|
||||
aux->crumb = realloc(aux->crumb, (aux->ncrumb + 1) * sizeof(Crumb));
|
||||
aux->ncrumb++;
|
||||
c = crumb(aux, 0);
|
||||
o = crumb(aux, 1);
|
||||
memset(c, 0, sizeof(Crumb));
|
||||
c->mode = o->mode;
|
||||
c->mtime = o->mtime;
|
||||
c->obj = o->obj ? ref(o->obj) : nil;
|
||||
|
||||
switch(QDIR(&fid->qid)){
|
||||
case Qroot:
|
||||
if(strcmp(name, "HEAD") == 0){
|
||||
*q = (Qid){Qhead, 0, QTDIR};
|
||||
c->mode = DMDIR | 0555;
|
||||
c->obj = readref(".git/HEAD");
|
||||
}else if(strcmp(name, "object") == 0){
|
||||
*q = (Qid){Qobject, 0, QTDIR};
|
||||
c->mode = DMDIR | 0555;
|
||||
}else if(strcmp(name, "branch") == 0){
|
||||
*q = (Qid){Qbranch, 0, QTDIR};
|
||||
aux->refpath = estrdup(".git/refs/");
|
||||
c->mode = DMDIR | 0555;
|
||||
}else if(strcmp(name, "ctl") == 0){
|
||||
*q = (Qid){Qctl, 0, 0};
|
||||
c->mode = 0644;
|
||||
}else{
|
||||
e = Eexist;
|
||||
}
|
||||
break;
|
||||
case Qbranch:
|
||||
if(strcmp(aux->refpath, ".git/refs/heads") == 0 && strcmp(name, "HEAD") == 0)
|
||||
snprint(path, sizeof(path), ".git/HEAD");
|
||||
else
|
||||
snprint(path, sizeof(path), "%s/%s", aux->refpath, name);
|
||||
q->type = QTDIR;
|
||||
d = dirstat(path);
|
||||
if(d && d->qid.type == QTDIR)
|
||||
q->path = qpath(o, Qbranch, branchid(aux, path), Qbranch);
|
||||
else if(d && (c->obj = readref(path)) != nil)
|
||||
q->path = qpath(o, Qbranch, c->obj->id, Qcommit);
|
||||
else
|
||||
e = Eexist;
|
||||
free(d);
|
||||
break;
|
||||
case Qobject:
|
||||
if(c->obj){
|
||||
e = objwalk1(q, o->obj, o, c, name, Qobject, aux);
|
||||
}else{
|
||||
if(hparse(&h, name) == -1)
|
||||
return "invalid object name";
|
||||
if((c->obj = readobject(h)) == nil)
|
||||
return "could not read object";
|
||||
if(c->obj->type == GBlob || c->obj->type == GTag){
|
||||
c->mode = 0644;
|
||||
q->type = 0;
|
||||
}else{
|
||||
c->mode = DMDIR | 0755;
|
||||
q->type = QTDIR;
|
||||
}
|
||||
q->path = qpath(o, Qobject, c->obj->id, Qobject);
|
||||
q->vers = 0;
|
||||
}
|
||||
break;
|
||||
case Qhead:
|
||||
e = objwalk1(q, o->obj, o, c, name, Qhead, aux);
|
||||
break;
|
||||
case Qcommit:
|
||||
e = objwalk1(q, o->obj, o, c, name, Qcommit, aux);
|
||||
break;
|
||||
case Qcommittree:
|
||||
e = objwalk1(q, o->obj, o, c, name, Qcommittree, aux);
|
||||
break;
|
||||
case Qcommitparent:
|
||||
case Qcommitmsg:
|
||||
case Qcommitdata:
|
||||
case Qcommithash:
|
||||
case Qcommitauthor:
|
||||
case Qctl:
|
||||
return Enodir;
|
||||
default:
|
||||
return Egreg;
|
||||
}
|
||||
|
||||
c->name = estrdup(name);
|
||||
c->qid = *q;
|
||||
fid->qid = *q;
|
||||
return e;
|
||||
}
|
||||
|
||||
static char*
|
||||
gitclone(Fid *o, Fid *n)
|
||||
{
|
||||
Gitaux *aux, *oaux;
|
||||
int i;
|
||||
|
||||
oaux = o->aux;
|
||||
aux = emalloc(sizeof(Gitaux));
|
||||
aux->ncrumb = oaux->ncrumb;
|
||||
aux->crumb = eamalloc(oaux->ncrumb, sizeof(Crumb));
|
||||
for(i = 0; i < aux->ncrumb; i++){
|
||||
aux->crumb[i] = oaux->crumb[i];
|
||||
aux->crumb[i].name = estrdup(oaux->crumb[i].name);
|
||||
if(aux->crumb[i].obj)
|
||||
aux->crumb[i].obj = ref(oaux->crumb[i].obj);
|
||||
}
|
||||
if(oaux->refpath)
|
||||
aux->refpath = strdup(oaux->refpath);
|
||||
aux->qdir = oaux->qdir;
|
||||
n->aux = aux;
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void
|
||||
gitdestroyfid(Fid *f)
|
||||
{
|
||||
Gitaux *aux;
|
||||
int i;
|
||||
|
||||
if((aux = f->aux) == nil)
|
||||
return;
|
||||
for(i = 0; i < aux->ncrumb; i++){
|
||||
if(aux->crumb[i].obj)
|
||||
unref(aux->crumb[i].obj);
|
||||
free(aux->crumb[i].name);
|
||||
}
|
||||
olsfree(aux->ols);
|
||||
free(aux->refpath);
|
||||
free(aux->crumb);
|
||||
free(aux);
|
||||
}
|
||||
|
||||
static char *
|
||||
readctl(Req *r)
|
||||
{
|
||||
char data[1024], ref[512], *s, *e;
|
||||
int fd, n;
|
||||
|
||||
if((fd = open(".git/HEAD", OREAD)) == -1)
|
||||
return Erepo;
|
||||
/* empty HEAD is invalid */
|
||||
if((n = readn(fd, ref, sizeof(ref) - 1)) <= 0)
|
||||
return Erepo;
|
||||
close(fd);
|
||||
|
||||
s = ref;
|
||||
ref[n] = 0;
|
||||
if(strncmp(s, "ref:", 4) == 0)
|
||||
s += 4;
|
||||
while(*s == ' ' || *s == '\t')
|
||||
s++;
|
||||
if((e = strchr(s, '\n')) != nil)
|
||||
*e = 0;
|
||||
if(strstr(s, "refs/") == s)
|
||||
s += strlen("refs/");
|
||||
|
||||
snprint(data, sizeof(data), "branch %s\nrepo %s\n", s, gitdir);
|
||||
readstr(r, data);
|
||||
return nil;
|
||||
}
|
||||
|
||||
static void
|
||||
gitread(Req *r)
|
||||
{
|
||||
char buf[256], *e;
|
||||
Gitaux *aux;
|
||||
Object *o;
|
||||
Qid *q;
|
||||
|
||||
aux = r->fid->aux;
|
||||
q = &r->fid->qid;
|
||||
o = crumb(aux, 0)->obj;
|
||||
e = nil;
|
||||
|
||||
switch(QDIR(q)){
|
||||
case Qroot:
|
||||
dirread9p(r, rootgen, aux);
|
||||
break;
|
||||
case Qbranch:
|
||||
if(o)
|
||||
objread(r, aux);
|
||||
else
|
||||
dirread9p(r, branchgen, aux);
|
||||
break;
|
||||
case Qobject:
|
||||
if(o)
|
||||
objread(r, aux);
|
||||
else
|
||||
dirread9p(r, objgen, aux);
|
||||
break;
|
||||
case Qcommitmsg:
|
||||
readbuf(r, o->commit->msg, o->commit->nmsg);
|
||||
break;
|
||||
case Qcommitparent:
|
||||
readcommitparent(r, o);
|
||||
break;
|
||||
case Qcommithash:
|
||||
snprint(buf, sizeof(buf), "%H\n", o->hash);
|
||||
readstr(r, buf);
|
||||
break;
|
||||
case Qcommitauthor:
|
||||
snprint(buf, sizeof(buf), "%s\n", o->commit->author);
|
||||
readstr(r, buf);
|
||||
break;
|
||||
case Qctl:
|
||||
e = readctl(r);
|
||||
break;
|
||||
case Qhead:
|
||||
/* Empty repositories have no HEAD */
|
||||
if(o == nil)
|
||||
r->ofcall.count = 0;
|
||||
else
|
||||
objread(r, aux);
|
||||
break;
|
||||
case Qcommit:
|
||||
case Qcommittree:
|
||||
case Qcommitdata:
|
||||
objread(r, aux);
|
||||
break;
|
||||
default:
|
||||
e = Egreg;
|
||||
}
|
||||
respond(r, e);
|
||||
}
|
||||
|
||||
static void
|
||||
gitstat(Req *r)
|
||||
{
|
||||
Gitaux *aux;
|
||||
Crumb *c;
|
||||
|
||||
aux = r->fid->aux;
|
||||
c = crumb(aux, 0);
|
||||
r->d.uid = estrdup9p(username);
|
||||
r->d.gid = estrdup9p(username);
|
||||
r->d.muid = estrdup9p(username);
|
||||
r->d.qid = r->fid->qid;
|
||||
r->d.mtime = c->mtime;
|
||||
r->d.atime = c->mtime;
|
||||
r->d.mode = c->mode;
|
||||
if(c->obj)
|
||||
obj2dir(&r->d, c, c->obj, c->name);
|
||||
else
|
||||
r->d.name = estrdup9p(c->name);
|
||||
respond(r, nil);
|
||||
}
|
||||
|
||||
Srv gitsrv = {
|
||||
.attach=gitattach,
|
||||
.walk1=gitwalk1,
|
||||
.clone=gitclone,
|
||||
.read=gitread,
|
||||
.stat=gitstat,
|
||||
.destroyfid=gitdestroyfid,
|
||||
};
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-d]\n", argv0);
|
||||
fprint(2, "\t-d: debug\n");
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
gitinit();
|
||||
ARGBEGIN{
|
||||
case 'd': chatty9p++; break;
|
||||
default: usage(); break;
|
||||
}ARGEND;
|
||||
if(argc != 0)
|
||||
usage();
|
||||
|
||||
username = getuser();
|
||||
branches = emalloc(sizeof(char*));
|
||||
branches[0] = nil;
|
||||
postmountsrv(&gitsrv, nil, "/mnt/git", MCREATE);
|
||||
exits(nil);
|
||||
}
|
303
sys/src/cmd/git/git.h
Normal file
303
sys/src/cmd/git/git.h
Normal file
|
@ -0,0 +1,303 @@
|
|||
#include <bio.h>
|
||||
#include <mp.h>
|
||||
#include <libsec.h>
|
||||
#include <flate.h>
|
||||
#include <regexp.h>
|
||||
|
||||
typedef struct Conn Conn;
|
||||
typedef struct Hash Hash;
|
||||
typedef struct Delta Delta;
|
||||
typedef struct Cinfo Cinfo;
|
||||
typedef struct Tinfo Tinfo;
|
||||
typedef struct Object Object;
|
||||
typedef struct Objset Objset;
|
||||
typedef struct Pack Pack;
|
||||
typedef struct Buf Buf;
|
||||
typedef struct Dirent Dirent;
|
||||
typedef struct Idxent Idxent;
|
||||
typedef struct Objlist Objlist;
|
||||
typedef struct Dtab Dtab;
|
||||
typedef struct Dblock Dblock;
|
||||
|
||||
enum {
|
||||
Pathmax = 512,
|
||||
Npackcache = 32,
|
||||
Hashsz = 20,
|
||||
Pktmax = 65536,
|
||||
};
|
||||
|
||||
enum {
|
||||
GNone = 0,
|
||||
GCommit = 1,
|
||||
GTree = 2,
|
||||
GBlob = 3,
|
||||
GTag = 4,
|
||||
GOdelta = 6,
|
||||
GRdelta = 7,
|
||||
};
|
||||
|
||||
enum {
|
||||
Cloaded = 1 << 0,
|
||||
Cidx = 1 << 1,
|
||||
Ccache = 1 << 2,
|
||||
Cexist = 1 << 3,
|
||||
Cparsed = 1 << 5,
|
||||
Cthin = 1 << 6,
|
||||
};
|
||||
|
||||
enum {
|
||||
ConnGit,
|
||||
ConnGit9,
|
||||
ConnSsh,
|
||||
ConnHttp,
|
||||
};
|
||||
|
||||
struct Objlist {
|
||||
int idx;
|
||||
|
||||
int fd;
|
||||
int state;
|
||||
int stage;
|
||||
|
||||
Dir *top;
|
||||
int ntop;
|
||||
int topidx;
|
||||
Dir *loose;
|
||||
int nloose;
|
||||
int looseidx;
|
||||
Dir *pack;
|
||||
int npack;
|
||||
int packidx;
|
||||
int nent;
|
||||
int entidx;
|
||||
};
|
||||
|
||||
struct Hash {
|
||||
uchar h[20];
|
||||
};
|
||||
|
||||
struct Conn {
|
||||
int type;
|
||||
int rfd;
|
||||
int wfd;
|
||||
|
||||
/* only used by http */
|
||||
int cfd;
|
||||
char *url; /* note, first GET uses a different url */
|
||||
char *dir;
|
||||
char *direction;
|
||||
};
|
||||
|
||||
struct Dirent {
|
||||
char *name;
|
||||
int mode;
|
||||
Hash h;
|
||||
char ismod;
|
||||
char islink;
|
||||
};
|
||||
|
||||
struct Object {
|
||||
/* Git data */
|
||||
Hash hash;
|
||||
int type;
|
||||
|
||||
/* Cache */
|
||||
int id;
|
||||
int flag;
|
||||
int refs;
|
||||
Object *next;
|
||||
Object *prev;
|
||||
|
||||
/* For indexing */
|
||||
vlong off;
|
||||
vlong len;
|
||||
u32int crc;
|
||||
|
||||
/* Everything below here gets cleared */
|
||||
char *all;
|
||||
char *data;
|
||||
/* size excludes header */
|
||||
vlong size;
|
||||
|
||||
/* Significant win on memory use */
|
||||
union {
|
||||
Cinfo *commit;
|
||||
Tinfo *tree;
|
||||
};
|
||||
};
|
||||
|
||||
struct Tinfo {
|
||||
/* Tree */
|
||||
Dirent *ent;
|
||||
int nent;
|
||||
};
|
||||
|
||||
struct Cinfo {
|
||||
/* Commit */
|
||||
Hash *parent;
|
||||
int nparent;
|
||||
Hash tree;
|
||||
char *author;
|
||||
char *committer;
|
||||
char *msg;
|
||||
int nmsg;
|
||||
vlong ctime;
|
||||
vlong mtime;
|
||||
};
|
||||
|
||||
struct Objset {
|
||||
Object **obj;
|
||||
int nobj;
|
||||
int sz;
|
||||
};
|
||||
|
||||
struct Dtab {
|
||||
Object *o;
|
||||
uchar *base;
|
||||
int nbase;
|
||||
Dblock *b;
|
||||
int nb;
|
||||
int sz;
|
||||
};
|
||||
|
||||
struct Dblock {
|
||||
uchar *buf;
|
||||
int len;
|
||||
int off;
|
||||
u64int hash;
|
||||
};
|
||||
|
||||
struct Delta {
|
||||
int cpy;
|
||||
int off;
|
||||
int len;
|
||||
};
|
||||
|
||||
|
||||
#define GETBE16(b)\
|
||||
((((b)[0] & 0xFFul) << 8) | \
|
||||
(((b)[1] & 0xFFul) << 0))
|
||||
|
||||
#define GETBE32(b)\
|
||||
((((b)[0] & 0xFFul) << 24) | \
|
||||
(((b)[1] & 0xFFul) << 16) | \
|
||||
(((b)[2] & 0xFFul) << 8) | \
|
||||
(((b)[3] & 0xFFul) << 0))
|
||||
#define GETBE64(b)\
|
||||
((((b)[0] & 0xFFull) << 56) | \
|
||||
(((b)[1] & 0xFFull) << 48) | \
|
||||
(((b)[2] & 0xFFull) << 40) | \
|
||||
(((b)[3] & 0xFFull) << 32) | \
|
||||
(((b)[4] & 0xFFull) << 24) | \
|
||||
(((b)[5] & 0xFFull) << 16) | \
|
||||
(((b)[6] & 0xFFull) << 8) | \
|
||||
(((b)[7] & 0xFFull) << 0))
|
||||
|
||||
#define PUTBE16(b, n)\
|
||||
do{ \
|
||||
(b)[0] = (n) >> 8; \
|
||||
(b)[1] = (n) >> 0; \
|
||||
} while(0)
|
||||
|
||||
#define PUTBE32(b, n)\
|
||||
do{ \
|
||||
(b)[0] = (n) >> 24; \
|
||||
(b)[1] = (n) >> 16; \
|
||||
(b)[2] = (n) >> 8; \
|
||||
(b)[3] = (n) >> 0; \
|
||||
} while(0)
|
||||
|
||||
#define PUTBE64(b, n)\
|
||||
do{ \
|
||||
(b)[0] = (n) >> 56; \
|
||||
(b)[1] = (n) >> 48; \
|
||||
(b)[2] = (n) >> 40; \
|
||||
(b)[3] = (n) >> 32; \
|
||||
(b)[4] = (n) >> 24; \
|
||||
(b)[5] = (n) >> 16; \
|
||||
(b)[6] = (n) >> 8; \
|
||||
(b)[7] = (n) >> 0; \
|
||||
} while(0)
|
||||
|
||||
#define QDIR(qid) ((int)(qid)->path & (0xff))
|
||||
#define isblank(c) \
|
||||
(((c) != '\n') && isspace(c))
|
||||
|
||||
extern Reprog *authorpat;
|
||||
extern Objset objcache;
|
||||
extern Hash Zhash;
|
||||
extern int chattygit;
|
||||
extern int cachemax;
|
||||
extern int interactive;
|
||||
|
||||
#pragma varargck type "H" Hash
|
||||
#pragma varargck type "T" int
|
||||
#pragma varargck type "O" Object*
|
||||
#pragma varargck type "Q" Qid
|
||||
int Hfmt(Fmt*);
|
||||
int Tfmt(Fmt*);
|
||||
int Ofmt(Fmt*);
|
||||
int Qfmt(Fmt*);
|
||||
|
||||
void gitinit(void);
|
||||
|
||||
/* object io */
|
||||
int resolverefs(Hash **, char *);
|
||||
int resolveref(Hash *, char *);
|
||||
int listrefs(Hash **, char ***);
|
||||
Object *ancestor(Object *, Object *);
|
||||
int findtwixt(Hash *, int, Hash *, int, Object ***, int *);
|
||||
Object *readobject(Hash);
|
||||
Object *clearedobject(Hash, int);
|
||||
void parseobject(Object *);
|
||||
int indexpack(char *, char *, Hash);
|
||||
int writepack(int, Hash*, int, Hash*, int, Hash*);
|
||||
int hasheq(Hash *, Hash *);
|
||||
Object *ref(Object *);
|
||||
void unref(Object *);
|
||||
void cache(Object *);
|
||||
Object *emptydir(void);
|
||||
|
||||
/* object sets */
|
||||
void osinit(Objset *);
|
||||
void osclear(Objset *);
|
||||
void osadd(Objset *, Object *);
|
||||
int oshas(Objset *, Hash);
|
||||
Object *osfind(Objset *, Hash);
|
||||
|
||||
/* object listing */
|
||||
Objlist *mkols(void);
|
||||
int olsnext(Objlist *, Hash *);
|
||||
void olsfree(Objlist *);
|
||||
|
||||
/* util functions */
|
||||
#define dprint(lvl, ...) \
|
||||
if(chattygit >= lvl) _dprint(__VA_ARGS__)
|
||||
void _dprint(char *, ...);
|
||||
void *eamalloc(ulong, ulong);
|
||||
void *emalloc(ulong);
|
||||
void *earealloc(void *, ulong, ulong);
|
||||
void *erealloc(void *, ulong);
|
||||
char *estrdup(char *);
|
||||
int slurpdir(char *, Dir **);
|
||||
int hparse(Hash *, char *);
|
||||
int hassuffix(char *, char *);
|
||||
int swapsuffix(char *, int, char *, char *, char *);
|
||||
char *strip(char *);
|
||||
int findrepo(char *, int);
|
||||
int showprogress(int, int);
|
||||
|
||||
/* packing */
|
||||
void dtinit(Dtab *, Object*);
|
||||
void dtclear(Dtab*);
|
||||
Delta* deltify(Object*, Dtab*, int*);
|
||||
|
||||
/* proto handling */
|
||||
int readpkt(Conn*, char*, int);
|
||||
int writepkt(Conn*, char*, int);
|
||||
int flushpkt(Conn*);
|
||||
void initconn(Conn*, int, int);
|
||||
int gitconnect(Conn *, char *, char *);
|
||||
int readphase(Conn *);
|
||||
int writephase(Conn *);
|
||||
void closeconn(Conn *);
|
99
sys/src/cmd/git/import
Executable file
99
sys/src/cmd/git/import
Executable file
|
@ -0,0 +1,99 @@
|
|||
#!/bin/rc
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
diffpath=/tmp/gitimport.$pid.diff
|
||||
fn sigexit {
|
||||
rm -f $diffpath
|
||||
}
|
||||
|
||||
fn apply @{
|
||||
git/fs
|
||||
email=''
|
||||
name=''
|
||||
msg=''
|
||||
parents='-p'^`{git/query HEAD}
|
||||
branch=`{git/branch}
|
||||
if(test -e /mnt/git/branch/$branch/tree)
|
||||
refpath=.git/refs/$branch
|
||||
if not if(test -e /mnt/git/object/$branch/tree)
|
||||
refpath=.git/HEAD
|
||||
if not
|
||||
die 'invalid branch:' $branch
|
||||
awk '
|
||||
BEGIN{
|
||||
state="headers"
|
||||
}
|
||||
state=="headers" && /^From:/ {
|
||||
sub(/^From:[ \t]*/, "", $0);
|
||||
name=$0;
|
||||
email=$0;
|
||||
sub(/[ \t]*<.*$/, "", name);
|
||||
sub(/.*</, "", email);
|
||||
sub(/>/, "", email);
|
||||
}
|
||||
state=="headers" && /^Date:/{
|
||||
sub(/^Date:[ \t]*/, "", $0)
|
||||
date=$0
|
||||
}
|
||||
state=="headers" && /^Subject:/{
|
||||
sub(/^Subject:[ \t]*(\[PATCH( [0-9]+\/[0-9]+)?\])*[ \t]*/, "", $0);
|
||||
gotmsg = 1
|
||||
print > "/env/msg"
|
||||
}
|
||||
state=="headers" && /^$/ {
|
||||
state="body"
|
||||
next
|
||||
}
|
||||
(state=="headers" || state=="body") && (/^diff/ || /^---[ ]*$/){
|
||||
state="diff"
|
||||
}
|
||||
state=="body" {
|
||||
print > "/env/msg"
|
||||
}
|
||||
state=="diff" {
|
||||
print > ENVIRON["diffpath"]
|
||||
}
|
||||
END{
|
||||
if(state != "diff")
|
||||
exit("malformed patch: " state);
|
||||
if(name == "" || email == "" || date == "" || gotmsg == "")
|
||||
exit("missing headers");
|
||||
printf "%s", name > "/env/name"
|
||||
printf "%s", email > "/env/email"
|
||||
printf "%s", date > "/env/date"
|
||||
}
|
||||
' || die 'could not import:' $status
|
||||
|
||||
# force re-reading env
|
||||
rc -c '
|
||||
echo applying $msg | sed 1q
|
||||
date=`{seconds $date}
|
||||
if(! files=`$nl{ape/patch -Ep1 < $diffpath | grep ''^patching file'' | sed ''s/^patching file `(.*)''''/\1/''})
|
||||
die ''patch failed''
|
||||
for(f in $files){
|
||||
if(test -e $f)
|
||||
git/add $f
|
||||
if not
|
||||
git/add -r $f
|
||||
}
|
||||
git/walk -fRMA $files
|
||||
if(~ $#nocommit 0){
|
||||
hash=`{git/save -n $name -e $email -m $msg -d $date $parents $files}
|
||||
echo $hash > $refpath
|
||||
}
|
||||
status=''''
|
||||
'
|
||||
}
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='n:nocommit'; args='file ...'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
patches=(/fd/0)
|
||||
if(! ~ $#* 0)
|
||||
patches=$*
|
||||
for(f in $patches)
|
||||
apply < $f || die $status
|
||||
exit ''
|
38
sys/src/cmd/git/init
Executable file
38
sys/src/cmd/git/init
Executable file
|
@ -0,0 +1,38 @@
|
|||
#!/bin/rc -e
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
flagfmt='u:upstream upstream,b:branch branch'; args='name'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
dir=$1
|
||||
if(~ $#dir 0)
|
||||
dir=.
|
||||
if(~ $#branch 0)
|
||||
branch=front
|
||||
if(test -e $dir/.git)
|
||||
die $dir/.git already exists
|
||||
name=`{basename `{cleanname -d `{pwd} $dir}}
|
||||
if(~ $#upstream 0){
|
||||
upstream=`{git/conf 'defaults "origin".baseurl'}
|
||||
if(! ~ $#upstream 0)
|
||||
upstream=$upstream/$name
|
||||
}
|
||||
|
||||
mkdir -p $dir/.git/refs/^(heads remotes)
|
||||
>$dir/.git/config {
|
||||
echo '[core]'
|
||||
echo ' repositoryformatversion = p9.0'
|
||||
if(! ~ $#upstream 0){
|
||||
echo '[remote "origin"]'
|
||||
echo ' url = '$upstream
|
||||
}
|
||||
echo '[branch "'$branch'"]'
|
||||
echo ' remote = origin'
|
||||
}
|
||||
|
||||
>$dir/.git/HEAD {
|
||||
echo ref: refs/heads/$branch
|
||||
}
|
||||
|
||||
exit ''
|
329
sys/src/cmd/git/log.c
Normal file
329
sys/src/cmd/git/log.c
Normal file
|
@ -0,0 +1,329 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include "git.h"
|
||||
|
||||
typedef struct Pfilt Pfilt;
|
||||
struct Pfilt {
|
||||
char *elt;
|
||||
int show;
|
||||
Pfilt *sub;
|
||||
int nsub;
|
||||
};
|
||||
|
||||
Biobuf *out;
|
||||
char *queryexpr;
|
||||
char *commitid;
|
||||
int shortlog;
|
||||
|
||||
Object **heap;
|
||||
int nheap;
|
||||
int heapsz;
|
||||
Objset done;
|
||||
Pfilt *pathfilt;
|
||||
|
||||
void
|
||||
filteradd(Pfilt *pf, char *path)
|
||||
{
|
||||
char *p, *e;
|
||||
int i;
|
||||
|
||||
if((e = strchr(path, '/')) != nil)
|
||||
p = smprint("%.*s", (int)(e - path), path);
|
||||
else
|
||||
p = strdup(path);
|
||||
|
||||
while(e != nil && *e == '/')
|
||||
e++;
|
||||
for(i = 0; i < pf->nsub; i++){
|
||||
if(strcmp(pf->sub[i].elt, p) == 0){
|
||||
pf->sub[i].show = pf->sub[i].show || (e == nil);
|
||||
if(e != nil)
|
||||
filteradd(&pf->sub[i], e);
|
||||
free(p);
|
||||
return;
|
||||
}
|
||||
}
|
||||
pf->sub = earealloc(pf->sub, pf->nsub+1, sizeof(Pfilt));
|
||||
pf->sub[pf->nsub].elt = p;
|
||||
pf->sub[pf->nsub].show = (e == nil);
|
||||
pf->sub[pf->nsub].nsub = 0;
|
||||
pf->sub[pf->nsub].sub = nil;
|
||||
if(e != nil)
|
||||
filteradd(&pf->sub[pf->nsub], e);
|
||||
pf->nsub++;
|
||||
}
|
||||
|
||||
Hash
|
||||
lookup(Pfilt *pf, Object *o)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < o->tree->nent; i++)
|
||||
if(strcmp(o->tree->ent[i].name, pf->elt) == 0)
|
||||
return o->tree->ent[i].h;
|
||||
return Zhash;
|
||||
}
|
||||
|
||||
int
|
||||
filtermatch1(Pfilt *pf, Object *t, Object *pt)
|
||||
{
|
||||
Object *a, *b;
|
||||
Hash ha, hb;
|
||||
int i, r;
|
||||
|
||||
if(pf->show)
|
||||
return 1;
|
||||
if(t->type != pt->type)
|
||||
return 1;
|
||||
if(t->type != GTree)
|
||||
return 0;
|
||||
|
||||
for(i = 0; i < pf->nsub; i++){
|
||||
ha = lookup(&pf->sub[i], t);
|
||||
hb = lookup(&pf->sub[i], pt);
|
||||
if(hasheq(&ha, &hb))
|
||||
continue;
|
||||
if(hasheq(&ha, &Zhash) || hasheq(&hb, &Zhash))
|
||||
return 1;
|
||||
if((a = readobject(ha)) == nil)
|
||||
sysfatal("read %H: %r", ha);
|
||||
if((b = readobject(hb)) == nil)
|
||||
sysfatal("read %H: %r", hb);
|
||||
r = filtermatch1(&pf->sub[i], a, b);
|
||||
unref(a);
|
||||
unref(b);
|
||||
if(r)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
filtermatch(Object *o)
|
||||
{
|
||||
Object *t, *p, *pt;
|
||||
int i, r;
|
||||
|
||||
if(pathfilt == nil)
|
||||
return 1;
|
||||
if((t = readobject(o->commit->tree)) == nil)
|
||||
sysfatal("read %H: %r", o->commit->tree);
|
||||
for(i = 0; i < o->commit->nparent; i++){
|
||||
if((p = readobject(o->commit->parent[i])) == nil)
|
||||
sysfatal("read %H: %r", o->commit->parent[i]);
|
||||
if((pt = readobject(p->commit->tree)) == nil)
|
||||
sysfatal("read %H: %r", o->commit->tree);
|
||||
r = filtermatch1(pathfilt, t, pt);
|
||||
unref(p);
|
||||
unref(pt);
|
||||
if(r)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static char*
|
||||
nextline(char *p, char *e)
|
||||
{
|
||||
for(; p != e; p++)
|
||||
if(*p == '\n')
|
||||
break;
|
||||
return p;
|
||||
}
|
||||
|
||||
static void
|
||||
show(Object *o)
|
||||
{
|
||||
Tm tm;
|
||||
char *p, *q, *e;
|
||||
|
||||
assert(o->type == GCommit);
|
||||
if(!filtermatch(o))
|
||||
return;
|
||||
|
||||
if(shortlog){
|
||||
p = o->commit->msg;
|
||||
e = p + o->commit->nmsg;
|
||||
q = nextline(p, e);
|
||||
Bprint(out, "%H ", o->hash);
|
||||
Bwrite(out, p, q - p);
|
||||
Bputc(out, '\n');
|
||||
}else{
|
||||
tmtime(&tm, o->commit->mtime, tzload("local"));
|
||||
Bprint(out, "Hash:\t%H\n", o->hash);
|
||||
Bprint(out, "Author:\t%s\n", o->commit->author);
|
||||
Bprint(out, "Date:\t%τ\n", tmfmt(&tm, "WW MMM D hh:mm:ss z YYYY"));
|
||||
Bprint(out, "\n");
|
||||
p = o->commit->msg;
|
||||
e = p + o->commit->nmsg;
|
||||
for(; p != e; p = q){
|
||||
q = nextline(p, e);
|
||||
Bputc(out, '\t');
|
||||
Bwrite(out, p, q - p);
|
||||
Bputc(out, '\n');
|
||||
if(q != e)
|
||||
q++;
|
||||
}
|
||||
Bprint(out, "\n");
|
||||
}
|
||||
Bflush(out);
|
||||
}
|
||||
|
||||
static void
|
||||
showquery(char *q)
|
||||
{
|
||||
Object *o;
|
||||
Hash *h;
|
||||
int n, i;
|
||||
|
||||
if((n = resolverefs(&h, q)) == -1)
|
||||
sysfatal("resolve: %r");
|
||||
for(i = 0; i < n; i++){
|
||||
if((o = readobject(h[i])) == nil)
|
||||
sysfatal("read %H: %r", h[i]);
|
||||
show(o);
|
||||
unref(o);
|
||||
}
|
||||
exits(nil);
|
||||
}
|
||||
|
||||
static void
|
||||
qput(Object *o)
|
||||
{
|
||||
Object *p;
|
||||
int i;
|
||||
|
||||
if(oshas(&done, o->hash))
|
||||
return;
|
||||
osadd(&done, o);
|
||||
if(nheap == heapsz){
|
||||
heapsz *= 2;
|
||||
heap = earealloc(heap, heapsz, sizeof(Object*));
|
||||
}
|
||||
heap[nheap++] = o;
|
||||
for(i = nheap - 1; i > 0; i = (i-1)/2){
|
||||
o = heap[i];
|
||||
p = heap[(i-1)/2];
|
||||
if(o->commit->mtime < p->commit->mtime)
|
||||
break;
|
||||
heap[i] = p;
|
||||
heap[(i-1)/2] = o;
|
||||
}
|
||||
}
|
||||
|
||||
static Object*
|
||||
qpop(void)
|
||||
{
|
||||
Object *o, *t;
|
||||
int i, l, r, m;
|
||||
|
||||
if(nheap == 0)
|
||||
return nil;
|
||||
|
||||
i = 0;
|
||||
o = heap[0];
|
||||
t = heap[--nheap];
|
||||
heap[0] = t;
|
||||
while(1){
|
||||
m = i;
|
||||
l = 2*i+1;
|
||||
r = 2*i+2;
|
||||
if(l < nheap && heap[m]->commit->mtime < heap[l]->commit->mtime)
|
||||
m = l;
|
||||
if(r < nheap && heap[m]->commit->mtime < heap[r]->commit->mtime)
|
||||
m = r;
|
||||
else
|
||||
break;
|
||||
t = heap[m];
|
||||
heap[m] = heap[i];
|
||||
heap[i] = t;
|
||||
i = m;
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
static void
|
||||
showcommits(char *c)
|
||||
{
|
||||
Object *o, *p;
|
||||
int i;
|
||||
Hash h;
|
||||
|
||||
if(c == nil)
|
||||
c = "HEAD";
|
||||
if(resolveref(&h, c) == -1)
|
||||
sysfatal("resolve %s: %r", c);
|
||||
if((o = readobject(h)) == nil)
|
||||
sysfatal("load %H: %r", h);
|
||||
heapsz = 8;
|
||||
heap = eamalloc(heapsz, sizeof(Object*));
|
||||
osinit(&done);
|
||||
qput(o);
|
||||
while((o = qpop()) != nil){
|
||||
show(o);
|
||||
for(i = 0; i < o->commit->nparent; i++){
|
||||
if((p = readobject(o->commit->parent[i])) == nil)
|
||||
sysfatal("load %H: %r", o->commit->parent[i]);
|
||||
qput(p);
|
||||
}
|
||||
unref(o);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-s] [-e expr | -c commit] files..\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char path[1024], repo[1024], *p, *r;
|
||||
int i;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'e':
|
||||
queryexpr = EARGF(usage());
|
||||
break;
|
||||
case 'c':
|
||||
commitid = EARGF(usage());
|
||||
break;
|
||||
case 's':
|
||||
shortlog++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}ARGEND;
|
||||
|
||||
if(findrepo(repo, sizeof(repo)) == -1)
|
||||
sysfatal("find root: %r");
|
||||
if(argc != 0){
|
||||
if(getwd(path, sizeof(path)) == nil)
|
||||
sysfatal("getwd: %r");
|
||||
if(strlen(path) < strlen(repo))
|
||||
sysfatal("path changed");
|
||||
p = path + strlen(repo);
|
||||
pathfilt = emalloc(sizeof(Pfilt));
|
||||
for(i = 0; i < argc; i++){
|
||||
r = smprint("./%s/%s", p, argv[i]);
|
||||
cleanname(r);
|
||||
filteradd(pathfilt, r);
|
||||
free(r);
|
||||
}
|
||||
}
|
||||
if(chdir(repo) == -1)
|
||||
sysfatal("chdir: %r");
|
||||
|
||||
gitinit();
|
||||
tmfmtinstall();
|
||||
out = Bfdopen(1, OWRITE);
|
||||
if(queryexpr != nil)
|
||||
showquery(queryexpr);
|
||||
else
|
||||
showcommits(commitid);
|
||||
exits(nil);
|
||||
}
|
47
sys/src/cmd/git/merge
Executable file
47
sys/src/cmd/git/merge
Executable file
|
@ -0,0 +1,47 @@
|
|||
#!/bin/rc -e
|
||||
rfork ne
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
fn merge{
|
||||
ourbr=/mnt/git/object/$1/tree
|
||||
basebr=/mnt/git/object/$2/tree
|
||||
theirbr=/mnt/git/object/$3/tree
|
||||
|
||||
all=`$nl{{git/query -c $1 $2; git/query -c $2 $3} | sed 's/^..//' | \
|
||||
subst -g '^('$ourbr'|'$basebr'|'$theirbr')/*' | sort | uniq}
|
||||
for(f in $all){
|
||||
ours=$ourbr/$f
|
||||
base=$basebr/$f
|
||||
theirs=$theirbr/$f
|
||||
merge1 $f $theirs $base $ours
|
||||
}
|
||||
}
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt=''; args='theirs'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
if(! ~ $#* 1)
|
||||
exec aux/usage
|
||||
|
||||
theirs=`{git/query $1}
|
||||
ours=`{git/query HEAD}
|
||||
base=`{git/query $theirs ^ ' ' ^ $ours ^ '@'}
|
||||
|
||||
if(~ $base $theirs)
|
||||
die 'nothing to merge, doofus'
|
||||
if(! git/walk -q)
|
||||
die 'dirty work tree, refusing to merge'
|
||||
if(~ $base $ours){
|
||||
>[1=2] echo 'fast forwarding...'
|
||||
echo $theirs > .git/refs/`{git/branch}
|
||||
git/revert .
|
||||
exit ''
|
||||
}
|
||||
echo $ours >> .git/index9/merge-parents
|
||||
echo $theirs >> .git/index9/merge-parents
|
||||
|
||||
merge $ours $base $theirs
|
||||
>[1=2] echo 'merge complete: remember to commit'
|
||||
exit ''
|
57
sys/src/cmd/git/mkfile
Normal file
57
sys/src/cmd/git/mkfile
Normal file
|
@ -0,0 +1,57 @@
|
|||
</$objtype/mkfile
|
||||
|
||||
BIN=/$objtype/bin/git
|
||||
TARG=\
|
||||
conf\
|
||||
fetch\
|
||||
fs\
|
||||
log\
|
||||
query\
|
||||
repack\
|
||||
save\
|
||||
send\
|
||||
serve\
|
||||
walk
|
||||
|
||||
RC=\
|
||||
add\
|
||||
branch\
|
||||
clone\
|
||||
commit\
|
||||
compat\
|
||||
diff\
|
||||
export\
|
||||
import\
|
||||
init\
|
||||
merge\
|
||||
pull\
|
||||
push\
|
||||
rebase\
|
||||
revert\
|
||||
rm
|
||||
|
||||
OFILES=\
|
||||
delta.$O\
|
||||
objset.$O\
|
||||
ols.$O\
|
||||
pack.$O\
|
||||
proto.$O\
|
||||
util.$O\
|
||||
ref.$O
|
||||
|
||||
HFILES=git.h
|
||||
|
||||
</sys/src/cmd/mkmany
|
||||
|
||||
# Override install target to install rc.
|
||||
install:V:
|
||||
mkdir -p $BIN
|
||||
mkdir -p /sys/lib/git
|
||||
for (i in $TARG)
|
||||
mk $MKFLAGS $i.install
|
||||
for (i in $RC)
|
||||
mk $MKFLAGS $i.rcinstall
|
||||
|
||||
%.rcinstall:V:
|
||||
cp $stem $BIN/$stem
|
||||
chmod +x $BIN/$stem
|
67
sys/src/cmd/git/objset.c
Normal file
67
sys/src/cmd/git/objset.c
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
void
|
||||
osinit(Objset *s)
|
||||
{
|
||||
s->sz = 16;
|
||||
s->nobj = 0;
|
||||
s->obj = eamalloc(s->sz, sizeof(Hash));
|
||||
}
|
||||
|
||||
void
|
||||
osclear(Objset *s)
|
||||
{
|
||||
free(s->obj);
|
||||
}
|
||||
|
||||
void
|
||||
osadd(Objset *s, Object *o)
|
||||
{
|
||||
u32int probe;
|
||||
Object **obj;
|
||||
int i, sz;
|
||||
|
||||
probe = GETBE32(o->hash.h) % s->sz;
|
||||
while(s->obj[probe]){
|
||||
if(hasheq(&s->obj[probe]->hash, &o->hash)){
|
||||
s->obj[probe] = o;
|
||||
return;
|
||||
}
|
||||
probe = (probe + 1) % s->sz;
|
||||
}
|
||||
assert(s->obj[probe] == nil);
|
||||
s->obj[probe] = o;
|
||||
s->nobj++;
|
||||
if(s->sz < 2*s->nobj){
|
||||
sz = s->sz;
|
||||
obj = s->obj;
|
||||
|
||||
s->sz *= 2;
|
||||
s->nobj = 0;
|
||||
s->obj = eamalloc(s->sz, sizeof(Hash));
|
||||
for(i = 0; i < sz; i++)
|
||||
if(obj[i])
|
||||
osadd(s, obj[i]);
|
||||
free(obj);
|
||||
}
|
||||
}
|
||||
|
||||
Object*
|
||||
osfind(Objset *s, Hash h)
|
||||
{
|
||||
u32int probe;
|
||||
|
||||
for(probe = GETBE32(h.h) % s->sz; s->obj[probe]; probe = (probe + 1) % s->sz)
|
||||
if(hasheq(&s->obj[probe]->hash, &h))
|
||||
return s->obj[probe];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
oshas(Objset *s, Hash h)
|
||||
{
|
||||
return osfind(s, h) != nil;
|
||||
}
|
170
sys/src/cmd/git/ols.c
Normal file
170
sys/src/cmd/git/ols.c
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
#include "git.h"
|
||||
|
||||
enum {
|
||||
Sinit,
|
||||
Siter,
|
||||
};
|
||||
|
||||
static int
|
||||
crackidx(char *path, int *np)
|
||||
{
|
||||
int fd;
|
||||
char buf[4];
|
||||
|
||||
if((fd = open(path, OREAD)) == -1)
|
||||
return -1;
|
||||
if(seek(fd, 8 + 255*4, 0) == -1)
|
||||
return -1;
|
||||
if(readn(fd, buf, sizeof(buf)) != sizeof(buf))
|
||||
return -1;
|
||||
*np = GETBE32(buf);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int
|
||||
isloosedir(char *s)
|
||||
{
|
||||
return strlen(s) == 2 && isxdigit(s[0]) && isxdigit(s[1]);
|
||||
}
|
||||
|
||||
int
|
||||
endswith(char *n, char *s)
|
||||
{
|
||||
int nn, ns;
|
||||
|
||||
nn = strlen(n);
|
||||
ns = strlen(s);
|
||||
return nn > ns && strcmp(n + nn - ns, s) == 0;
|
||||
}
|
||||
|
||||
int
|
||||
olsreadpacked(Objlist *ols, Hash *h)
|
||||
{
|
||||
char *p;
|
||||
int i, j;
|
||||
|
||||
i = ols->packidx;
|
||||
j = ols->entidx;
|
||||
|
||||
if(ols->state == Siter)
|
||||
goto step;
|
||||
for(i = 0; i < ols->npack; i++){
|
||||
if(!endswith(ols->pack[i].name, ".idx"))
|
||||
continue;
|
||||
if((p = smprint(".git/objects/pack/%s", ols->pack[i].name)) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
ols->fd = crackidx(p, &ols->nent);
|
||||
free(p);
|
||||
if(ols->fd == -1)
|
||||
continue;
|
||||
j = 0;
|
||||
while(j < ols->nent){
|
||||
if(readn(ols->fd, h->h, sizeof(h->h)) != sizeof(h->h))
|
||||
continue;
|
||||
ols->state = Siter;
|
||||
ols->packidx = i;
|
||||
ols->entidx = j;
|
||||
return 0;
|
||||
step:
|
||||
j++;
|
||||
}
|
||||
close(ols->fd);
|
||||
}
|
||||
ols->state = Sinit;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
olsreadloose(Objlist *ols, Hash *h)
|
||||
{
|
||||
char buf[64], *p;
|
||||
int i, j, n;
|
||||
|
||||
i = ols->topidx;
|
||||
j = ols->looseidx;
|
||||
if(ols->state == Siter)
|
||||
goto step;
|
||||
for(i = 0; i < ols->ntop; i++){
|
||||
if(!isloosedir(ols->top[i].name))
|
||||
continue;
|
||||
if((p = smprint(".git/objects/%s", ols->top[i].name)) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
ols->fd = open(p, OREAD);
|
||||
free(p);
|
||||
if(ols->fd == -1)
|
||||
continue;
|
||||
while((ols->nloose = dirread(ols->fd, &ols->loose)) > 0){
|
||||
j = 0;
|
||||
while(j < ols->nloose){
|
||||
n = snprint(buf, sizeof(buf), "%s%s", ols->top[i].name, ols->loose[j].name);
|
||||
if(n >= sizeof(buf))
|
||||
goto step;
|
||||
if(hparse(h, buf) == -1)
|
||||
goto step;
|
||||
ols->state = Siter;
|
||||
ols->topidx = i;
|
||||
ols->looseidx = j;
|
||||
return 0;
|
||||
step:
|
||||
j++;
|
||||
}
|
||||
free(ols->loose);
|
||||
ols->loose = nil;
|
||||
}
|
||||
close(ols->fd);
|
||||
ols->fd = -1;
|
||||
}
|
||||
ols->state = Sinit;
|
||||
return -1;
|
||||
}
|
||||
|
||||
Objlist*
|
||||
mkols(void)
|
||||
{
|
||||
Objlist *ols;
|
||||
|
||||
ols = emalloc(sizeof(Objlist));
|
||||
if((ols->ntop = slurpdir(".git/objects", &ols->top)) == -1)
|
||||
sysfatal("read top level: %r");
|
||||
if((ols->npack = slurpdir(".git/objects/pack", &ols->pack)) == -1)
|
||||
ols->pack = nil;
|
||||
ols->fd = -1;
|
||||
return ols;
|
||||
}
|
||||
|
||||
void
|
||||
olsfree(Objlist *ols)
|
||||
{
|
||||
if(ols == nil)
|
||||
return;
|
||||
if(ols->fd != -1)
|
||||
close(ols->fd);
|
||||
free(ols->top);
|
||||
free(ols->loose);
|
||||
free(ols->pack);
|
||||
free(ols);
|
||||
}
|
||||
|
||||
int
|
||||
olsnext(Objlist *ols, Hash *h)
|
||||
{
|
||||
if(ols->stage == 0){
|
||||
if(olsreadloose(ols, h) != -1){
|
||||
ols->idx++;
|
||||
return 0;
|
||||
}
|
||||
ols->stage++;
|
||||
}
|
||||
if(ols->stage == 1){
|
||||
if(olsreadpacked(ols, h) != -1){
|
||||
ols->idx++;
|
||||
return 0;
|
||||
}
|
||||
ols->stage++;
|
||||
}
|
||||
return -1;
|
||||
}
|
1712
sys/src/cmd/git/pack.c
Normal file
1712
sys/src/cmd/git/pack.c
Normal file
File diff suppressed because it is too large
Load diff
459
sys/src/cmd/git/proto.c
Normal file
459
sys/src/cmd/git/proto.c
Normal file
|
@ -0,0 +1,459 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
#define Useragent "useragent git/2.24.1"
|
||||
#define Contenthdr "headers Content-Type: application/x-git-%s-pack-request"
|
||||
#define Accepthdr "headers Accept: application/x-git-%s-pack-result"
|
||||
|
||||
enum {
|
||||
Nproto = 16,
|
||||
Nport = 16,
|
||||
Nhost = 256,
|
||||
Npath = 128,
|
||||
Nrepo = 64,
|
||||
Nbranch = 32,
|
||||
};
|
||||
|
||||
void
|
||||
tracepkt(int v, char *pfx, char *b, int n)
|
||||
{
|
||||
char *f;
|
||||
int o, i;
|
||||
|
||||
if(chattygit < v)
|
||||
return;
|
||||
o = 0;
|
||||
f = emalloc(n*4 + 1);
|
||||
for(i = 0; i < n; i++){
|
||||
if(isprint(b[i])){
|
||||
f[o++] = b[i];
|
||||
continue;
|
||||
}
|
||||
f[o++] = '\\';
|
||||
switch(b[i]){
|
||||
case '\\': f[o++] = '\\'; break;
|
||||
case '\n': f[o++] = 'n'; break;
|
||||
case '\r': f[o++] = 'r'; break;
|
||||
case '\v': f[o++] = 'v'; break;
|
||||
case '\0': f[o++] = '0'; break;
|
||||
default:
|
||||
f[o++] = 'x';
|
||||
f[o++] = "0123456789abcdef"[(b[i]>>4)&0xf];
|
||||
f[o++] = "0123456789abcdef"[(b[i]>>0)&0xf];
|
||||
break;
|
||||
}
|
||||
}
|
||||
f[o] = '\0';
|
||||
fprint(2, "%s %04x:\t%s\n", pfx, n, f);
|
||||
free(f);
|
||||
}
|
||||
|
||||
int
|
||||
readpkt(Conn *c, char *buf, int nbuf)
|
||||
{
|
||||
char len[5];
|
||||
char *e;
|
||||
int n;
|
||||
|
||||
if(readn(c->rfd, len, 4) == -1)
|
||||
return -1;
|
||||
len[4] = 0;
|
||||
n = strtol(len, &e, 16);
|
||||
if(n == 0){
|
||||
dprint(1, "=r=> 0000\n");
|
||||
return 0;
|
||||
}
|
||||
if(e != len + 4 || n <= 4)
|
||||
sysfatal("pktline: bad length '%s'", len);
|
||||
n -= 4;
|
||||
if(n >= nbuf)
|
||||
sysfatal("pktline: undersize buffer");
|
||||
if(readn(c->rfd, buf, n) != n)
|
||||
return -1;
|
||||
buf[n] = 0;
|
||||
tracepkt(1, "=r=>", buf, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
writepkt(Conn *c, char *buf, int nbuf)
|
||||
{
|
||||
char len[5];
|
||||
|
||||
|
||||
snprint(len, sizeof(len), "%04x", nbuf + 4);
|
||||
if(write(c->wfd, len, 4) != 4)
|
||||
return -1;
|
||||
if(write(c->wfd, buf, nbuf) != nbuf)
|
||||
return -1;
|
||||
tracepkt(1, "<=w=", buf, nbuf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
flushpkt(Conn *c)
|
||||
{
|
||||
dprint(1, "<=w= 0000\n");
|
||||
return write(c->wfd, "0000", 4);
|
||||
}
|
||||
|
||||
static void
|
||||
grab(char *dst, int n, char *p, char *e)
|
||||
{
|
||||
int l;
|
||||
|
||||
l = e - p;
|
||||
if(l >= n)
|
||||
sysfatal("overlong component");
|
||||
memcpy(dst, p, l);
|
||||
dst[l] = 0;
|
||||
}
|
||||
|
||||
static int
|
||||
parseuri(char *uri, char *proto, char *host, char *port, char *path, char *repo)
|
||||
{
|
||||
char *s, *p, *q;
|
||||
int n, hasport;
|
||||
print("uri: \"%s\"\n", uri);
|
||||
|
||||
p = strstr(uri, "://");
|
||||
if(p == nil)
|
||||
snprint(proto, Nproto, "ssh");
|
||||
else if(strncmp(uri, "git+", 4) == 0)
|
||||
grab(proto, Nproto, uri + 4, p);
|
||||
else
|
||||
grab(proto, Nproto, uri, p);
|
||||
*port = 0;
|
||||
hasport = 1;
|
||||
if(strcmp(proto, "git") == 0)
|
||||
snprint(port, Nport, "9418");
|
||||
else if(strncmp(proto, "https", 5) == 0)
|
||||
snprint(port, Nport, "443");
|
||||
else if(strncmp(proto, "http", 4) == 0)
|
||||
snprint(port, Nport, "80");
|
||||
else if(strncmp(proto, "hjgit", 5) == 0)
|
||||
snprint(port, Nport, "17021");
|
||||
else if(strncmp(proto, "gits", 5) == 0)
|
||||
snprint(port, Nport, "9419");
|
||||
else
|
||||
hasport = 0;
|
||||
s = (p != nil) ? p + 3 : uri;
|
||||
p = nil;
|
||||
if(!hasport){
|
||||
p = strstr(s, ":");
|
||||
if(p != nil)
|
||||
p++;
|
||||
}
|
||||
if(p == nil)
|
||||
p = strstr(s, "/");
|
||||
if(p == nil || strlen(p) == 1){
|
||||
werrstr("missing path");
|
||||
return -1;
|
||||
}
|
||||
|
||||
q = memchr(s, ':', p - s);
|
||||
if(q){
|
||||
grab(host, Nhost, s, q);
|
||||
grab(port, Nport, q + 1, p);
|
||||
}else{
|
||||
grab(host, Nhost, s, p);
|
||||
}
|
||||
|
||||
snprint(path, Npath, "%s", p);
|
||||
if((q = strrchr(p, '/')) != nil)
|
||||
p = q + 1;
|
||||
if(strlen(p) == 0){
|
||||
werrstr("missing repository in uri");
|
||||
return -1;
|
||||
}
|
||||
n = strlen(p);
|
||||
if(hassuffix(p, ".git"))
|
||||
n -= 4;
|
||||
grab(repo, Nrepo, p, p + n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
webclone(Conn *c, char *url)
|
||||
{
|
||||
char buf[16];
|
||||
int n, conn;
|
||||
|
||||
if((c->cfd = open("/mnt/web/clone", ORDWR)) < 0)
|
||||
goto err;
|
||||
if((n = read(c->cfd, buf, sizeof(buf)-1)) == -1)
|
||||
goto err;
|
||||
buf[n] = 0;
|
||||
conn = atoi(buf);
|
||||
|
||||
/* github will behave differently based on useragent */
|
||||
if(write(c->cfd, Useragent, sizeof(Useragent)) == -1)
|
||||
return -1;
|
||||
dprint(1, "open url %s\n", url);
|
||||
if(fprint(c->cfd, "url %s", url) == -1)
|
||||
goto err;
|
||||
free(c->dir);
|
||||
c->dir = smprint("/mnt/web/%d", conn);
|
||||
return 0;
|
||||
err:
|
||||
if(c->cfd != -1)
|
||||
close(c->cfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
webopen(Conn *c, char *file, int mode)
|
||||
{
|
||||
char path[128];
|
||||
int fd;
|
||||
|
||||
snprint(path, sizeof(path), "%s/%s", c->dir, file);
|
||||
if((fd = open(path, mode)) == -1)
|
||||
return -1;
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int
|
||||
issmarthttp(Conn *c, char *direction)
|
||||
{
|
||||
char buf[Pktmax+1], svc[128];
|
||||
int n;
|
||||
|
||||
if((n = readpkt(c, buf, sizeof(buf))) == -1)
|
||||
sysfatal("http read: %r");
|
||||
buf[n] = 0;
|
||||
snprint(svc, sizeof(svc), "# service=git-%s-pack\n", direction);
|
||||
if(strncmp(svc, buf, n) != 0){
|
||||
werrstr("dumb http protocol not supported");
|
||||
return -1;
|
||||
}
|
||||
if(readpkt(c, buf, sizeof(buf)) != 0){
|
||||
werrstr("protocol garble: expected flushpkt");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dialhttp(Conn *c, char *host, char *port, char *path, char *direction)
|
||||
{
|
||||
char *geturl, *suff, *hsep, *psep;
|
||||
|
||||
suff = "";
|
||||
hsep = "";
|
||||
psep = "";
|
||||
if(port && strlen(port) != 0)
|
||||
hsep = ":";
|
||||
if(path && path[0] != '/')
|
||||
psep = "/";
|
||||
memset(c, 0, sizeof(*c));
|
||||
geturl = smprint("https://%s%s%s%s%s%s/info/refs?service=git-%s-pack", host, hsep, port, psep, path, suff, direction);
|
||||
c->type = ConnHttp;
|
||||
c->url = smprint("https://%s%s%s%s%s%s/git-%s-pack", host, hsep, port, psep, path, suff, direction);
|
||||
c->cfd = webclone(c, geturl);
|
||||
free(geturl);
|
||||
if(c->cfd == -1)
|
||||
return -1;
|
||||
c->rfd = webopen(c, "body", OREAD);
|
||||
c->wfd = -1;
|
||||
if(c->rfd == -1)
|
||||
return -1;
|
||||
if(issmarthttp(c, direction) == -1)
|
||||
return -1;
|
||||
c->direction = estrdup(direction);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dialssh(Conn *c, char *host, char *, char *path, char *direction)
|
||||
{
|
||||
int pid, pfd[2];
|
||||
char cmd[64];
|
||||
|
||||
if(pipe(pfd) == -1)
|
||||
sysfatal("unable to open pipe: %r");
|
||||
pid = fork();
|
||||
if(pid == -1)
|
||||
sysfatal("unable to fork");
|
||||
if(pid == 0){
|
||||
close(pfd[1]);
|
||||
dup(pfd[0], 0);
|
||||
dup(pfd[0], 1);
|
||||
snprint(cmd, sizeof(cmd), "git-%s-pack", direction);
|
||||
dprint(1, "exec ssh '%s' '%s' %s\n", host, cmd, path);
|
||||
execl("/bin/ssh", "ssh", host, cmd, path, nil);
|
||||
}else{
|
||||
close(pfd[0]);
|
||||
c->type = ConnSsh;
|
||||
c->rfd = pfd[1];
|
||||
c->wfd = dup(pfd[1], -1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
dialhjgit(Conn *c, char *host, char *port, char *path, char *direction, int auth)
|
||||
{
|
||||
char *ds, *p, *e, cmd[512];
|
||||
int pid, pfd[2];
|
||||
|
||||
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
||||
return -1;
|
||||
if(pipe(pfd) == -1)
|
||||
sysfatal("unable to open pipe: %r");
|
||||
pid = fork();
|
||||
if(pid == -1)
|
||||
sysfatal("unable to fork");
|
||||
if(pid == 0){
|
||||
close(pfd[1]);
|
||||
dup(pfd[0], 0);
|
||||
dup(pfd[0], 1);
|
||||
dprint(1, "exec tlsclient -a %s\n", ds);
|
||||
if(auth)
|
||||
execl("/bin/tlsclient", "tlsclient", "-a", ds, nil);
|
||||
else
|
||||
execl("/bin/tlsclient", "tlsclient", ds, nil);
|
||||
sysfatal("exec: %r");
|
||||
}else{
|
||||
close(pfd[0]);
|
||||
p = cmd;
|
||||
e = cmd + sizeof(cmd);
|
||||
p = seprint(p, e - 1, "git-%s-pack %s", direction, path);
|
||||
p = seprint(p + 1, e, "host=%s", host);
|
||||
c->type = ConnGit9;
|
||||
c->rfd = pfd[1];
|
||||
c->wfd = dup(pfd[1], -1);
|
||||
if(writepkt(c, cmd, p - cmd + 1) == -1){
|
||||
fprint(2, "failed to write message\n");
|
||||
close(c->rfd);
|
||||
close(c->wfd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
dialgit(Conn *c, char *host, char *port, char *path, char *direction)
|
||||
{
|
||||
char *ds, *p, *e, cmd[512];
|
||||
int fd;
|
||||
|
||||
if((ds = netmkaddr(host, "tcp", port)) == nil)
|
||||
return -1;
|
||||
dprint(1, "dial %s git-%s-pack %s\n", ds, direction, path);
|
||||
fd = dial(ds, nil, nil, nil);
|
||||
if(fd == -1)
|
||||
return -1;
|
||||
p = cmd;
|
||||
e = cmd + sizeof(cmd);
|
||||
p = seprint(p, e - 1, "git-%s-pack %s", direction, path);
|
||||
p = seprint(p + 1, e, "host=%s", host);
|
||||
c->type = ConnGit;
|
||||
c->rfd = fd;
|
||||
c->wfd = dup(fd, -1);
|
||||
if(writepkt(c, cmd, p - cmd + 1) == -1){
|
||||
fprint(2, "failed to write message\n");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
initconn(Conn *c, int rd, int wr)
|
||||
{
|
||||
c->type = ConnGit;
|
||||
c->rfd = rd;
|
||||
c->wfd = wr;
|
||||
}
|
||||
|
||||
int
|
||||
gitconnect(Conn *c, char *uri, char *direction)
|
||||
{
|
||||
char proto[Nproto], host[Nhost], port[Nport];
|
||||
char repo[Nrepo], path[Npath];
|
||||
|
||||
if(parseuri(uri, proto, host, port, path, repo) == -1){
|
||||
werrstr("bad uri %s", uri);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(c, 0, sizeof(Conn));
|
||||
if(strcmp(proto, "ssh") == 0)
|
||||
return dialssh(c, host, port, path, direction);
|
||||
else if(strcmp(proto, "git") == 0)
|
||||
return dialgit(c, host, port, path, direction);
|
||||
else if(strcmp(proto, "hjgit") == 0)
|
||||
return dialhjgit(c, host, port, path, direction, 1);
|
||||
else if(strcmp(proto, "gits") == 0)
|
||||
return dialhjgit(c, host, port, path, direction, 0);
|
||||
else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0)
|
||||
return dialhttp(c, host, port, path, direction);
|
||||
werrstr("unknown protocol %s", proto);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
writephase(Conn *c)
|
||||
{
|
||||
char hdr[128];
|
||||
int n;
|
||||
|
||||
dprint(1, "start write phase\n");
|
||||
if(c->type != ConnHttp)
|
||||
return 0;
|
||||
|
||||
if(c->wfd != -1)
|
||||
close(c->wfd);
|
||||
if(c->cfd != -1)
|
||||
close(c->cfd);
|
||||
if((c->cfd = webclone(c, c->url)) == -1)
|
||||
return -1;
|
||||
n = snprint(hdr, sizeof(hdr), Contenthdr, c->direction);
|
||||
if(write(c->cfd, hdr, n) == -1)
|
||||
return -1;
|
||||
n = snprint(hdr, sizeof(hdr), Accepthdr, c->direction);
|
||||
if(write(c->cfd, hdr, n) == -1)
|
||||
return -1;
|
||||
if((c->wfd = webopen(c, "postbody", OWRITE)) == -1)
|
||||
return -1;
|
||||
c->rfd = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
readphase(Conn *c)
|
||||
{
|
||||
dprint(1, "start read phase\n");
|
||||
if(c->type != ConnHttp)
|
||||
return 0;
|
||||
if(close(c->wfd) == -1)
|
||||
return -1;
|
||||
if((c->rfd = webopen(c, "body", OREAD)) == -1)
|
||||
return -1;
|
||||
c->wfd = -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
closeconn(Conn *c)
|
||||
{
|
||||
close(c->rfd);
|
||||
close(c->wfd);
|
||||
switch(c->type){
|
||||
case ConnGit:
|
||||
break;
|
||||
case ConnGit9:
|
||||
case ConnSsh:
|
||||
free(wait());
|
||||
break;
|
||||
case ConnHttp:
|
||||
close(c->cfd);
|
||||
break;
|
||||
}
|
||||
}
|
82
sys/src/cmd/git/pull
Executable file
82
sys/src/cmd/git/pull
Executable file
|
@ -0,0 +1,82 @@
|
|||
#!/bin/rc -e
|
||||
rfork en
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
fn update{
|
||||
branch=$1
|
||||
upstream=$2
|
||||
url=$3
|
||||
dir=$4
|
||||
bflag=()
|
||||
dflag=()
|
||||
if(! ~ $#branch 0)
|
||||
bflag=(-b $branch)
|
||||
if(! ~ $#debug 0)
|
||||
dflag='-d'
|
||||
{git/fetch $dflag $bflag -u $upstream $url >[2=3] || die $status} | awk '
|
||||
/^remote/{
|
||||
if($2=="HEAD")
|
||||
next
|
||||
ref=$2
|
||||
hash=$3
|
||||
gsub("^refs/heads", "refs/remotes/'$upstream'", ref)
|
||||
outfile = ".git/"ref
|
||||
system("mkdir -p `{basename -d "outfile"}");
|
||||
print hash > outfile;
|
||||
close(outfile);
|
||||
}
|
||||
' |[3] tr '\x0d' '\x0a'
|
||||
}
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='a:allbranch, b:branch branch, d:debug,
|
||||
f:fetchonly, u:upstream upstream, q:quiet'
|
||||
args=''
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
if(~ $#branch 0)
|
||||
branch=refs/`{git/branch}
|
||||
if(~ $allbranch 1)
|
||||
branch=''
|
||||
|
||||
if(~ $#upstream 0)
|
||||
upstream=origin
|
||||
remote=`$nl{git/conf 'remote "'$upstream'".url'}
|
||||
if(~ $#remote 0){
|
||||
remote=$upstream
|
||||
upstream=THEM
|
||||
}
|
||||
|
||||
update $branch $upstream $remote
|
||||
if (~ $fetchonly 1)
|
||||
exit
|
||||
|
||||
local=`{git/branch}
|
||||
remote=`{git/branch | subst '^(refs/)?heads' 'remotes/'$upstream}
|
||||
|
||||
# we have local commits, but the remote hasn't changed.
|
||||
# in this case, we want to keep the local commits untouched.
|
||||
if(~ `{git/query HEAD $remote @} `{git/query $remote}){
|
||||
echo 'up to date' >[1=2]
|
||||
exit
|
||||
}
|
||||
# The remote repository and our HEAD have diverged: we
|
||||
# need to merge.
|
||||
if(! ~ `{git/query HEAD $remote @} `{git/query HEAD}){
|
||||
>[1=2]{
|
||||
echo ours: `{git/query HEAD}
|
||||
echo theirs: `{git/query $remote}
|
||||
echo common: `{git/query HEAD $remote @}
|
||||
echo git/merge $remote
|
||||
}
|
||||
exit diverged
|
||||
}
|
||||
# The remote is directly ahead of the local, and we have
|
||||
# no local commits that need merging.
|
||||
if(~ $#quiet 0)
|
||||
git/log -s -e $local'..'$remote >[1=2]
|
||||
echo
|
||||
echo $remote':' `{git/query $local} '=>' `{git/query $remote} >[1=2]
|
||||
git/branch -mnb $remote $local
|
||||
exit ''
|
51
sys/src/cmd/git/push
Executable file
51
sys/src/cmd/git/push
Executable file
|
@ -0,0 +1,51 @@
|
|||
#!/bin/rc -e
|
||||
rfork en
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='a:pushall, b:branch branch, f:force, d:debug,
|
||||
r:remove remove, u:upstream upstream' args=''
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
if(! ~ $#* 0)
|
||||
exec aux/usage
|
||||
|
||||
if(~ $pushall 1)
|
||||
branch=`$nl{cd .git/refs/heads && walk -f}
|
||||
if(~ $#branch 0)
|
||||
branch=`{git/branch}
|
||||
if(~ $#branch 0)
|
||||
die 'no branches'
|
||||
if(~ $force 1)
|
||||
force=-f
|
||||
if(~ $debug 1)
|
||||
debug='-d'
|
||||
|
||||
if(~ $#upstream 0)
|
||||
upstream=origin
|
||||
|
||||
remotes=`$nl{git/conf -a 'remote "'$upstream'".url'}
|
||||
if(~ $#remotes 0)
|
||||
remotes=$upstream
|
||||
branch=-b^$branch
|
||||
if(! ~ $#remove 0)
|
||||
remove=-r^$remove
|
||||
for(remote in $remotes){
|
||||
updates=`$nl{git/send $debug $force $branch $remove $remote || die $status}
|
||||
for(ln in $updates){
|
||||
u=`{echo $ln}
|
||||
refpath=`{echo $u(2) | subst '^refs/heads/' '.git/refs/remotes/'$upstream'/'}
|
||||
switch($u(1)){
|
||||
case update;
|
||||
mkdir -p `{basename -d $refpath}
|
||||
echo $u(4) > $refpath
|
||||
echo $u(2)^':' $u(3) '=>' $u(4)
|
||||
case delete;
|
||||
echo $u(2)^': removed'
|
||||
rm -f $refpath
|
||||
case uptodate;
|
||||
echo $u(2)^': up to date'
|
||||
}
|
||||
}
|
||||
}
|
||||
exit ''
|
196
sys/src/cmd/git/query.c
Normal file
196
sys/src/cmd/git/query.c
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
#pragma varargck type "P" void
|
||||
|
||||
int fullpath;
|
||||
int changes;
|
||||
int reverse;
|
||||
char *path[128];
|
||||
int npath;
|
||||
|
||||
int
|
||||
Pfmt(Fmt *f)
|
||||
{
|
||||
int i, n;
|
||||
|
||||
n = 0;
|
||||
for(i = 0; i < npath; i++)
|
||||
n += fmtprint(f, "%s/", path[i]);
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
showdir(Hash dh, char *dname, char m)
|
||||
{
|
||||
Dirent *p, *e;
|
||||
Object *d;
|
||||
|
||||
|
||||
path[npath++] = dname;
|
||||
if((d = readobject(dh)) == nil)
|
||||
sysfatal("bad hash %H", dh);
|
||||
assert(d->type == GTree);
|
||||
p = d->tree->ent;
|
||||
e = p + d->tree->nent;
|
||||
for(; p != e; p++){
|
||||
if(p->ismod)
|
||||
continue;
|
||||
if(p->mode & DMDIR)
|
||||
showdir(p->h, p->name, m);
|
||||
else
|
||||
print("%c %P%s\n", m, p->name);
|
||||
}
|
||||
print("%c %P\n", m);
|
||||
unref(d);
|
||||
npath--;
|
||||
}
|
||||
|
||||
void
|
||||
show(Dirent *e, char m)
|
||||
{
|
||||
if(e->mode & DMDIR)
|
||||
showdir(e->h, e->name, m);
|
||||
else
|
||||
print("%c %P%s\n", m, e->name);
|
||||
}
|
||||
|
||||
void
|
||||
difftrees(Object *a, Object *b)
|
||||
{
|
||||
Dirent *ap, *bp, *ae, *be;
|
||||
int c;
|
||||
|
||||
ap = ae = nil;
|
||||
bp = be = nil;
|
||||
if(a != nil){
|
||||
if(a->type != GTree)
|
||||
return;
|
||||
ap = a->tree->ent;
|
||||
ae = ap + a->tree->nent;
|
||||
}
|
||||
if(b != nil){
|
||||
if(b->type != GTree)
|
||||
return;
|
||||
bp = b->tree->ent;
|
||||
be = bp + b->tree->nent;
|
||||
}
|
||||
while(ap != ae && bp != be){
|
||||
c = strcmp(ap->name, bp->name);
|
||||
if(c == 0){
|
||||
if(ap->mode == bp->mode && hasheq(&ap->h, &bp->h))
|
||||
goto next;
|
||||
if(ap->mode != bp->mode)
|
||||
print("! %P%s\n", ap->name);
|
||||
else if(!(ap->mode & DMDIR) || !(bp->mode & DMDIR))
|
||||
print("@ %P%s\n", ap->name);
|
||||
if((ap->mode & DMDIR) && (bp->mode & DMDIR)){
|
||||
if(npath >= nelem(path))
|
||||
sysfatal("path too deep");
|
||||
path[npath++] = ap->name;
|
||||
if((a = readobject(ap->h)) == nil)
|
||||
sysfatal("bad hash %H", ap->h);
|
||||
if((b = readobject(bp->h)) == nil)
|
||||
sysfatal("bad hash %H", bp->h);
|
||||
difftrees(a, b);
|
||||
unref(a);
|
||||
unref(b);
|
||||
npath--;
|
||||
}
|
||||
next:
|
||||
ap++;
|
||||
bp++;
|
||||
}else if(c < 0) {
|
||||
show(ap, '-');
|
||||
ap++;
|
||||
}else if(c > 0){
|
||||
show(bp, '+');
|
||||
bp++;
|
||||
}
|
||||
}
|
||||
for(; ap != ae; ap++)
|
||||
show(ap, '-');
|
||||
for(; bp != be; bp++)
|
||||
show(bp, '+');
|
||||
}
|
||||
|
||||
void
|
||||
diffcommits(Hash ah, Hash bh)
|
||||
{
|
||||
Object *a, *b, *at, *bt;
|
||||
|
||||
at = nil;
|
||||
bt = nil;
|
||||
if(!hasheq(&ah, &Zhash) && (a = readobject(ah)) != nil){
|
||||
if(a->type != GCommit)
|
||||
sysfatal("not commit: %H", ah);
|
||||
if((at = readobject(a->commit->tree)) == nil)
|
||||
sysfatal("bad hash %H", a->commit->tree);
|
||||
unref(a);
|
||||
}
|
||||
if(!hasheq(&bh, &Zhash) && (b = readobject(bh)) != nil){
|
||||
if(b->type != GCommit)
|
||||
sysfatal("not commit: %H", ah);
|
||||
if((bt = readobject(b->commit->tree)) == nil)
|
||||
sysfatal("bad hash %H", b->commit->tree);
|
||||
unref(b);
|
||||
}
|
||||
difftrees(at, bt);
|
||||
unref(at);
|
||||
unref(bt);
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-pcr] query\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i, j, n;
|
||||
Hash *h;
|
||||
char *p, *e, *s;
|
||||
char query[2048], repo[512];
|
||||
|
||||
ARGBEGIN{
|
||||
case 'p': fullpath++; break;
|
||||
case 'c': changes++; break;
|
||||
case 'r': reverse ^= 1; break;
|
||||
default: usage(); break;
|
||||
}ARGEND;
|
||||
|
||||
gitinit();
|
||||
fmtinstall('P', Pfmt);
|
||||
|
||||
if(argc == 0)
|
||||
usage();
|
||||
if(findrepo(repo, sizeof(repo)) == -1)
|
||||
sysfatal("find root: %r");
|
||||
if(chdir(repo) == -1)
|
||||
sysfatal("chdir: %r");
|
||||
s = "";
|
||||
p = query;
|
||||
e = query + nelem(query);
|
||||
for(i = 0; i < argc; i++){
|
||||
p = seprint(p, e, "%s%s", s, argv[i]);
|
||||
s = " ";
|
||||
}
|
||||
if((n = resolverefs(&h, query)) == -1)
|
||||
sysfatal("resolve: %r");
|
||||
if(changes){
|
||||
if(n != 2)
|
||||
sysfatal("diff: need 2 commits, got %d", n);
|
||||
diffcommits(h[0], h[1]);
|
||||
}else{
|
||||
p = (fullpath ? "/mnt/git/object/" : "");
|
||||
for(j = 0; j < n; j++)
|
||||
print("%s%H\n", p, h[reverse ? n - 1 - j : j]);
|
||||
}
|
||||
exits(nil);
|
||||
}
|
||||
|
92
sys/src/cmd/git/rebase
Executable file
92
sys/src/cmd/git/rebase
Executable file
|
@ -0,0 +1,92 @@
|
|||
#!/bin/rc
|
||||
|
||||
. /sys/lib/git/common.rc
|
||||
gitup
|
||||
|
||||
flagfmt='a:abort, r:resume, i:interactive'; args='onto'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
tmp=_rebase.working
|
||||
if(! git/walk -q)
|
||||
die dirty working tree
|
||||
if(~ $#abort 1){
|
||||
if(! test -f .git/rebase.todo)
|
||||
die no rebase to abort
|
||||
src=`{cat .git/rebase.src}
|
||||
rm -f .git/rebase.^(src todo)
|
||||
git/branch $src
|
||||
git/branch -d $tmp
|
||||
exit
|
||||
}
|
||||
if(test -f .git/rebase.todo){
|
||||
if(~ $#resume 0)
|
||||
die rebase in progress
|
||||
if(! ~ $#* 0)
|
||||
exec aux/usage
|
||||
src=`{cat .git/rebase.src}
|
||||
}
|
||||
if not{
|
||||
if(! ~ $#* 1)
|
||||
exec aux/usage
|
||||
src=`{git/branch}
|
||||
dst=`{git/query $1}
|
||||
echo $src > .git/rebase.src
|
||||
git/log -se $dst' '$src' @ .. '$src | sed 's/^/pick /' >.git/rebase.todo
|
||||
if(! ~ $#interactive 0){
|
||||
giteditor=`{git/conf core.editor}
|
||||
if(~ $#editor 0)
|
||||
editor=$giteditor
|
||||
if(~ $#editor 0)
|
||||
editor=hold
|
||||
$editor .git/rebase.todo
|
||||
}
|
||||
git/branch -nb $dst $tmp
|
||||
}
|
||||
todo=`$nl{cat .git/rebase.todo}
|
||||
|
||||
fn sigexit {
|
||||
s=$status
|
||||
if(!)
|
||||
echo 'fix and git/rebase -r'
|
||||
>.git/rebase.todo for(i in $todo)
|
||||
echo $i
|
||||
status=$s
|
||||
}
|
||||
|
||||
flag e +
|
||||
|
||||
while(! ~ $#todo 0){
|
||||
item=`{echo $todo(1)}
|
||||
todo=$todo(2-)
|
||||
echo $item
|
||||
c=$item(2)
|
||||
switch($item(1)){
|
||||
case p pick
|
||||
git/export $c | git/import
|
||||
case r reword
|
||||
git/export $c | git/import
|
||||
git/commit -re
|
||||
case e edit
|
||||
git/export $c | git/import
|
||||
echo 'stopped for edit, resume with git/rebase -r'
|
||||
exit
|
||||
case s squash
|
||||
git/export $c | git/import -n
|
||||
msg=`''{cat /mnt/git/HEAD/msg; echo; cat /mnt/git/object/$c/msg}
|
||||
git/commit -rem $msg .
|
||||
case f fixup
|
||||
git/export $c | git/import -n
|
||||
git/commit -r .
|
||||
case b break
|
||||
echo 'stopped, resume with git/rebase -r'
|
||||
exit
|
||||
case '#'* ''
|
||||
case *
|
||||
die 'unknown command '''^$item(1)^''''
|
||||
}
|
||||
}
|
||||
|
||||
fn sigexit
|
||||
git/branch -nb $tmp $src
|
||||
git/branch -d $tmp
|
||||
rm .git/rebase.todo .git/rebase.src
|
677
sys/src/cmd/git/ref.c
Normal file
677
sys/src/cmd/git/ref.c
Normal file
|
@ -0,0 +1,677 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
typedef struct Eval Eval;
|
||||
typedef struct XObject XObject;
|
||||
typedef struct Objq Objq;
|
||||
|
||||
enum {
|
||||
Blank,
|
||||
Keep,
|
||||
Drop,
|
||||
};
|
||||
|
||||
struct Eval {
|
||||
char *str;
|
||||
char *p;
|
||||
Object **stk;
|
||||
int nstk;
|
||||
int stksz;
|
||||
};
|
||||
|
||||
struct XObject {
|
||||
Object *obj;
|
||||
Object *mark;
|
||||
XObject *queue;
|
||||
XObject *next;
|
||||
};
|
||||
|
||||
struct Objq {
|
||||
Objq *next;
|
||||
Object *o;
|
||||
int color;
|
||||
};
|
||||
|
||||
static Object zcommit = {
|
||||
.type=GCommit
|
||||
};
|
||||
|
||||
void
|
||||
eatspace(Eval *ev)
|
||||
{
|
||||
while(isspace(ev->p[0]))
|
||||
ev->p++;
|
||||
}
|
||||
|
||||
int
|
||||
objdatecmp(void *pa, void *pb)
|
||||
{
|
||||
Object *a, *b;
|
||||
int r;
|
||||
|
||||
a = readobject((*(Object**)pa)->hash);
|
||||
b = readobject((*(Object**)pb)->hash);
|
||||
assert(a->type == GCommit && b->type == GCommit);
|
||||
if(a->commit->mtime == b->commit->mtime)
|
||||
r = 0;
|
||||
else if(a->commit->mtime < b->commit->mtime)
|
||||
r = -1;
|
||||
else
|
||||
r = 1;
|
||||
unref(a);
|
||||
unref(b);
|
||||
return r;
|
||||
}
|
||||
|
||||
void
|
||||
push(Eval *ev, Object *o)
|
||||
{
|
||||
if(ev->nstk == ev->stksz){
|
||||
ev->stksz = 2*ev->stksz + 1;
|
||||
ev->stk = erealloc(ev->stk, ev->stksz*sizeof(Object*));
|
||||
}
|
||||
ev->stk[ev->nstk++] = o;
|
||||
}
|
||||
|
||||
Object*
|
||||
pop(Eval *ev)
|
||||
{
|
||||
if(ev->nstk == 0)
|
||||
sysfatal("stack underflow");
|
||||
return ev->stk[--ev->nstk];
|
||||
}
|
||||
|
||||
Object*
|
||||
peek(Eval *ev)
|
||||
{
|
||||
if(ev->nstk == 0)
|
||||
sysfatal("stack underflow");
|
||||
return ev->stk[ev->nstk - 1];
|
||||
}
|
||||
|
||||
int
|
||||
isword(char e)
|
||||
{
|
||||
return isalnum(e) || e == '/' || e == '-' || e == '_' || e == '.';
|
||||
}
|
||||
|
||||
int
|
||||
word(Eval *ev, char *b, int nb)
|
||||
{
|
||||
char *p, *e;
|
||||
int n;
|
||||
|
||||
p = ev->p;
|
||||
for(e = p; isword(*e) && strncmp(e, "..", 2) != 0; e++)
|
||||
/* nothing */;
|
||||
/* 1 for nul terminator */
|
||||
n = e - p + 1;
|
||||
if(n >= nb)
|
||||
n = nb;
|
||||
snprint(b, n, "%s", p);
|
||||
ev->p = e;
|
||||
return n > 0;
|
||||
}
|
||||
|
||||
int
|
||||
take(Eval *ev, char *m)
|
||||
{
|
||||
int l;
|
||||
|
||||
l = strlen(m);
|
||||
if(strncmp(ev->p, m, l) != 0)
|
||||
return 0;
|
||||
ev->p += l;
|
||||
return 1;
|
||||
}
|
||||
|
||||
XObject*
|
||||
hnode(XObject *ht[], Object *o)
|
||||
{
|
||||
XObject *h;
|
||||
int hh;
|
||||
|
||||
hh = o->hash.h[0] & 0xff;
|
||||
for(h = ht[hh]; h; h = h->next)
|
||||
if(hasheq(&o->hash, &h->obj->hash))
|
||||
return h;
|
||||
|
||||
h = emalloc(sizeof(*h));
|
||||
h->obj = o;
|
||||
h->mark = nil;
|
||||
h->queue = nil;
|
||||
h->next = ht[hh];
|
||||
ht[hh] = h;
|
||||
return h;
|
||||
}
|
||||
|
||||
Object*
|
||||
ancestor(Object *a, Object *b)
|
||||
{
|
||||
Object *o, *p, *r;
|
||||
XObject *ht[256];
|
||||
XObject *h, *q, *q1, *q2;
|
||||
int i;
|
||||
|
||||
if(a == b)
|
||||
return a;
|
||||
if(a == nil || b == nil)
|
||||
return nil;
|
||||
r = nil;
|
||||
memset(ht, 0, sizeof(ht));
|
||||
q1 = nil;
|
||||
|
||||
h = hnode(ht, a);
|
||||
h->mark = a;
|
||||
h->queue = q1;
|
||||
q1 = h;
|
||||
|
||||
h = hnode(ht, b);
|
||||
h->mark = b;
|
||||
h->queue = q1;
|
||||
q1 = h;
|
||||
|
||||
while(1){
|
||||
q2 = nil;
|
||||
while(q = q1){
|
||||
q1 = q->queue;
|
||||
q->queue = nil;
|
||||
o = q->obj;
|
||||
for(i = 0; i < o->commit->nparent; i++){
|
||||
p = readobject(o->commit->parent[i]);
|
||||
if(p == nil)
|
||||
goto err;
|
||||
h = hnode(ht, p);
|
||||
if(h->mark != nil){
|
||||
if(h->mark != q->mark){
|
||||
r = h->obj;
|
||||
goto done;
|
||||
}
|
||||
} else {
|
||||
h->mark = q->mark;
|
||||
h->queue = q2;
|
||||
q2 = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(q2 == nil){
|
||||
err:
|
||||
werrstr("no common ancestor");
|
||||
break;
|
||||
}
|
||||
q1 = q2;
|
||||
}
|
||||
done:
|
||||
for(i=0; i<nelem(ht); i++){
|
||||
while(h = ht[i]){
|
||||
ht[i] = h->next;
|
||||
free(h);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
lca(Eval *ev)
|
||||
{
|
||||
Object *a, *b, *o;
|
||||
|
||||
if(ev->nstk < 2){
|
||||
werrstr("ancestor needs 2 objects");
|
||||
return -1;
|
||||
}
|
||||
a = pop(ev);
|
||||
b = pop(ev);
|
||||
o = ancestor(a, b);
|
||||
if(o == nil)
|
||||
return -1;
|
||||
push(ev, o);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
repaint(Objset *keep, Objset *drop, Object *o)
|
||||
{
|
||||
Object *p;
|
||||
int i;
|
||||
|
||||
if(!oshas(keep, o->hash) && !oshas(drop, o->hash)){
|
||||
dprint(2, "repaint: blank => drop %H\n", o->hash);
|
||||
osadd(drop, o);
|
||||
return 0;
|
||||
}
|
||||
if(oshas(keep, o->hash))
|
||||
dprint(2, "repaint: keep => drop %H\n", o->hash);
|
||||
osadd(drop, o);
|
||||
for(i = 0; i < o->commit->nparent; i++){
|
||||
if((p = readobject(o->commit->parent[i])) == nil)
|
||||
return -1;
|
||||
if(repaint(keep, drop, p) == -1)
|
||||
return -1;
|
||||
unref(p);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
findtwixt(Hash *head, int nhead, Hash *tail, int ntail, Object ***res, int *nres)
|
||||
{
|
||||
Objq *q, *e, *n, **p;
|
||||
Objset keep, drop;
|
||||
Object *o, *c;
|
||||
int i, ncolor;
|
||||
|
||||
e = nil;
|
||||
q = nil;
|
||||
p = &q;
|
||||
osinit(&keep);
|
||||
osinit(&drop);
|
||||
for(i = 0; i < nhead; i++){
|
||||
if(hasheq(&head[i], &Zhash))
|
||||
continue;
|
||||
if((o = readobject(head[i])) == nil){
|
||||
fprint(2, "warning: %H does not point at commit\n", o->hash);
|
||||
werrstr("read head %H: %r", head[i]);
|
||||
return -1;
|
||||
}
|
||||
if(o->type != GCommit){
|
||||
fprint(2, "warning: %H does not point at commit\n", o->hash);
|
||||
unref(o);
|
||||
continue;
|
||||
}
|
||||
dprint(1, "twixt init: keep %H\n", o->hash);
|
||||
e = emalloc(sizeof(Objq));
|
||||
e->o = o;
|
||||
e->color = Keep;
|
||||
*p = e;
|
||||
p = &e->next;
|
||||
unref(o);
|
||||
}
|
||||
for(i = 0; i < ntail; i++){
|
||||
if(hasheq(&tail[i], &Zhash))
|
||||
continue;
|
||||
if((o = readobject(tail[i])) == nil){
|
||||
fprint(2, "warning: %H does not point at commit\n", o->hash);
|
||||
werrstr("read tail %H: %r", tail[i]);
|
||||
return -1;
|
||||
}
|
||||
if(o->type != GCommit){
|
||||
unref(o);
|
||||
continue;
|
||||
}
|
||||
dprint(1, "init: drop %H\n", o->hash);
|
||||
e = emalloc(sizeof(Objq));
|
||||
e->o = o;
|
||||
e->color = Drop;
|
||||
*p = e;
|
||||
p = &e->next;
|
||||
unref(o);
|
||||
}
|
||||
|
||||
dprint(1, "finding twixt commits\n");
|
||||
while(q != nil){
|
||||
if(oshas(&drop, q->o->hash))
|
||||
ncolor = Drop;
|
||||
else if(oshas(&keep, q->o->hash))
|
||||
ncolor = Keep;
|
||||
else
|
||||
ncolor = Blank;
|
||||
if(ncolor == Drop || ncolor == Keep && q->color == Keep)
|
||||
goto next;
|
||||
if(ncolor == Keep && q->color == Drop){
|
||||
if(repaint(&keep, &drop, q->o) == -1)
|
||||
goto error;
|
||||
}else if (ncolor == Blank) {
|
||||
dprint(2, "visit: %s %H\n", q->color == Keep ? "keep" : "drop", q->o->hash);
|
||||
if(q->color == Keep)
|
||||
osadd(&keep, q->o);
|
||||
else
|
||||
osadd(&drop, q->o);
|
||||
for(i = 0; i < q->o->commit->nparent; i++){
|
||||
if((c = readobject(q->o->commit->parent[i])) == nil)
|
||||
goto error;
|
||||
if(c->type != GCommit){
|
||||
fprint(2, "warning: %H does not point at commit\n", c->hash);
|
||||
unref(c);
|
||||
continue;
|
||||
}
|
||||
dprint(2, "enqueue: %s %H\n", q->color == Keep ? "keep" : "drop", c->hash);
|
||||
n = emalloc(sizeof(Objq));
|
||||
n->color = q->color;
|
||||
n->next = nil;
|
||||
n->o = c;
|
||||
e->next = n;
|
||||
e = n;
|
||||
unref(c);
|
||||
}
|
||||
}else{
|
||||
sysfatal("oops");
|
||||
}
|
||||
next:
|
||||
n = q->next;
|
||||
free(q);
|
||||
q = n;
|
||||
}
|
||||
*res = eamalloc(keep.nobj, sizeof(Object*));
|
||||
*nres = 0;
|
||||
for(i = 0; i < keep.sz; i++){
|
||||
if(keep.obj[i] != nil && !oshas(&drop, keep.obj[i]->hash)){
|
||||
(*res)[*nres] = keep.obj[i];
|
||||
(*nres)++;
|
||||
}
|
||||
}
|
||||
osclear(&keep);
|
||||
osclear(&drop);
|
||||
return 0;
|
||||
error:
|
||||
for(; q != nil; q = n) {
|
||||
n = q->next;
|
||||
free(q);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
parent(Eval *ev)
|
||||
{
|
||||
Object *o, *p;
|
||||
|
||||
o = pop(ev);
|
||||
/* Special case: first commit has no parent. */
|
||||
if(o->commit->nparent == 0)
|
||||
p = emptydir();
|
||||
else if ((p = readobject(o->commit->parent[0])) == nil){
|
||||
werrstr("no parent for %H", o->hash);
|
||||
return -1;
|
||||
}
|
||||
|
||||
push(ev, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
unwind(Eval *ev, Object **obj, int *idx, int nobj, Object **p, Objset *set, int keep)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = nobj; i >= 0; i--){
|
||||
idx[i]++;
|
||||
if(keep && !oshas(set, obj[i]->hash)){
|
||||
push(ev, obj[i]);
|
||||
osadd(set, obj[i]);
|
||||
}else{
|
||||
osadd(set, obj[i]);
|
||||
}
|
||||
if(idx[i] < obj[i]->commit->nparent){
|
||||
*p = obj[i];
|
||||
return i;
|
||||
}
|
||||
unref(obj[i]);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
range(Eval *ev)
|
||||
{
|
||||
Object *a, *b, *p, *q, **all;
|
||||
int nall, *idx, mark;
|
||||
Objset keep, skip;
|
||||
|
||||
b = pop(ev);
|
||||
a = pop(ev);
|
||||
if(hasheq(&b->hash, &Zhash))
|
||||
b = &zcommit;
|
||||
if(hasheq(&a->hash, &Zhash))
|
||||
a = &zcommit;
|
||||
if(a->type != GCommit || b->type != GCommit){
|
||||
werrstr("non-commit object in range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
p = b;
|
||||
all = nil;
|
||||
idx = nil;
|
||||
nall = 0;
|
||||
mark = ev->nstk;
|
||||
osinit(&keep);
|
||||
osinit(&skip);
|
||||
osadd(&keep, a);
|
||||
while(1){
|
||||
all = earealloc(all, (nall + 1), sizeof(Object*));
|
||||
idx = earealloc(idx, (nall + 1), sizeof(int));
|
||||
all[nall] = p;
|
||||
idx[nall] = 0;
|
||||
if(p == a || p->commit->nparent == 0 && a == &zcommit){
|
||||
if((nall = unwind(ev, all, idx, nall, &p, &keep, 1)) == -1)
|
||||
break;
|
||||
}else if(p->commit->nparent == 0){
|
||||
if((nall = unwind(ev, all, idx, nall, &p, &skip, 0)) == -1)
|
||||
break;
|
||||
}else if(oshas(&keep, p->hash)){
|
||||
if((nall = unwind(ev, all, idx, nall, &p, &keep, 1)) == -1)
|
||||
break;
|
||||
}else if(oshas(&skip, p->hash))
|
||||
if((nall = unwind(ev, all, idx, nall, &p, &skip, 0)) == -1)
|
||||
break;
|
||||
if(p->commit->nparent == 0)
|
||||
break;
|
||||
if((q = readobject(p->commit->parent[idx[nall]])) == nil){
|
||||
werrstr("bad commit %H", p->commit->parent[idx[nall]]);
|
||||
goto error;
|
||||
}
|
||||
if(q->type != GCommit){
|
||||
werrstr("not commit: %H", q->hash);
|
||||
goto error;
|
||||
}
|
||||
p = q;
|
||||
nall++;
|
||||
}
|
||||
free(all);
|
||||
qsort(ev->stk + mark, ev->nstk - mark, sizeof(Object*), objdatecmp);
|
||||
return 0;
|
||||
error:
|
||||
free(all);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
readref(Hash *h, char *ref)
|
||||
{
|
||||
static char *try[] = {"", "refs/", "refs/heads/", "refs/remotes/", "refs/tags/", nil};
|
||||
char buf[256], s[256], **pfx;
|
||||
int r, f, n;
|
||||
|
||||
/* TODO: support hash prefixes */
|
||||
if((r = hparse(h, ref)) != -1)
|
||||
return r;
|
||||
if(strcmp(ref, "HEAD") == 0){
|
||||
snprint(buf, sizeof(buf), ".git/HEAD");
|
||||
if((f = open(buf, OREAD)) == -1)
|
||||
return -1;
|
||||
if((n = readn(f, s, sizeof(s) - 1))== -1)
|
||||
return -1;
|
||||
s[n] = 0;
|
||||
strip(s);
|
||||
r = hparse(h, s);
|
||||
goto found;
|
||||
}
|
||||
for(pfx = try; *pfx; pfx++){
|
||||
snprint(buf, sizeof(buf), ".git/%s%s", *pfx, ref);
|
||||
if((f = open(buf, OREAD)) == -1)
|
||||
continue;
|
||||
if((n = readn(f, s, sizeof(s) - 1)) == -1)
|
||||
continue;
|
||||
s[n] = 0;
|
||||
strip(s);
|
||||
r = hparse(h, s);
|
||||
close(f);
|
||||
goto found;
|
||||
}
|
||||
return -1;
|
||||
|
||||
found:
|
||||
if(r == -1 && strstr(s, "ref: ") == s)
|
||||
r = readref(h, s + strlen("ref: "));
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
evalpostfix(Eval *ev)
|
||||
{
|
||||
char name[256];
|
||||
Object *o;
|
||||
Hash h;
|
||||
|
||||
eatspace(ev);
|
||||
if(!word(ev, name, sizeof(name))){
|
||||
werrstr("expected name in expression");
|
||||
return -1;
|
||||
}
|
||||
if(readref(&h, name) == -1){
|
||||
werrstr("invalid ref %s", name);
|
||||
return -1;
|
||||
}
|
||||
if(hasheq(&h, &Zhash))
|
||||
o = &zcommit;
|
||||
else if((o = readobject(h)) == nil){
|
||||
werrstr("invalid ref %s (hash %H)", name, h);
|
||||
return -1;
|
||||
}
|
||||
push(ev, o);
|
||||
|
||||
while(1){
|
||||
eatspace(ev);
|
||||
switch(ev->p[0]){
|
||||
case '^':
|
||||
case '~':
|
||||
ev->p++;
|
||||
if(parent(ev) == -1)
|
||||
return -1;
|
||||
break;
|
||||
case '@':
|
||||
ev->p++;
|
||||
if(lca(ev) == -1)
|
||||
return -1;
|
||||
break;
|
||||
default:
|
||||
goto done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
done:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
evalexpr(Eval *ev, char *ref)
|
||||
{
|
||||
memset(ev, 0, sizeof(*ev));
|
||||
ev->str = ref;
|
||||
ev->p = ref;
|
||||
|
||||
while(1){
|
||||
if(evalpostfix(ev) == -1)
|
||||
return -1;
|
||||
if(ev->p[0] == '\0')
|
||||
return 0;
|
||||
else if(take(ev, ":") || take(ev, "..")){
|
||||
if(evalpostfix(ev) == -1)
|
||||
return -1;
|
||||
if(ev->p[0] != '\0'){
|
||||
werrstr("junk at end of expression");
|
||||
return -1;
|
||||
}
|
||||
return range(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
resolverefs(Hash **r, char *ref)
|
||||
{
|
||||
Eval ev;
|
||||
Hash *h;
|
||||
int i;
|
||||
|
||||
if(evalexpr(&ev, ref) == -1){
|
||||
free(ev.stk);
|
||||
return -1;
|
||||
}
|
||||
h = eamalloc(ev.nstk, sizeof(Hash));
|
||||
for(i = 0; i < ev.nstk; i++)
|
||||
h[i] = ev.stk[i]->hash;
|
||||
*r = h;
|
||||
free(ev.stk);
|
||||
return ev.nstk;
|
||||
}
|
||||
|
||||
int
|
||||
resolveref(Hash *r, char *ref)
|
||||
{
|
||||
Eval ev;
|
||||
|
||||
if(evalexpr(&ev, ref) == -1){
|
||||
free(ev.stk);
|
||||
return -1;
|
||||
}
|
||||
if(ev.nstk != 1){
|
||||
werrstr("ambiguous ref expr");
|
||||
free(ev.stk);
|
||||
return -1;
|
||||
}
|
||||
*r = ev.stk[0]->hash;
|
||||
free(ev.stk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
readrefdir(Hash **refs, char ***names, int *nrefs, char *dpath, char *dname)
|
||||
{
|
||||
Dir *d, *e, *dir;
|
||||
char *path, *name, *sep;
|
||||
int ndir;
|
||||
|
||||
if((ndir = slurpdir(dpath, &dir)) == -1)
|
||||
return -1;
|
||||
sep = (*dname == '\0') ? "" : "/";
|
||||
e = dir + ndir;
|
||||
for(d = dir; d != e; d++){
|
||||
path = smprint("%s/%s", dpath, d->name);
|
||||
name = smprint("%s%s%s", dname, sep, d->name);
|
||||
if(d->mode & DMDIR) {
|
||||
if(readrefdir(refs, names, nrefs, path, name) == -1)
|
||||
goto noref;
|
||||
}else{
|
||||
*refs = erealloc(*refs, (*nrefs + 1)*sizeof(Hash));
|
||||
*names = erealloc(*names, (*nrefs + 1)*sizeof(char*));
|
||||
if(resolveref(&(*refs)[*nrefs], name) == -1)
|
||||
goto noref;
|
||||
(*names)[*nrefs] = name;
|
||||
*nrefs += 1;
|
||||
goto next;
|
||||
}
|
||||
noref: free(name);
|
||||
next: free(path);
|
||||
}
|
||||
free(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
listrefs(Hash **refs, char ***names)
|
||||
{
|
||||
int nrefs;
|
||||
|
||||
*refs = nil;
|
||||
*names = nil;
|
||||
nrefs = 0;
|
||||
if(readrefdir(refs, names, &nrefs, ".git/refs", "") == -1){
|
||||
free(*refs);
|
||||
return -1;
|
||||
}
|
||||
return nrefs;
|
||||
}
|
85
sys/src/cmd/git/repack.c
Normal file
85
sys/src/cmd/git/repack.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
#define TMPPATH(suff) (".git/objects/pack/repack."suff)
|
||||
|
||||
int
|
||||
cleanup(Hash h)
|
||||
{
|
||||
char newpfx[42], dpath[256], fpath[256];
|
||||
int i, j, nd;
|
||||
Dir *d;
|
||||
|
||||
snprint(newpfx, sizeof(newpfx), "%H.", h);
|
||||
for(i = 0; i < 256; i++){
|
||||
snprint(dpath, sizeof(dpath), ".git/objects/%02x", i);
|
||||
if((nd = slurpdir(dpath, &d)) == -1)
|
||||
continue;
|
||||
for(j = 0; j < nd; j++){
|
||||
snprint(fpath, sizeof(fpath), ".git/objects/%02x/%s", i, d[j].name);
|
||||
remove(fpath);
|
||||
}
|
||||
remove(dpath);
|
||||
free(d);
|
||||
}
|
||||
snprint(dpath, sizeof(dpath), ".git/objects/pack");
|
||||
if((nd = slurpdir(dpath, &d)) == -1)
|
||||
return -1;
|
||||
for(i = 0; i < nd; i++){
|
||||
if(strncmp(d[i].name, newpfx, strlen(newpfx)) == 0)
|
||||
continue;
|
||||
snprint(fpath, sizeof(fpath), ".git/objects/pack/%s", d[i].name);
|
||||
remove(fpath);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-d]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char path[128], **names;
|
||||
int fd, nrefs;
|
||||
Hash *refs, h;
|
||||
Dir rn;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'd':
|
||||
chattygit++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}ARGEND;
|
||||
|
||||
gitinit();
|
||||
refs = nil;
|
||||
if((nrefs = listrefs(&refs, &names)) == -1)
|
||||
sysfatal("load refs: %r");
|
||||
if((fd = create(TMPPATH("pack.tmp"), OWRITE, 0644)) == -1)
|
||||
sysfatal("open %s: %r", TMPPATH("pack.tmp"));
|
||||
if(writepack(fd, refs, nrefs, nil, 0, &h) == -1)
|
||||
sysfatal("writepack: %r");
|
||||
if(indexpack(TMPPATH("pack.tmp"), TMPPATH("idx.tmp"), h) == -1)
|
||||
sysfatal("indexpack: %r");
|
||||
close(fd);
|
||||
|
||||
nulldir(&rn);
|
||||
rn.name = path;
|
||||
snprint(path, sizeof(path), "%H.pack", h);
|
||||
if(dirwstat(TMPPATH("pack.tmp"), &rn) == -1)
|
||||
sysfatal("rename pack: %r");
|
||||
snprint(path, sizeof(path), "%H.idx", h);
|
||||
if(dirwstat(TMPPATH("idx.tmp"), &rn) == -1)
|
||||
sysfatal("rename pack: %r");
|
||||
if(cleanup(h) == -1)
|
||||
sysfatal("cleanup: %r");
|
||||
exits(nil);
|
||||
}
|
19
sys/src/cmd/git/revert
Normal file
19
sys/src/cmd/git/revert
Normal file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/rc
|
||||
rfork e
|
||||
. /sys/lib/git/common.rc
|
||||
|
||||
gitup
|
||||
|
||||
flagfmt='c:query query' args='file ...'
|
||||
eval `''{aux/getflags $*} || exec aux/usage
|
||||
|
||||
commit=/mnt/git/HEAD
|
||||
if(~ $#query 1)
|
||||
commit=`{git/query -p $query}
|
||||
|
||||
for(f in `$nl{cd $commit/tree/ && walk -f ./$gitrel/$*}){
|
||||
mkdir -p `{basename -d $f}
|
||||
cp -- $commit/tree/$f $f
|
||||
git/add $f
|
||||
}
|
||||
exit ''
|
3
sys/src/cmd/git/rm
Executable file
3
sys/src/cmd/git/rm
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/rc -e
|
||||
|
||||
exec git/add -r $*
|
401
sys/src/cmd/git/save.c
Normal file
401
sys/src/cmd/git/save.c
Normal file
|
@ -0,0 +1,401 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include "git.h"
|
||||
|
||||
typedef struct Objbuf Objbuf;
|
||||
struct Objbuf {
|
||||
int off;
|
||||
char *hdr;
|
||||
int nhdr;
|
||||
char *dat;
|
||||
int ndat;
|
||||
};
|
||||
enum {
|
||||
Maxparents = 16,
|
||||
};
|
||||
|
||||
int
|
||||
gitmode(int m)
|
||||
{
|
||||
if(m & DMDIR) /* directory */
|
||||
return 0040000;
|
||||
else if(m & 0111) /* executable */
|
||||
return 0100755;
|
||||
else if(m != 0) /* regular */
|
||||
return 0100644;
|
||||
else /* symlink */
|
||||
return 0120000;
|
||||
}
|
||||
|
||||
int
|
||||
entcmp(void *pa, void *pb)
|
||||
{
|
||||
char abuf[256], bbuf[256], *ae, *be;
|
||||
Dirent *a, *b;
|
||||
|
||||
a = pa;
|
||||
b = pb;
|
||||
/*
|
||||
* If the files have the same name, they're equal.
|
||||
* Otherwise, If they're trees, they sort as thoug
|
||||
* there was a trailing slash.
|
||||
*
|
||||
* Wat.
|
||||
*/
|
||||
if(strcmp(a->name, b->name) == 0)
|
||||
return 0;
|
||||
|
||||
ae = seprint(abuf, abuf + sizeof(abuf) - 1, a->name);
|
||||
be = seprint(bbuf, bbuf + sizeof(bbuf) - 1, b->name);
|
||||
if(a->mode & DMDIR)
|
||||
*ae = '/';
|
||||
if(b->mode & DMDIR)
|
||||
*be = '/';
|
||||
return strcmp(abuf, bbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
bwrite(void *p, void *buf, int nbuf)
|
||||
{
|
||||
return Bwrite(p, buf, nbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
objbytes(void *p, void *buf, int nbuf)
|
||||
{
|
||||
Objbuf *b;
|
||||
int r, n, o;
|
||||
char *s;
|
||||
|
||||
b = p;
|
||||
n = 0;
|
||||
if(b->off < b->nhdr){
|
||||
r = b->nhdr - b->off;
|
||||
r = (nbuf < r) ? nbuf : r;
|
||||
memcpy(buf, b->hdr, r);
|
||||
b->off += r;
|
||||
nbuf -= r;
|
||||
n += r;
|
||||
}
|
||||
if(b->off < b->ndat + b->nhdr){
|
||||
s = buf;
|
||||
o = b->off - b->nhdr;
|
||||
r = b->ndat - o;
|
||||
r = (nbuf < r) ? nbuf : r;
|
||||
memcpy(s + n, b->dat + o, r);
|
||||
b->off += r;
|
||||
n += r;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
writeobj(Hash *h, char *hdr, int nhdr, char *dat, int ndat)
|
||||
{
|
||||
Objbuf b = {.off=0, .hdr=hdr, .nhdr=nhdr, .dat=dat, .ndat=ndat};
|
||||
char s[64], o[256];
|
||||
SHA1state *st;
|
||||
Biobuf *f;
|
||||
int fd;
|
||||
|
||||
st = sha1((uchar*)hdr, nhdr, nil, nil);
|
||||
st = sha1((uchar*)dat, ndat, nil, st);
|
||||
sha1(nil, 0, h->h, st);
|
||||
|
||||
snprint(s, sizeof(s), "%H", *h);
|
||||
fd = create(".git/objects", OREAD, DMDIR|0755);
|
||||
close(fd);
|
||||
snprint(o, sizeof(o), ".git/objects/%c%c", s[0], s[1]);
|
||||
fd = create(o, OREAD, DMDIR | 0755);
|
||||
close(fd);
|
||||
snprint(o, sizeof(o), ".git/objects/%c%c/%s", s[0], s[1], s + 2);
|
||||
if(readobject(*h) == nil){
|
||||
if((f = Bopen(o, OWRITE)) == nil)
|
||||
sysfatal("could not open %s: %r", o);
|
||||
if(deflatezlib(f, bwrite, &b, objbytes, 9, 0) == -1)
|
||||
sysfatal("could not write %s: %r", o);
|
||||
Bterm(f);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
writetree(Dirent *ent, int nent, Hash *h)
|
||||
{
|
||||
char *t, *txt, *etxt, hdr[128];
|
||||
int nhdr, n;
|
||||
Dirent *d, *p;
|
||||
|
||||
t = emalloc((16+256+20) * nent);
|
||||
txt = t;
|
||||
etxt = t + (16+256+20) * nent;
|
||||
|
||||
/* sqeeze out deleted entries */
|
||||
n = 0;
|
||||
p = ent;
|
||||
for(d = ent; d != ent + nent; d++)
|
||||
if(d->name)
|
||||
p[n++] = *d;
|
||||
nent = n;
|
||||
|
||||
qsort(ent, nent, sizeof(Dirent), entcmp);
|
||||
for(d = ent; d != ent + nent; d++){
|
||||
if(strlen(d->name) >= 255)
|
||||
sysfatal("overly long filename: %s", d->name);
|
||||
t = seprint(t, etxt, "%o %s", gitmode(d->mode), d->name) + 1;
|
||||
memcpy(t, d->h.h, sizeof(d->h.h));
|
||||
t += sizeof(d->h.h);
|
||||
}
|
||||
nhdr = snprint(hdr, sizeof(hdr), "%T %lld", GTree, (vlong)(t - txt)) + 1;
|
||||
writeobj(h, hdr, nhdr, txt, t - txt);
|
||||
free(txt);
|
||||
return nent;
|
||||
}
|
||||
|
||||
void
|
||||
blobify(Dir *d, char *path, int *mode, Hash *bh)
|
||||
{
|
||||
char h[64], *buf;
|
||||
int f, nh;
|
||||
|
||||
if((d->mode & DMDIR) != 0)
|
||||
sysfatal("not file: %s", path);
|
||||
*mode = d->mode;
|
||||
nh = snprint(h, sizeof(h), "%T %lld", GBlob, d->length) + 1;
|
||||
if((f = open(path, OREAD)) == -1)
|
||||
sysfatal("could not open %s: %r", path);
|
||||
buf = emalloc(d->length);
|
||||
if(readn(f, buf, d->length) != d->length)
|
||||
sysfatal("could not read blob %s: %r", path);
|
||||
writeobj(bh, h, nh, buf, d->length);
|
||||
free(buf);
|
||||
close(f);
|
||||
}
|
||||
|
||||
int
|
||||
tracked(char *path)
|
||||
{
|
||||
char ipath[256];
|
||||
Dir *d;
|
||||
|
||||
/* Explicitly removed. */
|
||||
snprint(ipath, sizeof(ipath), ".git/index9/removed/%s", path);
|
||||
if(strstr(cleanname(ipath), ".git/index9/removed") != ipath)
|
||||
sysfatal("path %s leaves index", ipath);
|
||||
d = dirstat(ipath);
|
||||
if(d != nil && d->qid.type != QTDIR){
|
||||
free(d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Explicitly added. */
|
||||
snprint(ipath, sizeof(ipath), ".git/index9/tracked/%s", path);
|
||||
if(strstr(cleanname(ipath), ".git/index9/tracked") != ipath)
|
||||
sysfatal("path %s leaves index", ipath);
|
||||
if(access(ipath, AEXIST) == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
pathelt(char *buf, int nbuf, char *p, int *isdir)
|
||||
{
|
||||
char *b;
|
||||
|
||||
b = buf;
|
||||
if(*p == '/')
|
||||
p++;
|
||||
while(*p && *p != '/' && b != buf + nbuf)
|
||||
*b++ = *p++;
|
||||
*b = '\0';
|
||||
*isdir = (*p == '/');
|
||||
return b - buf;
|
||||
}
|
||||
|
||||
Dirent*
|
||||
dirent(Dirent **ent, int *nent, char *name)
|
||||
{
|
||||
Dirent *d;
|
||||
|
||||
for(d = *ent; d != *ent + *nent; d++)
|
||||
if(d->name && strcmp(d->name, name) == 0)
|
||||
return d;
|
||||
*nent += 1;
|
||||
*ent = erealloc(*ent, *nent * sizeof(Dirent));
|
||||
d = *ent + (*nent - 1);
|
||||
memset(d, 0, sizeof(*d));
|
||||
d->name = estrdup(name);
|
||||
return d;
|
||||
}
|
||||
|
||||
int
|
||||
treeify(Object *t, char **path, char **epath, int off, Hash *h)
|
||||
{
|
||||
int r, n, ne, nsub, nent, isdir;
|
||||
char **p, **ep;
|
||||
char elt[256];
|
||||
Object **sub;
|
||||
Dirent *e, *ent;
|
||||
Dir *d;
|
||||
|
||||
r = -1;
|
||||
nsub = 0;
|
||||
nent = t->tree->nent;
|
||||
ent = eamalloc(nent, sizeof(*ent));
|
||||
sub = eamalloc((epath - path), sizeof(Object*));
|
||||
memcpy(ent, t->tree->ent, nent*sizeof(*ent));
|
||||
for(p = path; p != epath; p = ep){
|
||||
ne = pathelt(elt, sizeof(elt), *p + off, &isdir);
|
||||
for(ep = p; ep != epath; ep++){
|
||||
if(strncmp(elt, *ep + off, ne) != 0)
|
||||
break;
|
||||
if((*ep)[off+ne] != '\0' && (*ep)[off+ne] != '/')
|
||||
break;
|
||||
}
|
||||
e = dirent(&ent, &nent, elt);
|
||||
if(e->islink)
|
||||
sysfatal("symlinks may not be modified: %s", *path);
|
||||
if(e->ismod)
|
||||
sysfatal("submodules may not be modified: %s", *path);
|
||||
if(isdir){
|
||||
e->mode = DMDIR | 0755;
|
||||
sub[nsub] = readobject(e->h);
|
||||
if(sub[nsub] == nil || sub[nsub]->type != GTree)
|
||||
sub[nsub] = emptydir();
|
||||
/*
|
||||
* if after processing deletions, a tree is empty,
|
||||
* mark it for removal from the parent.
|
||||
*
|
||||
* Note, it is still written to the object store,
|
||||
* but this is fine -- and ensures that an empty
|
||||
* repository will continue to work.
|
||||
*/
|
||||
n = treeify(sub[nsub], p, ep, off + ne + 1, &e->h);
|
||||
if(n == 0)
|
||||
e->name = nil;
|
||||
else if(n == -1)
|
||||
goto err;
|
||||
}else{
|
||||
d = dirstat(*p);
|
||||
if(d != nil && tracked(*p))
|
||||
blobify(d, *p, &e->mode, &e->h);
|
||||
else
|
||||
e->name = nil;
|
||||
free(d);
|
||||
}
|
||||
}
|
||||
if(nent == 0){
|
||||
werrstr("%.*s: empty directory", off, *path);
|
||||
goto err;
|
||||
}
|
||||
|
||||
r = writetree(ent, nent, h);
|
||||
err:
|
||||
free(sub);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
mkcommit(Hash *c, char *msg, char *name, char *email, vlong date, Hash *parents, int nparents, Hash tree)
|
||||
{
|
||||
char *s, h[64];
|
||||
int ns, nh, i;
|
||||
Fmt f;
|
||||
|
||||
fmtstrinit(&f);
|
||||
fmtprint(&f, "tree %H\n", tree);
|
||||
for(i = 0; i < nparents; i++)
|
||||
fmtprint(&f, "parent %H\n", parents[i]);
|
||||
fmtprint(&f, "author %s <%s> %lld +0000\n", name, email, date);
|
||||
fmtprint(&f, "committer %s <%s> %lld +0000\n", name, email, date);
|
||||
fmtprint(&f, "\n");
|
||||
fmtprint(&f, "%s", msg);
|
||||
s = fmtstrflush(&f);
|
||||
|
||||
ns = strlen(s);
|
||||
nh = snprint(h, sizeof(h), "%T %d", GCommit, ns) + 1;
|
||||
writeobj(c, h, nh, s, ns);
|
||||
free(s);
|
||||
}
|
||||
|
||||
Object*
|
||||
findroot(void)
|
||||
{
|
||||
Object *t, *c;
|
||||
Hash h;
|
||||
|
||||
if(resolveref(&h, "HEAD") == -1)
|
||||
return emptydir();
|
||||
if((c = readobject(h)) == nil || c->type != GCommit)
|
||||
sysfatal("could not read HEAD %H", h);
|
||||
if((t = readobject(c->commit->tree)) == nil)
|
||||
sysfatal("could not read tree for commit %H", h);
|
||||
return t;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s -n name -e email -m message -d date [files...]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
Hash th, ch, parents[Maxparents];
|
||||
char *msg, *name, *email, *dstr;
|
||||
int i, r, nparents;
|
||||
vlong date;
|
||||
Object *t;
|
||||
|
||||
msg = nil;
|
||||
name = nil;
|
||||
email = nil;
|
||||
dstr = nil;
|
||||
date = time(nil);
|
||||
nparents = 0;
|
||||
gitinit();
|
||||
ARGBEGIN{
|
||||
case 'm': msg = EARGF(usage()); break;
|
||||
case 'n': name = EARGF(usage()); break;
|
||||
case 'e': email = EARGF(usage()); break;
|
||||
case 'd': dstr = EARGF(usage()); break;
|
||||
case 'p':
|
||||
if(nparents >= Maxparents)
|
||||
sysfatal("too many parents");
|
||||
if(resolveref(&parents[nparents++], EARGF(usage())) == -1)
|
||||
sysfatal("invalid parent: %r");
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}ARGEND;
|
||||
|
||||
if(!msg)
|
||||
sysfatal("missing message");
|
||||
if(!name)
|
||||
sysfatal("missing name");
|
||||
if(!email)
|
||||
sysfatal("missing email");
|
||||
if(dstr){
|
||||
date=strtoll(dstr, &dstr, 10);
|
||||
if(strlen(dstr) != 0)
|
||||
sysfatal("could not parse date %s", dstr);
|
||||
}
|
||||
if(msg == nil || name == nil)
|
||||
usage();
|
||||
for(i = 0; i < argc; i++)
|
||||
cleanname(argv[i]);
|
||||
|
||||
gitinit();
|
||||
if(access(".git", AEXIST) != 0)
|
||||
sysfatal("could not find git repo: %r");
|
||||
t = findroot();
|
||||
r = treeify(t, argv, argv + argc, 0, &th);
|
||||
if(r == -1)
|
||||
sysfatal("could not commit: %r\n");
|
||||
mkcommit(&ch, msg, name, email, date, parents, nparents, th);
|
||||
print("%H\n", ch);
|
||||
exits(nil);
|
||||
}
|
272
sys/src/cmd/git/send.c
Normal file
272
sys/src/cmd/git/send.c
Normal file
|
@ -0,0 +1,272 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
typedef struct Capset Capset;
|
||||
|
||||
struct Capset {
|
||||
int sideband;
|
||||
int sideband64k;
|
||||
int report;
|
||||
};
|
||||
|
||||
int sendall;
|
||||
int force;
|
||||
int nbranch;
|
||||
char **branch;
|
||||
char *removed[128];
|
||||
int nremoved;
|
||||
int npacked;
|
||||
int nsent;
|
||||
|
||||
int
|
||||
findref(char **r, int nr, char *ref)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < nr; i++)
|
||||
if(strcmp(r[i], ref) == 0)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
readours(Hash **tailp, char ***refp)
|
||||
{
|
||||
int nu, i, idx;
|
||||
char *r, *pfx, **ref;
|
||||
Hash *tail;
|
||||
|
||||
if(sendall)
|
||||
return listrefs(tailp, refp);
|
||||
nu = 0;
|
||||
tail = eamalloc((nremoved + nbranch), sizeof(Hash));
|
||||
ref = eamalloc((nremoved + nbranch), sizeof(char*));
|
||||
for(i = 0; i < nbranch; i++){
|
||||
ref[nu] = estrdup(branch[i]);
|
||||
if(resolveref(&tail[nu], branch[i]) == -1)
|
||||
sysfatal("broken branch %s", branch[i]);
|
||||
nu++;
|
||||
}
|
||||
for(i = 0; i < nremoved; i++){
|
||||
pfx = "refs/heads/";
|
||||
if(strstr(removed[i], "heads/") == removed[i])
|
||||
pfx = "refs/";
|
||||
if(strstr(removed[i], "refs/heads/") == removed[i])
|
||||
pfx = "";
|
||||
if((r = smprint("%s%s", pfx, removed[i])) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
if((idx = findref(ref, nu, r)) == -1)
|
||||
idx = nu++;
|
||||
assert(idx < nremoved + nbranch);
|
||||
memcpy(&tail[idx], &Zhash, sizeof(Hash));
|
||||
free(r);
|
||||
}
|
||||
dprint(1, "nu: %d\n", nu);
|
||||
for(i = 0; i < nu; i++)
|
||||
dprint(1, "update: %H %s\n", tail[i], ref[i]);
|
||||
*tailp = tail;
|
||||
*refp = ref;
|
||||
return nu;
|
||||
}
|
||||
|
||||
char *
|
||||
matchcap(char *s, char *cap, int full)
|
||||
{
|
||||
if(strncmp(s, cap, strlen(cap)) == 0)
|
||||
if(!full || strlen(s) == strlen(cap))
|
||||
return s + strlen(cap);
|
||||
return nil;
|
||||
}
|
||||
|
||||
void
|
||||
parsecaps(char *caps, Capset *cs)
|
||||
{
|
||||
char *p, *n;
|
||||
|
||||
for(p = caps; p != nil; p = n){
|
||||
n = strchr(p, ' ');
|
||||
if(n != nil)
|
||||
*n++ = 0;
|
||||
if(matchcap(p, "report-status", 1) != nil)
|
||||
cs->report = 1;
|
||||
if(matchcap(p, "side-band", 1) != nil)
|
||||
cs->sideband = 1;
|
||||
if(matchcap(p, "side-band-64k", 1) != nil)
|
||||
cs->sideband64k = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
sendpack(Conn *c)
|
||||
{
|
||||
int i, n, idx, nupd, nsp, send, first;
|
||||
char buf[Pktmax], *sp[3];
|
||||
Hash h, *theirs, *ours;
|
||||
Object *a, *b, *p;
|
||||
char **refs;
|
||||
Capset cs;
|
||||
|
||||
first = 1;
|
||||
memset(&cs, 0, sizeof(Capset));
|
||||
nupd = readours(&ours, &refs);
|
||||
theirs = eamalloc(nupd, sizeof(Hash));
|
||||
while(1){
|
||||
n = readpkt(c, buf, sizeof(buf));
|
||||
if(n == -1)
|
||||
return -1;
|
||||
if(n == 0)
|
||||
break;
|
||||
if(first && n > strlen(buf))
|
||||
parsecaps(buf + strlen(buf) + 1, &cs);
|
||||
first = 0;
|
||||
if(strncmp(buf, "ERR ", 4) == 0)
|
||||
sysfatal("%s", buf + 4);
|
||||
|
||||
if(getfields(buf, sp, nelem(sp), 1, " \t\r\n") != 2)
|
||||
sysfatal("invalid ref line %.*s", utfnlen(buf, n), buf);
|
||||
if((idx = findref(refs, nupd, sp[1])) == -1)
|
||||
continue;
|
||||
if(hparse(&theirs[idx], sp[0]) == -1)
|
||||
sysfatal("invalid hash %s", sp[0]);
|
||||
}
|
||||
|
||||
if(writephase(c) == -1)
|
||||
return -1;
|
||||
send = 0;
|
||||
if(force)
|
||||
send=1;
|
||||
for(i = 0; i < nupd; i++){
|
||||
a = readobject(theirs[i]);
|
||||
b = hasheq(&ours[i], &Zhash) ? nil : readobject(ours[i]);
|
||||
p = nil;
|
||||
if(a != nil && b != nil)
|
||||
p = ancestor(a, b);
|
||||
if(!force && !hasheq(&theirs[i], &Zhash) && (a == nil || p != a)){
|
||||
fprint(2, "remote has diverged\n");
|
||||
werrstr("force needed");
|
||||
flushpkt(c);
|
||||
return -1;
|
||||
}
|
||||
unref(a);
|
||||
unref(b);
|
||||
unref(p);
|
||||
if(hasheq(&theirs[i], &ours[i])){
|
||||
print("uptodate %s\n", refs[i]);
|
||||
continue;
|
||||
}
|
||||
print("update %s %H %H\n", refs[i], theirs[i], ours[i]);
|
||||
n = snprint(buf, sizeof(buf), "%H %H %s", theirs[i], ours[i], refs[i]);
|
||||
|
||||
/*
|
||||
* Workaround for github.
|
||||
*
|
||||
* Github will accept the pack but fail to update the references
|
||||
* if we don't have capabilities advertised. Report-status seems
|
||||
* harmless to add, so we add it.
|
||||
*
|
||||
* Github doesn't advertise any capabilities, so we can't check
|
||||
* for compatibility. We just need to add it blindly.
|
||||
*/
|
||||
if(i == 0 && cs.report){
|
||||
buf[n++] = '\0';
|
||||
n += snprint(buf + n, sizeof(buf) - n, " report-status");
|
||||
}
|
||||
if(writepkt(c, buf, n) == -1)
|
||||
sysfatal("unable to send update pkt");
|
||||
send = 1;
|
||||
}
|
||||
flushpkt(c);
|
||||
if(!send){
|
||||
fprint(2, "nothing to send\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(writepack(c->wfd, ours, nupd, theirs, nupd, &h) == -1)
|
||||
return -1;
|
||||
if(!cs.report)
|
||||
return 0;
|
||||
|
||||
if(readphase(c) == -1)
|
||||
return -1;
|
||||
/* We asked for a status report, may as well use it. */
|
||||
while((n = readpkt(c, buf, sizeof(buf))) > 0){
|
||||
buf[n] = 0;
|
||||
if(chattygit)
|
||||
fprint(2, "done sending pack, status %s\n", buf);
|
||||
nsp = getfields(buf, sp, nelem(sp), 1, " \t\n\r");
|
||||
if(nsp < 2)
|
||||
continue;
|
||||
if(nsp < 3)
|
||||
sp[2] = "";
|
||||
/*
|
||||
* Only report errors; successes will be reported by
|
||||
* surrounding scripts.
|
||||
*/
|
||||
if(strcmp(sp[0], "unpack") == 0 && strcmp(sp[1], "ok") != 0)
|
||||
fprint(2, "unpack %s\n", sp[1]);
|
||||
else if(strcmp(sp[0], "ng") == 0)
|
||||
fprint(2, "failed update: %s\n", sp[1]);
|
||||
else
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s remote [reponame]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char *br;
|
||||
Conn c;
|
||||
|
||||
ARGBEGIN{
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
case 'd':
|
||||
chattygit++;
|
||||
break;
|
||||
case 'f':
|
||||
force++;
|
||||
break;
|
||||
case 'r':
|
||||
if(nremoved == nelem(removed))
|
||||
sysfatal("too many deleted branches");
|
||||
removed[nremoved++] = EARGF(usage());
|
||||
break;
|
||||
case 'a':
|
||||
sendall++;
|
||||
break;
|
||||
case 'b':
|
||||
br = EARGF(usage());
|
||||
if(strncmp(br, "refs/heads/", strlen("refs/heads/")) == 0)
|
||||
br = smprint("%s", br);
|
||||
else if(strncmp(br, "heads/", strlen("heads/")) == 0)
|
||||
br = smprint("refs/%s", br);
|
||||
else
|
||||
br = smprint("refs/heads/%s", br);
|
||||
branch = erealloc(branch, (nbranch + 1)*sizeof(char*));
|
||||
branch[nbranch] = br;
|
||||
nbranch++;
|
||||
break;
|
||||
}ARGEND;
|
||||
|
||||
gitinit();
|
||||
if(argc != 1)
|
||||
usage();
|
||||
if(gitconnect(&c, argv[0], "receive") == -1)
|
||||
sysfatal("git connect: %s: %r", argv[0]);
|
||||
if(sendpack(&c) == -1)
|
||||
sysfatal("send failed: %r");
|
||||
closeconn(&c);
|
||||
exits(nil);
|
||||
}
|
558
sys/src/cmd/git/serve.c
Normal file
558
sys/src/cmd/git/serve.c
Normal file
|
@ -0,0 +1,558 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
#include <auth.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
char *pathpfx = "/usr/git";
|
||||
char *namespace = nil;
|
||||
int allowwrite;
|
||||
|
||||
int
|
||||
fmtpkt(Conn *c, char *fmt, ...)
|
||||
{
|
||||
char pkt[Pktmax];
|
||||
va_list ap;
|
||||
int n;
|
||||
|
||||
va_start(ap, fmt);
|
||||
n = vsnprint(pkt, sizeof(pkt), fmt, ap);
|
||||
n = writepkt(c, pkt, n);
|
||||
va_end(ap);
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
showrefs(Conn *c)
|
||||
{
|
||||
int i, ret, nrefs;
|
||||
Hash head, *refs;
|
||||
char **names;
|
||||
|
||||
ret = -1;
|
||||
nrefs = 0;
|
||||
refs = nil;
|
||||
names = nil;
|
||||
if(resolveref(&head, "HEAD") != -1)
|
||||
if(fmtpkt(c, "%H HEAD", head) == -1)
|
||||
goto error;
|
||||
|
||||
if((nrefs = listrefs(&refs, &names)) == -1)
|
||||
sysfatal("listrefs: %r");
|
||||
for(i = 0; i < nrefs; i++){
|
||||
if(strncmp(names[i], "heads/", strlen("heads/")) != 0)
|
||||
continue;
|
||||
if(fmtpkt(c, "%H refs/%s\n", refs[i], names[i]) == -1)
|
||||
goto error;
|
||||
}
|
||||
if(flushpkt(c) == -1)
|
||||
goto error;
|
||||
ret = 0;
|
||||
error:
|
||||
for(i = 0; i < nrefs; i++)
|
||||
free(names[i]);
|
||||
free(names);
|
||||
free(refs);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
servnegotiate(Conn *c, Hash **head, int *nhead, Hash **tail, int *ntail)
|
||||
{
|
||||
char pkt[Pktmax];
|
||||
int n, acked;
|
||||
Object *o;
|
||||
Hash h;
|
||||
|
||||
if(showrefs(c) == -1)
|
||||
return -1;
|
||||
|
||||
*head = nil;
|
||||
*tail = nil;
|
||||
*nhead = 0;
|
||||
*ntail = 0;
|
||||
while(1){
|
||||
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
|
||||
goto error;
|
||||
if(n == 0)
|
||||
break;
|
||||
if(strncmp(pkt, "want ", 5) != 0){
|
||||
werrstr(" protocol garble %s", pkt);
|
||||
goto error;
|
||||
}
|
||||
if(hparse(&h, &pkt[5]) == -1){
|
||||
werrstr(" garbled want");
|
||||
goto error;
|
||||
}
|
||||
if((o = readobject(h)) == nil){
|
||||
werrstr("requested nonexistent object");
|
||||
goto error;
|
||||
}
|
||||
unref(o);
|
||||
*head = erealloc(*head, (*nhead + 1)*sizeof(Hash));
|
||||
(*head)[*nhead] = h;
|
||||
*nhead += 1;
|
||||
}
|
||||
|
||||
acked = 0;
|
||||
while(1){
|
||||
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
|
||||
goto error;
|
||||
if(strncmp(pkt, "done", 4) == 0)
|
||||
break;
|
||||
if(n == 0){
|
||||
if(!acked && fmtpkt(c, "NAK") == -1)
|
||||
goto error;
|
||||
}
|
||||
if(strncmp(pkt, "have ", 5) != 0){
|
||||
werrstr(" protocol garble %s", pkt);
|
||||
goto error;
|
||||
}
|
||||
if(hparse(&h, &pkt[5]) == -1){
|
||||
werrstr(" garbled have");
|
||||
goto error;
|
||||
}
|
||||
if((o = readobject(h)) == nil)
|
||||
continue;
|
||||
if(!acked){
|
||||
if(fmtpkt(c, "ACK %H", h) == -1)
|
||||
goto error;
|
||||
acked = 1;
|
||||
}
|
||||
unref(o);
|
||||
*tail = erealloc(*tail, (*ntail + 1)*sizeof(Hash));
|
||||
(*tail)[*ntail] = h;
|
||||
*ntail += 1;
|
||||
}
|
||||
if(!acked && fmtpkt(c, "NAK\n") == -1)
|
||||
goto error;
|
||||
return 0;
|
||||
error:
|
||||
fmtpkt(c, "ERR %r\n");
|
||||
free(*head);
|
||||
free(*tail);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
servpack(Conn *c)
|
||||
{
|
||||
Hash *head, *tail, h;
|
||||
int nhead, ntail;
|
||||
|
||||
dprint(1, "negotiating pack\n");
|
||||
if(servnegotiate(c, &head, &nhead, &tail, &ntail) == -1)
|
||||
sysfatal("negotiate: %r");
|
||||
dprint(1, "writing pack\n");
|
||||
if(writepack(c->wfd, head, nhead, tail, ntail, &h) == -1)
|
||||
sysfatal("send: %r");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
validref(char *s)
|
||||
{
|
||||
if(strncmp(s, "refs/", 5) != 0)
|
||||
return 0;
|
||||
for(; *s != '\0'; s++)
|
||||
if(!isalnum(*s) && strchr("/-_.", *s) == nil)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
recvnegotiate(Conn *c, Hash **cur, Hash **upd, char ***ref, int *nupd)
|
||||
{
|
||||
char pkt[Pktmax], *sp[4];
|
||||
Hash old, new;
|
||||
int n, i;
|
||||
|
||||
if(showrefs(c) == -1)
|
||||
return -1;
|
||||
*cur = nil;
|
||||
*upd = nil;
|
||||
*ref = nil;
|
||||
*nupd = 0;
|
||||
while(1){
|
||||
if((n = readpkt(c, pkt, sizeof(pkt))) == -1)
|
||||
goto error;
|
||||
if(n == 0)
|
||||
break;
|
||||
if(getfields(pkt, sp, nelem(sp), 1, " \t\n\r") != 3){
|
||||
fmtpkt(c, "ERR protocol garble %s\n", pkt);
|
||||
goto error;
|
||||
}
|
||||
if(hparse(&old, sp[0]) == -1){
|
||||
fmtpkt(c, "ERR bad old hash %s\n", sp[0]);
|
||||
goto error;
|
||||
}
|
||||
if(hparse(&new, sp[1]) == -1){
|
||||
fmtpkt(c, "ERR bad new hash %s\n", sp[1]);
|
||||
goto error;
|
||||
}
|
||||
if(!validref(sp[2])){
|
||||
fmtpkt(c, "ERR invalid ref %s\n", sp[2]);
|
||||
goto error;
|
||||
}
|
||||
*cur = erealloc(*cur, (*nupd + 1)*sizeof(Hash));
|
||||
*upd = erealloc(*upd, (*nupd + 1)*sizeof(Hash));
|
||||
*ref = erealloc(*ref, (*nupd + 1)*sizeof(Hash));
|
||||
(*cur)[*nupd] = old;
|
||||
(*upd)[*nupd] = new;
|
||||
(*ref)[*nupd] = estrdup(sp[2]);
|
||||
*nupd += 1;
|
||||
}
|
||||
return 0;
|
||||
error:
|
||||
free(*cur);
|
||||
free(*upd);
|
||||
for(i = 0; i < *nupd; i++)
|
||||
free((*ref)[i]);
|
||||
free(*ref);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
rename(char *pack, char *idx, Hash h)
|
||||
{
|
||||
char name[128], path[196];
|
||||
Dir st;
|
||||
|
||||
nulldir(&st);
|
||||
st.name = name;
|
||||
snprint(name, sizeof(name), "%H.pack", h);
|
||||
snprint(path, sizeof(path), ".git/objects/pack/%s", name);
|
||||
if(access(path, AEXIST) == 0)
|
||||
fprint(2, "warning, pack %s already pushed\n", name);
|
||||
else if(dirwstat(pack, &st) == -1)
|
||||
return -1;
|
||||
snprint(name, sizeof(name), "%H.idx", h);
|
||||
snprint(path, sizeof(path), ".git/objects/pack/%s", name);
|
||||
if(access(path, AEXIST) == 0)
|
||||
fprint(2, "warning, pack %s already indexed\n", name);
|
||||
else if(dirwstat(idx, &st) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
checkhash(int fd, vlong sz, Hash *hcomp)
|
||||
{
|
||||
DigestState *st;
|
||||
Hash hexpect;
|
||||
char buf[Pktmax];
|
||||
vlong n, r;
|
||||
int nr;
|
||||
|
||||
if(sz < 28){
|
||||
werrstr("undersize packfile");
|
||||
return -1;
|
||||
}
|
||||
|
||||
st = nil;
|
||||
n = 0;
|
||||
if(seek(fd, 0, 0) == -1)
|
||||
sysfatal("packfile seek: %r");
|
||||
while(n != sz - 20){
|
||||
nr = sizeof(buf);
|
||||
if(sz - n - 20 < sizeof(buf))
|
||||
nr = sz - n - 20;
|
||||
r = readn(fd, buf, nr);
|
||||
if(r != nr){
|
||||
werrstr("short read");
|
||||
return -1;
|
||||
}
|
||||
st = sha1((uchar*)buf, nr, nil, st);
|
||||
n += r;
|
||||
}
|
||||
sha1(nil, 0, hcomp->h, st);
|
||||
if(readn(fd, hexpect.h, sizeof(hexpect.h)) != sizeof(hexpect.h))
|
||||
sysfatal("truncated packfile");
|
||||
if(!hasheq(hcomp, &hexpect)){
|
||||
werrstr("bad hash: %H != %H", *hcomp, hexpect);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
mkdir(char *dir)
|
||||
{
|
||||
char buf[ERRMAX];
|
||||
int f;
|
||||
|
||||
if(access(dir, AEXIST) == 0)
|
||||
return 0;
|
||||
if((f = create(dir, OREAD, DMDIR | 0755)) == -1){
|
||||
rerrstr(buf, sizeof(buf));
|
||||
if(strstr(buf, "exist") == nil)
|
||||
return -1;
|
||||
}
|
||||
close(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
updatepack(Conn *c)
|
||||
{
|
||||
char buf[Pktmax], packtmp[128], idxtmp[128], ebuf[ERRMAX];
|
||||
int n, pfd, packsz;
|
||||
Hash h;
|
||||
|
||||
/* make sure the needed dirs exist */
|
||||
if(mkdir(".git/objects") == -1)
|
||||
return -1;
|
||||
if(mkdir(".git/objects/pack") == -1)
|
||||
return -1;
|
||||
if(mkdir(".git/refs") == -1)
|
||||
return -1;
|
||||
if(mkdir(".git/refs/heads") == -1)
|
||||
return -1;
|
||||
snprint(packtmp, sizeof(packtmp), ".git/objects/pack/recv-%d.pack.tmp", getpid());
|
||||
snprint(idxtmp, sizeof(idxtmp), ".git/objects/pack/recv-%d.idx.tmp", getpid());
|
||||
if((pfd = create(packtmp, ORDWR, 0644)) == -1)
|
||||
return -1;
|
||||
packsz = 0;
|
||||
while(1){
|
||||
n = read(c->rfd, buf, sizeof(buf));
|
||||
if(n == 0)
|
||||
break;
|
||||
if(n == -1){
|
||||
rerrstr(ebuf, sizeof(ebuf));
|
||||
if(strstr(ebuf, "hungup") == nil)
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
if(write(pfd, buf, n) != n)
|
||||
return -1;
|
||||
packsz += n;
|
||||
}
|
||||
if(checkhash(pfd, packsz, &h) == -1){
|
||||
dprint(1, "hash mismatch\n");
|
||||
goto error1;
|
||||
}
|
||||
if(indexpack(packtmp, idxtmp, h) == -1){
|
||||
dprint(1, "indexing failed: %r\n");
|
||||
goto error1;
|
||||
}
|
||||
if(rename(packtmp, idxtmp, h) == -1){
|
||||
dprint(1, "rename failed: %r\n");
|
||||
goto error2;
|
||||
}
|
||||
return 0;
|
||||
|
||||
error2: remove(idxtmp);
|
||||
error1: remove(packtmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
lockrepo(void)
|
||||
{
|
||||
int fd, i;
|
||||
|
||||
for(i = 0; i < 10; i++) {
|
||||
if((fd = create(".git/_lock", ORCLOSE|ORDWR|OTRUNC|OEXCL, 0644))!= -1)
|
||||
return fd;
|
||||
sleep(250);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
updaterefs(Conn *c, Hash *cur, Hash *upd, char **ref, int nupd)
|
||||
{
|
||||
char refpath[512];
|
||||
int i, newidx, hadref, fd, ret, lockfd;
|
||||
vlong newtm;
|
||||
Object *o;
|
||||
Hash h;
|
||||
|
||||
ret = -1;
|
||||
hadref = 0;
|
||||
newidx = -1;
|
||||
/*
|
||||
* Date of Magna Carta.
|
||||
* Wrong because it was computed using
|
||||
* the proleptic gregorian calendar.
|
||||
*/
|
||||
newtm = -23811206400;
|
||||
if((lockfd = lockrepo()) == -1){
|
||||
werrstr("repo locked\n");
|
||||
return -1;
|
||||
}
|
||||
for(i = 0; i < nupd; i++){
|
||||
if(resolveref(&h, ref[i]) == 0){
|
||||
hadref = 1;
|
||||
if(!hasheq(&h, &cur[i])){
|
||||
werrstr("old ref changed: %s", ref[i]);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if(snprint(refpath, sizeof(refpath), ".git/%s", ref[i]) == sizeof(refpath)){
|
||||
werrstr("ref path too long: %s", ref[i]);
|
||||
goto error;
|
||||
}
|
||||
if(hasheq(&upd[i], &Zhash)){
|
||||
remove(refpath);
|
||||
continue;
|
||||
}
|
||||
if((o = readobject(upd[i])) == nil){
|
||||
werrstr("update to nonexistent hash %H", upd[i]);
|
||||
goto error;
|
||||
}
|
||||
if(o->type != GCommit){
|
||||
werrstr("not commit: %H", upd[i]);
|
||||
goto error;
|
||||
}
|
||||
if(o->commit->mtime > newtm){
|
||||
newtm = o->commit->mtime;
|
||||
newidx = i;
|
||||
}
|
||||
unref(o);
|
||||
if((fd = create(refpath, OWRITE|OTRUNC, 0644)) == -1){
|
||||
werrstr("open ref: %r");
|
||||
goto error;
|
||||
}
|
||||
if(fprint(fd, "%H", upd[i]) == -1){
|
||||
werrstr("upate ref: %r");
|
||||
close(fd);
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
/*
|
||||
* Heuristic:
|
||||
* If there are no valid refs, and HEAD is invalid, then
|
||||
* pick the ref with the newest commits as the default
|
||||
* branch.
|
||||
*
|
||||
* Several people have been caught out by pushing to
|
||||
* a repo where HEAD named differently from what got
|
||||
* pushed, and this is going to be more of a footgun
|
||||
* when 'master', 'main', and 'front' are all in active
|
||||
* use. This should make us pick a useful default in
|
||||
* those cases, instead of silently failing.
|
||||
*/
|
||||
if(resolveref(&h, "HEAD") == -1 && hadref == 0 && newidx != -1){
|
||||
if((fd = create(".git/HEAD", OWRITE|OTRUNC, 0644)) == -1){
|
||||
werrstr("open HEAD: %r");
|
||||
goto error;
|
||||
}
|
||||
if(fprint(fd, "ref: %s", ref[0]) == -1){
|
||||
werrstr("write HEAD ref: %r");
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
ret = 0;
|
||||
error:
|
||||
fmtpkt(c, "ERR %r");
|
||||
close(lockfd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
recvpack(Conn *c)
|
||||
{
|
||||
Hash *cur, *upd;
|
||||
char **ref;
|
||||
int nupd;
|
||||
|
||||
if(recvnegotiate(c, &cur, &upd, &ref, &nupd) == -1)
|
||||
sysfatal("negotiate refs: %r");
|
||||
if(nupd != 0 && updatepack(c) == -1)
|
||||
sysfatal("update pack: %r");
|
||||
if(nupd != 0 && updaterefs(c, cur, upd, ref, nupd) == -1)
|
||||
sysfatal("update refs: %r");
|
||||
return 0;
|
||||
}
|
||||
|
||||
char*
|
||||
parsecmd(char *buf, char *cmd, int ncmd)
|
||||
{
|
||||
int i;
|
||||
char *p;
|
||||
|
||||
for(p = buf, i = 0; *p && i < ncmd - 1; i++, p++){
|
||||
if(*p == ' ' || *p == '\t'){
|
||||
cmd[i] = 0;
|
||||
break;
|
||||
}
|
||||
cmd[i] = *p;
|
||||
}
|
||||
while(*p == ' ' || *p == '\t')
|
||||
p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-dw] [-r rel]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char *repo, cmd[32], buf[512];
|
||||
char *user;
|
||||
Conn c;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'd':
|
||||
chattygit++;
|
||||
break;
|
||||
case 'r':
|
||||
pathpfx = EARGF(usage());
|
||||
if(*pathpfx != '/')
|
||||
sysfatal("path prefix must begin with '/'");
|
||||
break;
|
||||
case 'n':
|
||||
namespace=EARGF(usage());
|
||||
break;
|
||||
case 'w':
|
||||
allowwrite++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
break;
|
||||
}ARGEND;
|
||||
|
||||
gitinit();
|
||||
user = "none";
|
||||
interactive = 0;
|
||||
if(allowwrite)
|
||||
user = getuser();
|
||||
if(newns(user, namespace) == -1)
|
||||
sysfatal("addns: %r");
|
||||
if(bind(pathpfx, "/", MREPL) == -1)
|
||||
sysfatal("bind: %r");
|
||||
if(rfork(RFNOMNT) == -1)
|
||||
sysfatal("rfork: %r");
|
||||
|
||||
initconn(&c, 0, 1);
|
||||
if(readpkt(&c, buf, sizeof(buf)) == -1)
|
||||
sysfatal("readpkt: %r");
|
||||
repo = parsecmd(buf, cmd, sizeof(cmd));
|
||||
cleanname(repo);
|
||||
if(strncmp(repo, "../", 3) == 0)
|
||||
sysfatal("invalid path %s\n", repo);
|
||||
if(bind(repo, "/", MREPL) == -1){
|
||||
fmtpkt(&c, "ERR no repo %r\n");
|
||||
sysfatal("enter %s: %r", repo);
|
||||
}
|
||||
if(chdir("/") == -1)
|
||||
sysfatal("chdir: %r");
|
||||
if(access(".git", AREAD) == -1)
|
||||
sysfatal("no git repository");
|
||||
if(strcmp(cmd, "git-receive-pack") == 0 && allowwrite)
|
||||
recvpack(&c);
|
||||
else if(strcmp(cmd, "git-upload-pack") == 0)
|
||||
servpack(&c);
|
||||
else
|
||||
sysfatal("unsupported command '%s'", cmd);
|
||||
exits(nil);
|
||||
}
|
321
sys/src/cmd/git/util.c
Normal file
321
sys/src/cmd/git/util.c
Normal file
|
@ -0,0 +1,321 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include <ctype.h>
|
||||
|
||||
#include "git.h"
|
||||
|
||||
Reprog *authorpat;
|
||||
Hash Zhash;
|
||||
|
||||
int chattygit;
|
||||
int interactive = 1;
|
||||
|
||||
Object*
|
||||
emptydir(void)
|
||||
{
|
||||
static Object *e;
|
||||
|
||||
if(e != nil)
|
||||
return ref(e);
|
||||
e = emalloc(sizeof(Object));
|
||||
e->hash = Zhash;
|
||||
e->type = GTree;
|
||||
e->tree = emalloc(sizeof(Tinfo));
|
||||
e->tree->ent = nil;
|
||||
e->tree->nent = 0;
|
||||
e->flag |= Cloaded|Cparsed;
|
||||
e->off = -1;
|
||||
ref(e);
|
||||
cache(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
int
|
||||
hasheq(Hash *a, Hash *b)
|
||||
{
|
||||
return memcmp(a->h, b->h, sizeof(a->h)) == 0;
|
||||
}
|
||||
|
||||
static int
|
||||
charval(int c, int *err)
|
||||
{
|
||||
if(c >= '0' && c <= '9')
|
||||
return c - '0';
|
||||
if(c >= 'a' && c <= 'f')
|
||||
return c - 'a' + 10;
|
||||
if(c >= 'A' && c <= 'F')
|
||||
return c - 'A' + 10;
|
||||
*err = 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *
|
||||
emalloc(ulong n)
|
||||
{
|
||||
void *v;
|
||||
|
||||
v = mallocz(n, 1);
|
||||
if(v == nil)
|
||||
sysfatal("malloc: %r");
|
||||
setmalloctag(v, getcallerpc(&n));
|
||||
return v;
|
||||
}
|
||||
|
||||
void *
|
||||
eamalloc(ulong n, ulong sz)
|
||||
{
|
||||
uvlong na;
|
||||
void *v;
|
||||
|
||||
if((na = (uvlong)n*(uvlong)sz) >= (1ULL<<30))
|
||||
sysfatal("alloc: overflow");
|
||||
v = mallocz(na, 1);
|
||||
if(v == nil)
|
||||
sysfatal("malloc: %r");
|
||||
setmalloctag(v, getcallerpc(&n));
|
||||
return v;
|
||||
}
|
||||
|
||||
void *
|
||||
erealloc(void *p, ulong n)
|
||||
{
|
||||
void *v;
|
||||
|
||||
v = realloc(p, n);
|
||||
if(v == nil)
|
||||
sysfatal("realloc: %r");
|
||||
setmalloctag(v, getcallerpc(&p));
|
||||
return v;
|
||||
}
|
||||
|
||||
void *
|
||||
earealloc(void *p, ulong n, ulong sz)
|
||||
{
|
||||
uvlong na;
|
||||
void *v;
|
||||
|
||||
if((na = (uvlong)n*(uvlong)sz) >= (1ULL<<30))
|
||||
sysfatal("alloc: overflow");
|
||||
v = realloc(p, na);
|
||||
if(v == nil)
|
||||
sysfatal("realloc: %r");
|
||||
setmalloctag(v, getcallerpc(&p));
|
||||
return v;
|
||||
}
|
||||
|
||||
char*
|
||||
estrdup(char *s)
|
||||
{
|
||||
s = strdup(s);
|
||||
if(s == nil)
|
||||
sysfatal("strdup: %r");
|
||||
setmalloctag(s, getcallerpc(&s));
|
||||
return s;
|
||||
}
|
||||
|
||||
int
|
||||
Hfmt(Fmt *fmt)
|
||||
{
|
||||
Hash h;
|
||||
int i, n, l;
|
||||
char c0, c1;
|
||||
|
||||
l = 0;
|
||||
h = va_arg(fmt->args, Hash);
|
||||
for(i = 0; i < sizeof h.h; i++){
|
||||
n = (h.h[i] >> 4) & 0xf;
|
||||
c0 = (n >= 10) ? n-10 + 'a' : n + '0';
|
||||
n = h.h[i] & 0xf;
|
||||
c1 = (n >= 10) ? n-10 + 'a' : n + '0';
|
||||
l += fmtprint(fmt, "%c%c", c0, c1);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int
|
||||
Tfmt(Fmt *fmt)
|
||||
{
|
||||
int t;
|
||||
int l;
|
||||
|
||||
t = va_arg(fmt->args, int);
|
||||
switch(t){
|
||||
case GNone: l = fmtprint(fmt, "none"); break;
|
||||
case GCommit: l = fmtprint(fmt, "commit"); break;
|
||||
case GTree: l = fmtprint(fmt, "tree"); break;
|
||||
case GBlob: l = fmtprint(fmt, "blob"); break;
|
||||
case GTag: l = fmtprint(fmt, "tag"); break;
|
||||
case GOdelta: l = fmtprint(fmt, "odelta"); break;
|
||||
case GRdelta: l = fmtprint(fmt, "gdelta"); break;
|
||||
default: l = fmtprint(fmt, "?%d?", t); break;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int
|
||||
Ofmt(Fmt *fmt)
|
||||
{
|
||||
Object *o;
|
||||
int l;
|
||||
|
||||
o = va_arg(fmt->args, Object *);
|
||||
print("== %H (%T) ==\n", o->hash, o->type);
|
||||
switch(o->type){
|
||||
case GTree:
|
||||
l = fmtprint(fmt, "tree\n");
|
||||
break;
|
||||
case GBlob:
|
||||
l = fmtprint(fmt, "blob %s\n", o->data);
|
||||
break;
|
||||
case GCommit:
|
||||
l = fmtprint(fmt, "commit\n");
|
||||
break;
|
||||
case GTag:
|
||||
l = fmtprint(fmt, "tag\n");
|
||||
break;
|
||||
default:
|
||||
l = fmtprint(fmt, "invalid: %d\n", o->type);
|
||||
break;
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int
|
||||
Qfmt(Fmt *fmt)
|
||||
{
|
||||
Qid q;
|
||||
|
||||
q = va_arg(fmt->args, Qid);
|
||||
return fmtprint(fmt, "Qid{path=0x%llx(dir:%d,obj:%lld), vers=%ld, type=%d}",
|
||||
q.path, QDIR(&q), (q.path >> 8), q.vers, q.type);
|
||||
}
|
||||
|
||||
void
|
||||
gitinit(void)
|
||||
{
|
||||
fmtinstall('H', Hfmt);
|
||||
fmtinstall('T', Tfmt);
|
||||
fmtinstall('O', Ofmt);
|
||||
fmtinstall('Q', Qfmt);
|
||||
inflateinit();
|
||||
deflateinit();
|
||||
authorpat = regcomp("[\t ]*(.*)[\t ]+([0-9]+)[\t ]+([\\-+]?[0-9]+)");
|
||||
osinit(&objcache);
|
||||
}
|
||||
|
||||
int
|
||||
hparse(Hash *h, char *b)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
err = 0;
|
||||
for(i = 0; i < sizeof(h->h); i++){
|
||||
err = 0;
|
||||
h->h[i] = 0;
|
||||
h->h[i] |= ((charval(b[2*i], &err) & 0xf) << 4);
|
||||
h->h[i] |= ((charval(b[2*i+1], &err)& 0xf) << 0);
|
||||
if(err){
|
||||
werrstr("invalid hash");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
slurpdir(char *p, Dir **d)
|
||||
{
|
||||
int r, f;
|
||||
|
||||
if((f = open(p, OREAD)) == -1)
|
||||
return -1;
|
||||
r = dirreadall(f, d);
|
||||
close(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
int
|
||||
hassuffix(char *base, char *suf)
|
||||
{
|
||||
int nb, ns;
|
||||
|
||||
nb = strlen(base);
|
||||
ns = strlen(suf);
|
||||
if(ns <= nb && strcmp(base + (nb - ns), suf) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
swapsuffix(char *dst, int dstsz, char *base, char *oldsuf, char *suf)
|
||||
{
|
||||
int bl, ol, sl, l;
|
||||
|
||||
bl = strlen(base);
|
||||
ol = strlen(oldsuf);
|
||||
sl = strlen(suf);
|
||||
l = bl + sl - ol;
|
||||
if(l + 1 > dstsz || ol > bl)
|
||||
return -1;
|
||||
memmove(dst, base, bl - ol);
|
||||
memmove(dst + bl - ol, suf, sl);
|
||||
dst[l] = 0;
|
||||
return l;
|
||||
}
|
||||
|
||||
char *
|
||||
strip(char *s)
|
||||
{
|
||||
char *e;
|
||||
|
||||
while(isspace(*s))
|
||||
s++;
|
||||
e = s + strlen(s);
|
||||
while(e > s && isspace(*--e))
|
||||
*e = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
_dprint(char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprint(2, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Finds the directory containing the git repo. */
|
||||
int
|
||||
findrepo(char *buf, int nbuf)
|
||||
{
|
||||
char *p, *suff;
|
||||
|
||||
suff = "/.git/HEAD";
|
||||
if(getwd(buf, nbuf - strlen(suff) - 1) == nil)
|
||||
return -1;
|
||||
|
||||
for(p = buf + strlen(buf); p != nil; p = strrchr(buf, '/')){
|
||||
strcpy(p, suff);
|
||||
if(access(buf, AEXIST) == 0){
|
||||
p[p == buf] = '\0';
|
||||
return 0;
|
||||
}
|
||||
*p = '\0';
|
||||
}
|
||||
werrstr("not a git repository");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
showprogress(int x, int pct)
|
||||
{
|
||||
if(!interactive)
|
||||
return 0;
|
||||
if(x > pct){
|
||||
pct = x;
|
||||
fprint(2, "\b\b\b\b%3d%%", pct);
|
||||
}
|
||||
return pct;
|
||||
}
|
333
sys/src/cmd/git/walk.c
Normal file
333
sys/src/cmd/git/walk.c
Normal file
|
@ -0,0 +1,333 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
#include "git.h"
|
||||
|
||||
#define NCACHE 4096
|
||||
#define TDIR ".git/index9/tracked"
|
||||
#define RDIR ".git/index9/removed"
|
||||
#define HDIR "/mnt/git/HEAD/tree"
|
||||
typedef struct Cache Cache;
|
||||
typedef struct Wres Wres;
|
||||
struct Cache {
|
||||
Dir* cache;
|
||||
int n;
|
||||
int max;
|
||||
};
|
||||
|
||||
struct Wres {
|
||||
char **path;
|
||||
int npath;
|
||||
int pathsz;
|
||||
};
|
||||
|
||||
enum {
|
||||
Rflg = 1 << 0,
|
||||
Mflg = 1 << 1,
|
||||
Aflg = 1 << 2,
|
||||
Tflg = 1 << 3,
|
||||
};
|
||||
|
||||
Cache seencache[NCACHE];
|
||||
int quiet;
|
||||
int printflg;
|
||||
char *rstr = "R ";
|
||||
char *tstr = "T ";
|
||||
char *mstr = "M ";
|
||||
char *astr = "A ";
|
||||
|
||||
int
|
||||
seen(Dir *dir)
|
||||
{
|
||||
Dir *dp;
|
||||
int i;
|
||||
Cache *c;
|
||||
|
||||
c = &seencache[dir->qid.path&(NCACHE-1)];
|
||||
dp = c->cache;
|
||||
for(i=0; i<c->n; i++, dp++)
|
||||
if(dir->qid.path == dp->qid.path &&
|
||||
dir->type == dp->type &&
|
||||
dir->dev == dp->dev)
|
||||
return 1;
|
||||
if(c->n == c->max){
|
||||
if (c->max == 0)
|
||||
c->max = 8;
|
||||
else
|
||||
c->max += c->max/2;
|
||||
c->cache = realloc(c->cache, c->max*sizeof(Dir));
|
||||
if(c->cache == nil)
|
||||
sysfatal("realloc: %r");
|
||||
}
|
||||
c->cache[c->n++] = *dir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
grow(Wres *r)
|
||||
{
|
||||
if(r->npath == r->pathsz){
|
||||
r->pathsz = 2*r->pathsz + 1;
|
||||
r->path = erealloc(r->path, r->pathsz * sizeof(char*));
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
readpaths(Wres *r, char *pfx, char *dir)
|
||||
{
|
||||
char *f, *sub, *full, *sep;
|
||||
Dir *d;
|
||||
int fd, ret, i, n;
|
||||
|
||||
d = nil;
|
||||
ret = -1;
|
||||
sep = "";
|
||||
if(dir[0] != 0)
|
||||
sep = "/";
|
||||
if((full = smprint("%s/%s", pfx, dir)) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
if((fd = open(full, OREAD)) < 0)
|
||||
goto error;
|
||||
while((n = dirread(fd, &d)) > 0){
|
||||
for(i = 0; i < n; i++){
|
||||
if(seen(&d[i]))
|
||||
continue;
|
||||
if(d[i].qid.type & QTDIR){
|
||||
if((sub = smprint("%s%s%s", dir, sep, d[i].name)) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
if(readpaths(r, pfx, sub) == -1){
|
||||
free(sub);
|
||||
goto error;
|
||||
}
|
||||
free(sub);
|
||||
}else{
|
||||
grow(r);
|
||||
if((f = smprint("%s%s%s", dir, sep, d[i].name)) == nil)
|
||||
sysfatal("smprint: %r");
|
||||
r->path[r->npath++] = f;
|
||||
}
|
||||
}
|
||||
free(d);
|
||||
}
|
||||
ret = r->npath;
|
||||
error:
|
||||
close(fd);
|
||||
free(full);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
cmp(void *pa, void *pb)
|
||||
{
|
||||
return strcmp(*(char **)pa, *(char **)pb);
|
||||
}
|
||||
|
||||
void
|
||||
dedup(Wres *r)
|
||||
{
|
||||
int i, o;
|
||||
|
||||
if(r->npath <= 1)
|
||||
return;
|
||||
o = 0;
|
||||
qsort(r->path, r->npath, sizeof(r->path[0]), cmp);
|
||||
for(i = 1; i < r->npath; i++)
|
||||
if(strcmp(r->path[o], r->path[i]) != 0)
|
||||
r->path[++o] = r->path[i];
|
||||
r->npath = o + 1;
|
||||
}
|
||||
|
||||
int
|
||||
sameqid(Dir *d, char *qf)
|
||||
{
|
||||
char indexqid[64], fileqid[64], *p;
|
||||
int fd, n;
|
||||
|
||||
if(!d)
|
||||
return 0;
|
||||
if((fd = open(qf, OREAD)) == -1)
|
||||
return 0;
|
||||
if((n = readn(fd, indexqid, sizeof(indexqid) - 1)) == -1)
|
||||
return 0;
|
||||
indexqid[n] = 0;
|
||||
close(fd);
|
||||
if((p = strpbrk(indexqid, " \t\n\r")) != nil)
|
||||
*p = 0;
|
||||
|
||||
snprint(fileqid, sizeof(fileqid), "%ullx.%uld.%.2uhhx",
|
||||
d->qid.path, d->qid.vers, d->qid.type);
|
||||
|
||||
if(strcmp(indexqid, fileqid) == 0)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
writeqid(Dir *d, char *qf)
|
||||
{
|
||||
int fd;
|
||||
|
||||
if((fd = create(qf, OWRITE, 0666)) == -1)
|
||||
return;
|
||||
fprint(fd, "%ullx.%uld.%.2uhhx\n",
|
||||
d->qid.path, d->qid.vers, d->qid.type);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
int
|
||||
samedata(char *pa, char *pb)
|
||||
{
|
||||
char ba[32*1024], bb[32*1024];
|
||||
int fa, fb, na, nb, same;
|
||||
|
||||
same = 0;
|
||||
fa = open(pa, OREAD);
|
||||
fb = open(pb, OREAD);
|
||||
if(fa == -1 || fb == -1){
|
||||
goto mismatch;
|
||||
}
|
||||
while(1){
|
||||
if((na = readn(fa, ba, sizeof(ba))) == -1)
|
||||
goto mismatch;
|
||||
if((nb = readn(fb, bb, sizeof(bb))) == -1)
|
||||
goto mismatch;
|
||||
if(na != nb)
|
||||
goto mismatch;
|
||||
if(na == 0)
|
||||
break;
|
||||
if(memcmp(ba, bb, na) != 0)
|
||||
goto mismatch;
|
||||
}
|
||||
same = 1;
|
||||
mismatch:
|
||||
if(fa != -1)
|
||||
close(fa);
|
||||
if(fb != -1)
|
||||
close(fb);
|
||||
return same;
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprint(2, "usage: %s [-qbc] [-f filt] [paths...]\n", argv0);
|
||||
exits("usage");
|
||||
}
|
||||
|
||||
void
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char *rpath, *tpath, *bpath, buf[8], repo[512];
|
||||
char *p, *e;
|
||||
int i, dirty;
|
||||
Wres r;
|
||||
Dir *d;
|
||||
|
||||
ARGBEGIN{
|
||||
case 'q':
|
||||
quiet++;
|
||||
break;
|
||||
case 'c':
|
||||
rstr = "";
|
||||
tstr = "";
|
||||
mstr = "";
|
||||
astr = "";
|
||||
break;
|
||||
case 'f':
|
||||
for(p = EARGF(usage()); *p; p++)
|
||||
switch(*p){
|
||||
case 'T': printflg |= Tflg; break;
|
||||
case 'A': printflg |= Aflg; break;
|
||||
case 'M': printflg |= Mflg; break;
|
||||
case 'R': printflg |= Rflg; break;
|
||||
default: usage(); break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}ARGEND
|
||||
|
||||
if(access("/mnt/git/ctl", AEXIST) != 0)
|
||||
sysfatal("no running git/fs");
|
||||
if(findrepo(repo, sizeof(repo)) == -1)
|
||||
sysfatal("find root: %r");
|
||||
if(chdir(repo) == -1)
|
||||
sysfatal("chdir: %r");
|
||||
dirty = 0;
|
||||
memset(&r, 0, sizeof(r));
|
||||
if(access("/mnt/git/ctl", AEXIST) != 0)
|
||||
sysfatal("git/fs does not seem to be running");
|
||||
if(printflg == 0)
|
||||
printflg = Tflg | Aflg | Mflg | Rflg;
|
||||
if(argc == 0){
|
||||
if(access(TDIR, AEXIST) == 0 && readpaths(&r, TDIR, "") == -1)
|
||||
sysfatal("read tracked: %r");
|
||||
if(access(RDIR, AEXIST) == 0 && readpaths(&r, RDIR, "") == -1)
|
||||
sysfatal("read removed: %r");
|
||||
}else{
|
||||
for(i = 0; i < argc; i++){
|
||||
tpath = smprint(TDIR"/%s", argv[i]);
|
||||
rpath = smprint(RDIR"/%s", argv[i]);
|
||||
if((d = dirstat(tpath)) == nil && (d = dirstat(rpath)) == nil)
|
||||
goto nextarg;
|
||||
if(d->mode & DMDIR){
|
||||
readpaths(&r, TDIR, argv[i]);
|
||||
readpaths(&r, RDIR, argv[i]);
|
||||
}else{
|
||||
grow(&r);
|
||||
r.path[r.npath++] = estrdup(argv[i]);
|
||||
}
|
||||
nextarg:
|
||||
free(tpath);
|
||||
free(rpath);
|
||||
free(d);
|
||||
}
|
||||
}
|
||||
dedup(&r);
|
||||
|
||||
for(i = 0; i < r.npath; i++){
|
||||
p = r.path[i];
|
||||
d = dirstat(p);
|
||||
if(d && d->mode & DMDIR)
|
||||
goto next;
|
||||
rpath = smprint(RDIR"/%s", p);
|
||||
tpath = smprint(TDIR"/%s", p);
|
||||
bpath = smprint(HDIR"/%s", p);
|
||||
/* Fast path: we don't want to force access to the rpath. */
|
||||
if(d && sameqid(d, tpath)) {
|
||||
if(!quiet && (printflg & Tflg))
|
||||
print("%s%s\n", tstr, p);
|
||||
}else{
|
||||
if(d == nil || access(rpath, AEXIST) == 0){
|
||||
dirty |= Rflg;
|
||||
if(!quiet && (printflg & Rflg))
|
||||
print("%s%s\n", rstr, p);
|
||||
}else if(access(bpath, AEXIST) == -1) {
|
||||
dirty |= Aflg;
|
||||
if(!quiet && (printflg & Aflg))
|
||||
print("%s%s\n", astr, p);
|
||||
}else if(samedata(p, bpath)){
|
||||
if(!quiet && (printflg & Tflg))
|
||||
print("%s%s\n", tstr, p);
|
||||
writeqid(d, tpath);
|
||||
}else{
|
||||
dirty |= Mflg;
|
||||
if(!quiet && (printflg & Mflg))
|
||||
print("%s%s\n", mstr, p);
|
||||
}
|
||||
}
|
||||
free(rpath);
|
||||
free(tpath);
|
||||
free(bpath);
|
||||
next:
|
||||
free(d);
|
||||
}
|
||||
if(!dirty)
|
||||
exits(nil);
|
||||
|
||||
p = buf;
|
||||
e = buf + sizeof(buf);
|
||||
for(i = 0; (1 << i) != Tflg; i++)
|
||||
if(dirty & (1 << i))
|
||||
p = seprint(p, e, "%c", "DMAT"[i]);
|
||||
exits(buf);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue